We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
Hi,
I have two M0 projects. The first project has only one assembly file startup.s. The second project has one assembly file and one C file. When I checked the memory map file of the second project (shown below), there are some extra object codes included in the final binary in addition to startup.o and a.o. I presume they are related to C libararies. Can someone describe what they are for? Are these libaries required? If not required do we have a way to exclude them?
Thanks a lot.
--- Memory map of project 1: startup.s ----
Memory Map of the image
Image Entry point : 0x00000041
Load Region LR_1 (Base: 0x00000000, Size: 0x0000015c, Max: 0xffffffff, ABSOLUTE)
Execution Region ER_RO (Base: 0x00000000, Size: 0x0000015c, Max: 0xffffffff, ABSOLUTE)
Base Addr Size Type Attr Idx E Section Name Object
0x00000000 0x00000040 Data RO 3 RESET startup.o 0x00000040 0x0000011c Code RO 4 * .text startup.o
--- Memory map of project 2: startup.s & a.c ---
Image Entry point : 0x0000020d
Load Region LR_1 (Base: 0x00000000, Size: 0x0000027c, Max: 0xffffffff, ABSOLUTE)
Execution Region ER_RO (Base: 0x00000000, Size: 0x00000274, Max: 0xffffffff, ABSOLUTE)
0x00000000 0x0000004c Data RO 20 RESET startup.o 0x0000004c 0x00000008 Code RO 30 * !!!main c_p.l(__main.o) 0x00000054 0x0000003c Code RO 195 !!!scatter c_p.l(__scatter.o) 0x00000090 0x0000001a Code RO 197 !!handler_copy c_p.l(__scatter_copy.o) 0x000000aa 0x00000002 PAD 0x000000ac 0x0000001c Code RO 199 !!handler_zi c_p.l(__scatter_zi.o) 0x000000c8 0x00000006 Code RO 68 .ARM.Collect$$libinit$$00000000 c_p.l(libinit.o) 0x000000ce 0x00000000 Code RO 77 .ARM.Collect$$libinit$$00000006 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 79 .ARM.Collect$$libinit$$00000008 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 81 .ARM.Collect$$libinit$$0000000A c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 84 .ARM.Collect$$libinit$$0000000D c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 86 .ARM.Collect$$libinit$$0000000F c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 88 .ARM.Collect$$libinit$$00000011 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 90 .ARM.Collect$$libinit$$00000013 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 92 .ARM.Collect$$libinit$$00000015 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 94 .ARM.Collect$$libinit$$00000017 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 96 .ARM.Collect$$libinit$$00000019 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 98 .ARM.Collect$$libinit$$0000001B c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 100 .ARM.Collect$$libinit$$0000001D c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 102 .ARM.Collect$$libinit$$0000001F c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 104 .ARM.Collect$$libinit$$00000021 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 108 .ARM.Collect$$libinit$$00000028 c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 110 .ARM.Collect$$libinit$$0000002A c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 112 .ARM.Collect$$libinit$$0000002C c_p.l(libinit2.o) 0x000000ce 0x00000000 Code RO 114 .ARM.Collect$$libinit$$0000002E c_p.l(libinit2.o) 0x000000ce 0x00000002 Code RO 115 .ARM.Collect$$libinit$$0000002F c_p.l(libinit2.o) 0x000000d0 0x00000002 Code RO 136 .ARM.Collect$$libshutdown$$00000000 c_p.l(libshutdown.o) 0x000000d2 0x00000000 Code RO 150 .ARM.Collect$$libshutdown$$00000003 c_p.l(libshutdown2.o) 0x000000d2 0x00000000 Code RO 153 .ARM.Collect$$libshutdown$$00000006 c_p.l(libshutdown2.o) 0x000000d2 0x00000000 Code RO 156 .ARM.Collect$$libshutdown$$00000009 c_p.l(libshutdown2.o) 0x000000d2 0x00000000 Code RO 158 .ARM.Collect$$libshutdown$$0000000B c_p.l(libshutdown2.o) 0x000000d2 0x00000000 Code RO 161 .ARM.Collect$$libshutdown$$0000000E c_p.l(libshutdown2.o) 0x000000d2 0x00000002 Code RO 162 .ARM.Collect$$libshutdown$$0000000F c_p.l(libshutdown2.o) 0x000000d4 0x00000000 Code RO 32 .ARM.Collect$$rtentry$$00000000 c_p.l(rtentry.o) 0x000000d4 0x00000000 Code RO 35 .ARM.Collect$$rtentry$$00000002 c_p.l(rtentry2.o) 0x000000d4 0x00000004 Code RO 52 .ARM.Collect$$rtentry$$00000005 c_p.l(rtentry5.o) 0x000000d8 0x00000000 Code RO 37 .ARM.Collect$$rtentry$$00000009 c_p.l(rtentry2.o) 0x000000d8 0x00000004 Code RO 38 .ARM.Collect$$rtentry$$0000000A c_p.l(rtentry2.o) 0x000000dc 0x00000000 Code RO 40 .ARM.Collect$$rtentry$$0000000C c_p.l(rtentry2.o) 0x000000dc 0x00000008 Code RO 41 .ARM.Collect$$rtentry$$0000000D c_p.l(rtentry2.o) 0x000000e4 0x00000004 Code RO 53 .ARM.Collect$$rtentry$$00002716 c_p.l(rtentry5.o) 0x000000e8 0x00000002 Code RO 73 .ARM.Collect$$rtexit$$00000000 c_p.l(rtexit.o) 0x000000ea 0x00000000 Code RO 119 .ARM.Collect$$rtexit$$00000002 c_p.l(rtexit2.o) 0x000000ea 0x00000004 Code RO 120 .ARM.Collect$$rtexit$$00000003 c_p.l(rtexit2.o) 0x000000ee 0x00000006 Code RO 121 .ARM.Collect$$rtexit$$00000004 c_p.l(rtexit2.o) 0x000000f4 0x00000118 Code RO 1 .text a.o 0x0000020c 0x00000024 Code RO 21 * .text startup.o 0x00000230 0x00000006 Code RO 28 .text c_p.l(heapauxi.o) 0x00000236 0x0000000c Code RO 60 .text c_p.l(exit.o) 0x00000242 0x00000002 PAD 0x00000244 0x0000000c Code RO 128 .text c_p.l(sys_exit.o) 0x00000250 0x00000002 Code RO 139 .text c_p.l(use_no_semi.o) 0x00000252 0x00000000 Code RO 141 .text c_p.l(indicate_semi.o) 0x00000252 0x00000002 PAD 0x00000254 0x00000020 Data RO 193 Region$$Table anon$$obj.o
I always avoid things like "printf". printf uses fprintf and snprintf. fprintf needs fwrite, and fwrite means including all file I/O, including fopen, fread, fclose, fseek, etc. Usually it means I get a huge library, so instead, I either roll my own low-level routine that just sends a character over the UART for instance, and then another routine, which takes a string and send the string's characters one-by-one using the other routine.
Looking at the above, I see some exit and libshutdown references; that's completely redundant, because you never really exit your program on a microcontroller anyway - you would usually stay in a 'forever' while-loop. (Remember to put a __WFI(); in the loop, to save power, if you can).
The standard C library probably also wants malloc and free, two things that are not appropriate for most Cortex-M0 (unless they're combined with a Cortex-M4 for instance, like the NXP LPC43xx or LPC541xx; these have plenty of space).
So what I'd do in your place, would be to take a look at newlib nano. It's a smaller version of the standard C library, which is much more useful on smaller devices.
But I'd also try to avoid using functions that need the C-library for as long as possible.
-But if you at some point already need the standard library, then using it will not cost much extra. If you already must have printf, just in one place, keep using it, because you might be able to save space, when compared to rolling your own.
There are other library functions, which takes up a lot of space. For instance if you're using math multiplication or division. Mulitplication isn't really that bad, but dividing a 32-bit value by a 32-bit value producing a remainder and a quotient will generate a medium sized subroutine.
If you attempt to use floating point, then you'll get huge and very slow code. Avoid floating point, at almost all cost.
Normally, you'll need only integer math, it's also often much more precise (32-bit floating point only has 24-bit precision for instance).
jensbauer wrote: I always avoid things like "printf". printf uses fprintf and snprintf. fprintf needs fwrite, and fwrite means including all file I/O, including fopen, fread, fclose, fseek, etc. Usually it means I get a huge library, so instead, I either roll my own low-level routine that just sends a character over the UART for instance, and then another routine, which takes a string and send the string's characters one-by-one using the other routine.
jensbauer wrote:
Does using ARM Microlib help in reducing the size?
I do not know the ARM Microlib, but from the sound of the name, I would expect so.
You can try building with the normal libraries and then switch to ARM Microlib and see what the difference is.
Making a simple printf program will probably show a significant difference, but malloc alone is probably also a good library function to test with.
There is something here. Will try it out sometime.
Thanks,
Hi wshen,
I think the 2nd case includes another startup file which had been provided by the compiler. Are the procedure of compiling for both projects same? If you use GCC, how about adding the '-nostdlib' option to the command line?
Best regards,Yasuhiko Koumoto.
It turns out that main() function will include these C library initialization functions to be included as part of __rt_lib_init automatically. So if your program doesn't have main() function, such C functions won't be included.
__rt_lib_init
Well, would be good to know more about your sources, compiler and settings. Overall, your binary isn't very large, so the library calls aren't consuming much memory. When using GCC (you didn't say which compiler you're using) you typically have the memory initialisation at the beginning, in startup.s like below. This called from your reset handler and subsequently jumps to your main code.
Using a regular printf would be very heavy, as others have mentioned. From your code size ( Size: 0x0000027c, ) I doubt you're using it. I'm not familiar with the ARM micro library (but with newlib) and there are useful free small printf implementations around.
000004f0 <Default_Reset_Handler>:
{
/* Initialize data and bss */
unsigned long *pulDest;
/* Zero fill the bss segment. */
for(pulDest = &_sbss; pulDest < &_ebss; )
4f0: 4809 ldr r0, [pc, #36] ; (518 <Default_Reset_Handler+0x28>)
4f2: 4b0a ldr r3, [pc, #40] ; (51c <Default_Reset_Handler+0x2c>)
* @param None
* @retval None
*/
// must be optimized to speed up clearing bss
__attribute__ ((no_instrument_function)) __attribute__((optimize("O2"))) void Default_Reset_Handler(void)
4f4: b510 push {r4, lr}
4f6: 4298 cmp r0, r3
4f8: d20b bcs.n 512 <Default_Reset_Handler+0x22>
4fa: 43c1 mvns r1, r0
4fc: 2200 movs r2, #0
*(pulDest++) = 0;
4fe: 2400 movs r4, #0
500: 18cb adds r3, r1, r3
502: 089b lsrs r3, r3, #2
504: 3301 adds r3, #1
506: 009b lsls r3, r3, #2
508: 1881 adds r1, r0, r2
50a: 3204 adds r2, #4
50c: 600c str r4, [r1, #0]
50e: 429a cmp r2, r3
510: d1fa bne.n 508 <Default_Reset_Handler+0x18>
/* Setup the microcontroller system. */
//SystemInit();
/* Call the application's entry point.*/
main();
512: f7ff fe1b bl 14c <main>
// main shouldn't return
while (1);
516: e7fe b.n 516 <Default_Reset_Handler+0x26>
518: 00008e58 .word 0x00008e58
51c: 0000a374 .word 0x0000a374
In fact, it's not the job of the 'main()' function, but the job of the RESET_Handler to initialize the C-libraries.
Such initialization code is normally put in crt.c or startup.c (or crt.s or startup.s).
Some code from the C-libraries might be included, even when unused (eg. never called), when you use GCC.
On Cortex-M0, it's often preferred to omit such code, as it's huge compared to the size of the program space available.