2

Recently I realized that the size of my executables is quite large. I am developing software for Cortex-M microcontrollers, using Eclipse and GCC.

To check this out, I used an example project I found on the internet, that simply blinks an LED by manipulating the registers directly and that is makefile based.

I created a very similar project using my libraries, startup code, linker scripts etc that uses Eclipse's managed makefiles.

The first project compiled successfully, and produced a binary file of app. 6kB. The second project produced a binary file of app.48kB! That is obviously quite a large difference for essentially the same result, and the later is definitely a huge file, for just blinking an LED. In both cases optimizations were off.

In my own libraries, there are some volatile buffers, that may be the excuse for the large BSS, or data sections, so I decided to begin by concentrating on the text section (which is still 5 times larger 5kB to 27kB).

I took a look at the map file to see what is really linked to the binary file. Same or similar functions had also similar size.

There is one thing that seems very-very odd to me. There are functions wich are defined only once in the whole project, but appear to have been linked multiple times, each time from a different object file, and each time occupying space in the text section. Take a look for example to function .text.port_lock.

Is this normal? How can I reduce the final file size, and how can I tell the toolchain to only link once each function?

The map file

Edit: As stated in the comments the two programs are not different, it is the same thing, with minor modifications (e.g. startup code, and function to access the GPIO register). I am not testing GCC's ability to optimize code, thus I used -O0. I try to understand the map file, and why I see the some functions multiple times.

  • So to be clear, you're saying that you are comparing the compiled sizes of two completely separate and independent programs that perform the same (simple) function? – John Bollinger Apr 16 '16 at 21:57
  • GCC has an option, `-Os`, to optimize for small executable size. You could consider turning that on. Or even `-O1` might do better size-wise than no optimization at all. As for multiple copies of the same function in the executable image -- if indeed that's really what you see -- you haven't given us much to go on. Can you produce a [mcve] exhibiting the problem? – John Bollinger Apr 16 '16 at 22:02
  • Deliberately the optimizations where turned off. I am not testing the compiler's ability to optimize code. I am investigating how can I optimize the code. The two programs, are not different. They are the same, with some very minor modifications. The question is about understanding the map file, and way I see the same function multiple times. – Fotis Panagiotopoulos Apr 17 '16 at 01:53

1 Answers1

5

You are misreading the map file. None of the occurrences of .text.port_lock, for example, represents a definition of the ChibiOS function void port_lock(void).

All the occurrences of .text.port_lock refer to input linker sections.

The first 4 occurrences, lying within the section of the map file titled Discarded input sections, refer to input linker sections that the linker discarded. For example:

.text.port_lock
                0x00000000       0x1c /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chmempools.o)

means that the linker found a section .text.port_lock of size 28 bytes in input file /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chmempools.o) and threw it away.

The next 6 occurrences, lying within the the section of the map file titled Linker script and memory map all refer to input linker sections that were mapped into the output .text section. For example the first one:

.text.port_lock
            0x000012a8       0x1c /tmp/ccaossic.ltrans0.ltrans.o

means that the linker found a section .text.port_lock of size 28 bytes in input file /tmp/ccaossic.ltrans0.ltrans.o and mapped it at address 0x000012a8 in the output .text section. Likewise the second occurrence:

.text.port_lock
                0x00001f70       0x1c /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chsys.o)

means that an input section of the same name and size also was found in input file /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chsys.o) and was mapped at address 0x00001f70 in the output .text section.

Altogether there are .text.port_lock input sections, all of them 28 bytes, mapped in your output .text section from these input files:

/tmp/ccaossic.ltrans0.ltrans.o
/home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chsys.o)
/home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chthreads.o)
/home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chcore_v7m.o)
/home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chmemcore.o)
/home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chschd.o)

In all 6 of these cases, the input section contains no symbols, and in particular no functions. For contrast, here is an example of an input section that does contain symbols:

 .text          0x000002f0       0x28 /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chcoreasm_v7m.o)
                0x000002f0                _port_switch
                0x00000300                _port_thread_start
                0x00000310                _port_switch_from_isr
                0x00000314                _port_exit_from_isr

This is the input .text section from /home/fotis/Documents/Resources/Chibios/Chibios/Debug/libChibios.a(chcoreasm_v7m.o).

The map file contains no indication that the port_lock function is linked multiple times. It contains no indication that this function is linked at all. If it were linked multiple times then there would have been a multiple-definition linkage error (except in the event that it had been annotated as a weak symbol).

Why these six 28-byte input sections containing no symbols are all linked, or if they need to be, is a matter about which I have no adequate evidence or ChibiOS expertise. I notice that all but one of the object files from which these input sections come are archive members of libChibios. In that light, it is worth remembering that if your linkage requires an archive member for any reason then by default you will link the whole archive member, even it contains more stuff than you need. On the other hand, the fact that some port_lock input sections are discarded and some are kept suggests that there is a need to keep the ones that are kept. If for my own cunning reasons I write a source file essentially like:

static int __attribute__((section(".text.foo"))) __attribute__((used)) 
boo(int i)
{
    return i * 2;
} 

int bar(int i) 
{
    return boo(i);
}

then in my map file you will see an empty input section called .text.foo. This doesn't tell you anything about the symbols I'm linking.

How can I tell the toolchain to only link once each function?

The linker will not link any symbol definition more than once, except in the special case of weak symbols. Your map file contains no evidence of any function being linked more than once.

How can I reduce the final file size?

Compile with -Os for your release, of course. And to minimize linkage redundancy, see this question.

Reading a linker map file is usually a clunky way of investigating the symbols and sections in your binaries. Prefer objdump, readelf and nm

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182