2

SOLVED. SEE THE SOLUTION SECTION BELOW.

I've got a problem where my build environment is outputting a large binary file, and I'm hoping that someone can help get me moving again.

I'm using an STM32F105 processor, Eclipse, FreeRTOS, and CodeSourcery compiler to try to get some AHRS evaluation code running on this device. I have much of the code running, but I ran into a problem when implementing the section of the eval code which uses malloc to allocate memory. I had to add some code for _sbrk to get it to compile, and now my binary went from 35K to almost 400MB. I think that this is a linker problem, as the .out file (before objcopy) is about the same size. Even the .s files output from the objdump look pretty comparable.

Here are some (hopefully) relevant bits and pieces:

MEMORY
{
  RAM      (RWX) : ORIGIN = 0x20000000, LENGTH = 20K
  FLASH    (RX)  : ORIGIN = 0x08000000, LENGTH = 128K
}

_estack = ORIGIN(RAM)+LENGTH(RAM);

SECTIONS
{
  .text ORIGIN(FLASH):
  {
    *(.isr_vector)
    *(.text)
    *(.text.*)
    *(.rodata)
    _sidata = .;
  }

  .data ORIGIN(RAM):
  AT (_sidata) 
  {
    _sdata = . ;
    *(.data)
    _edata = . ;
  }

  .bss (_edata) (NOLOAD):
  {
    _sbss = .;
    *(.bss)
    *(.bss.*)
    *(COMMON)
    _ebss = . ;
    . = ALIGN(4);
   _end = .;
  }
}


/* end of allocated ram _end */
PROVIDE( _HEAP_START = _end );

/* end of the heap -> align 4 byte */ 
PROVIDE ( _HEAP_END = ALIGN(ORIGIN(RAM) + LENGTH(RAM) - 4 ,4) );

The makefile:

BOOT = boot
RTOS = FreeRTOSSource
FREERTOS = $(RTOS)/port.c $(RTOS)/tasks.c $(RTOS)/croutine.c $(RTOS)/heap_2.c $(RTOS)/list.c $(RTOS)/queue.c 
APP_SOURCE = app_source/sysmon.c app_source/hscan.c app_source/gps.c app_source/mems.c app_source/gpio.c app_source/mainstates.c app_source/leds.c app_source/database.c
INEMO_LIB = inemo/mems/LSM303DLH.c inemo/mems/L3GD20.c

OBJS = main.o \
      $(BOOT)/core_cm3.o \
      $(BOOT)/system_stm32f10x.o \
      $(BOOT)/stm32f10x_rcc.o \
      $(BOOT)/stm32f10x_gpio.o \
      $(BOOT)/stm32f10x_can.o \
      $(BOOT)/stm32f10x_iwdg.o \
      $(BOOT)/stm32f10x_i2c.o \
      $(BOOT)/startup.o \
      $(BOOT)/mx_gpio.o \
      $(FREERTOS:%.c=%.o) \
      $(INEMO_LIB:%.c=%.o) \
      $(APP_SOURCE:%.c=%.o)

CFLAGS = -O0 -gdwarf-2 -mcpu=cortex-m3 -mthumb -fno-common -I$(BOOT) -std=gnu99 -c -mfloat-abi=soft -Wall -g
LFLAGS  = -mthumb -mcpu=cortex-m3 -Tscripts/stm32f103.ld -nostartfiles -lgcc -lm -lc -mfloat-abi=soft -Wall -g -O0
CPFLAGS = -O binary 

TARGET = arm-none-eabi
#TARGET = arm-elf

CC = $(TARGET)-gcc

LD = $(TARGET)-gcc
CP = $(TARGET)-objcopy
OD = $(TARGET)-objdump

all: version $(OBJS) link

$(BOOT)/startup.o:
    $(CC) $(CFLAGS) $(BOOT)/startup_stm32f10x_cl.s -lm -lc -lnosys -o $@

%.o: %.c
    $(CC) $(CFLAGS) $< -o $@

version:
    $(CC) --version

link: $(OBJS)
    $(LD) -o main.out $(OBJS) $(LFLAGS) 
    $(CP) $(CPFLAGS) main.out main.bin
    $(OD) -D -h main.out > main.S

clean:
    rm -rf $(OBJS) main.bin main.out main.S

The addition of this code plus a call to malloc causes the binary to grow to almost 400MB:

#include <sys/types.h>

extern unsigned int _HEAP_START;
caddr_t * _sbrk(int incr) {

      static unsigned char *heap = NULL;
      unsigned char *prev_heap;
      if (heap == NULL) {
          heap = (unsigned char *)_HEAP_START;
      }

      prev_heap = heap;
      heap += incr;
      return (caddr_t) prev_heap;
}

Any thoughts on how to get moving again? Thanks for any help you can provide!

THE SOLUTION

With the comments of Notlikethat, I saw that another section of code was getting created in the build process, but the linker script didn't have a section with the same name. The linker decided to put this section in RAM, when it should have put it in FLASH. Since it spanned RAM and FLASH, the bin file filled the area between them, causing the large binary. Adding the following line to the linker script (in the FLASH section), allowed the code to build again at a normal size.

*(.rodata.str1.4)

The new complete linker script looks like this:

MEMORY
{
  RAM      (RWX) : ORIGIN = 0x20000000, LENGTH = 20K
  FLASH    (RX)  : ORIGIN = 0x08000000, LENGTH = 128K
}

_estack = ORIGIN(RAM)+LENGTH(RAM);

SECTIONS
{
  .text ORIGIN(FLASH):
  {
    *(.isr_vector)
    *(.text)
    *(.text.*)
    *(.rodata)
    *(.rodata.str1.4)
    _sidata = .;
  }

  .data ORIGIN(RAM):
  AT (_sidata) 
  {
    _sdata = . ;
    *(.data)
    _edata = . ;
  }

  .bss (_edata) (NOLOAD):
  {
    _sbss = .;
    *(.bss)
    *(.bss.*)
    *(COMMON)
    _ebss = . ;
    . = ALIGN(4);
   _end = .;
  }
}


/* end of allocated ram _end */
PROVIDE( _HEAP_START = _end );

/* end of the heap -> align 4 byte */ 
PROVIDE ( _HEAP_END = ALIGN(ORIGIN(RAM) + LENGTH(RAM) - 4 ,4) );

Thanks for the help!

Dan Puccio
  • 23
  • 5
  • Good thought. I had a look through the .s file after the build process, and I didn't see anything which looked like that might be the case. The list of additional variables is small in the source code between a "good" (normal sized binary) build and a "bad" (big binary) one. Is there any easy (non-human-eyeball dependent) way to test for that? – Dan Puccio Feb 11 '15 at 22:33
  • Really, 400MB? Not 400KB? – TonyK Feb 12 '15 at 18:52
  • Yes, 400MB. Quite large! – Dan Puccio Feb 12 '15 at 19:54
  • I would spell `*(.rodata.str1.4)` as `*(.rodata.*)` to cover any other surprising subsections of read-only data that might appear as you maintain the code. I'd also wonder if `*(.data.*)` might not be wise to include right after `*(.data)` for similar hardening. But at least you're aware that your tools can occasionally produce "interestingly" named sections that need to be accounted for in the linker script. – RBerteig Feb 12 '15 at 22:08

1 Answers1

1

It looks like you have something inserting itself into a RAM section in a way the linker script doesn't catch. You can use objdump or similar on the final ELF to inspect the symbol table and check for anything suspicious, e.g. building some trivial code with this linker script gives:

$ arm-none-eabi-objdump -t a.out

a.out:     file format elf32-littlearm

SYMBOL TABLE:
20000000 l    d  .note.gnu.build-id 00000000 .note.gnu.build-id
08000000 l    d  .text  00000000 .text
20000000 l    d  .data  00000000 .data
20000004 l    d  .bss   00000000 .bss
00000000 l    d  .comment   00000000 .comment
00000000 l    d  .ARM.attributes    00000000 .ARM.attributes
00000000 l    df *ABS*  00000000 test.c
00000000 l    df *ABS*  00000000 sum.c
00000000 l    df *ABS*  00000000 
20000000 g     O .data  00000004 j
080000c0 g       .text  00000000 _sidata
20000004 g       .bss   00000000 _sbss
08000038 g     F .text  0000003c sum
20000000 g       .data  00000000 _sdata
080000bc g     O .text  00000004 k
20000008 g       .bss   00000000 _ebss
20000004 g     O .bss   00000004 i
08000000 g     F .text  00000038 main
08000074 g     F .text  00000048 sum2
20005000 g       *ABS*  00000000 _estack
20000004 g       .data  00000000 _edata
20000008 g       .bss   00000000 _end

In this case, there are a few symbols with RAM addresses, but what causes this final binary to blow up to ~400MB in this case is the .note.gnu.build-id entry. Checking the section headers reveals why:

$ arm-none-eabi-objdump -h a.out

a.out:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .note.gnu.build-id 00000024  20000000  20000000  00030000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         00000074  08000000  08000000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .data         00000004  20000000  08000074  00020000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000004  20000004  08000078  00020004  2**2
                  ALLOC
  4 .comment      00000036  00000000  00000000  00030024  2**0
                  CONTENTS, READONLY
  5 .ARM.attributes 00000033  00000000  00000000  0003005a  2**0
                  CONTENTS, READONLY

The .data and .bss sections have RAM virtual addresses (VMA), but load addresses still in flash (LMA). The note section on the other hand has a RAM load address as well, so converting the ELF to a raw binary causes it to placed ~400MB after the other sections in the image.

From the extra details provided, it sounds like library functions are generating their own .rodata sections which don't match anything in the script, thus get allocated fairly arbitrarily by the linker's heuristics. I'd try adding *(.rodata.*) to the .text section to catch those.

Notlikethat
  • 20,095
  • 3
  • 40
  • 77
  • OK, I'm feeling a bit dense, but don't you want j in the .data (RAM) memory area? On a previously working build, I had a global variable (the unit's internal CAN address) declared and defined as you did in your example, and that appears to get placed in .data. 2000002c l O .data 00000001 NMEA_Source_Address – Dan Puccio Feb 12 '15 at 16:46
  • Oops, sorry, turns out despite reproducing the issue my reasoning was a bit off. The linker script _is_ clever enough to handle a relocatable .data section, but other crud slips past. – Notlikethat Feb 12 '15 at 18:52
  • Wow, Notlikethat, thanks for your help on this. I can't tell you how much I appreciate it. I have isolated the issue in the source code to one call to sqrt. If I comment that out, then the binary builds at a normal size. If I put it back in, the binary becomes very big. I'm sure that what you said is happening, because I changed the addresses in the linker scripts, and the built binary size changes, so something is spanning RAM and FLASH. A new section appears called .rodata.str1.4 on a "bad" build. It looks like that is going into RAM. – Dan Puccio Feb 12 '15 at 19:53
  • I think that I'm starting to understand your comment. What I don't understand is how that section gets created (since that name doesn't exist in the linker script) and also how to fix it. – Dan Puccio Feb 12 '15 at 19:58
  • Great job - it's probably worth adding those details to the question for posterity. I think what you're missing is that the compiler/assembler create input sections, and the linker arranges them into output sections. If the linker script _doesn't_ specify where to put a particular input section, it more or less just gets shoved anywhere that works (see the docs I linked to; no, I don't really understand it either ;) – Notlikethat Feb 12 '15 at 20:40
  • I added a section to the linker script in the FLASH area called *(.rodata.str1.4), and that seems to allow the code to build at a normal size. Now the WDT is causing the unit to reset, but that's a separate issue. Thnaks for your help Notlikethat. I appreciate it very much! – Dan Puccio Feb 12 '15 at 20:52