When dealing with embedded software, I often find useful to have the standard C functions we all learn during our first programming course: printf
, malloc
, getchar
, strncpy
, …
A common way to have them is using Newlib. Newlib is an implementation of the standard C library that is specifically thought to run somewhere with low resources and undefined hardware. The idea of Newlib is to implement the hardware-independent parts of the standard C library and rely on few low-level system calls that must be implemented with the target hardware in mind.
I compiled Newlib with the CodeSourcery ARM compiler for bare-metal targets: Sourcery G++ Lite 2010.09-51. Once the toolchain is installed, the commands I used to download compile Newlib are:
$ wget ftp://sources.redhat.com/pub/newlib/newlib-1.18.0.tar.gz $ tar xzf newlib-1.18.0.tar.gz $ cd newlib-1.18.0/ $ ./configure --target arm-none-eabi --disable-newlib-supplied-syscalls $ make $ cd ..
The “--disable-newlib-supplied-syscalls
” option is necessary because otherwise Newlib compiles some pre-defined libraries for ARM that are useful in conjunction with debug features such as the RDI monitor. Many different outputs are compiled inside the arm-none-eabi
subdirectory, but in particular the “libc.a
” archive in the newlib
directory is the one I use.
As an example, I want to compile the following “test.c
” program:
#include #include <stdlib.h> extern char *heap_end; /* Defined in syscalls.c */ void c_entry() { char c; char *ptr = NULL; size_t alloc_size = 1; do { c =getchar(); printf("%d: %c\n", c, c); ptr = realloc(ptr, alloc_size); if(ptr == NULL) { puts("Out of memory!\nProgram halting."); for(;;); } else { printf("new alloc of %d bytes at address 0x%X\n", alloc_size, (unsigned int)ptr); alloc_size <<= 1; printf("Heap end = 0x%X\n", (unsigned int)heap_end); } } while (1); }
The program uses standard input/output functions and allocates more and more memory with realloc
, printing some information to understand what’s going on. The idea is to use the UART serial port as the input/output, and monitor how the memory is managed. The heap_end
variable is something that I will implement and use later to manage the memory allocation.
The program needs at least some basic starting code “startup.s
” that I borrow from a previous example of mine:
LDR sp, =stack_top BL c_entry B .
And the linker script “test.ld
” again is a modified version from this post:
SECTIONS { . = 0x10000; .ro : { startup.o (.text) *(.text) *(.rodata) } .rw : { *(.data) *(.bss) *(COMMON) } . = ALIGN(8); heap_low = .; /* for _sbrk */ . = . + 0x10000; /* 64kB of heap memory */ heap_top = .; /* for _sbrk */ . = . + 0x10000; /* 64kB of stack memory */ stack_top = .; /* for startup.s */ }
One thing that the program needs is the libgcc
library: it provides some basic hardware functionalities, such as the division, that are used by the compiler. If the linking phase complains about some undefined reference to “__aeabi_...
” symbol, it probably means that there’s some problem regarding the libgcc
library. The CodeSourcery toolchain version 2010.09-51 comes with its own version of this library inside the folder “~/CodeSourcery/Sourcery_G++_Lite/lib/gcc/arm-none-eabi/4.5.1/libgcc.a
“; the folder may slightly change depending on the toolchain version and installation path.
If I try to compile the program with the Newlib, this is the result:
$ arm-none-eabi-gcc -mcpu=cortex-a8 -I ./newlib-1.18.0/newlib/libc/include -c -o test.o test.c $ arm-none-eabi-as -mcpu=cortex-a8 -o startup.o startup.s $ arm-none-eabi-gcc -nostdlib -T test.ld test.o startup.o ./newlib-1.18.0/arm-none-eabi/newlib/libc.a /home/francesco/CodeSourcery/Sourcery_G++_Lite/lib/gcc/arm-none-eabi/4.5.1/libgcc.a -o test test.o: In function `c_entry': test.c:(.text+0x10c): undefined reference to `heap_end' test.c:(.text+0x110): undefined reference to `heap_end' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-sbrkr.o): In function `_sbrk_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/sbrkr.c:60: undefined reference to `_sbrk' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-writer.o): In function `_write_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/writer.c:58: undefined reference to `_write' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-closer.o): In function `_close_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/closer.c:53: undefined reference to `_close' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-fstatr.o): In function `_fstat_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/fstatr.c:62: undefined reference to `_fstat' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-isattyr.o): In function `_isatty_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/isattyr.c:58: undefined reference to `_isatty' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-lseekr.o): In function `_lseek_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/lseekr.c:58: undefined reference to `_lseek' ./newlib-1.18.0/arm-none-eabi/newlib/libc.a(lib_a-readr.o): In function `_read_r': /home/francesco/src/arm/newlib/newlib-1.18.0/arm-none-eabi/newlib/libc/reent/../../../.././newlib/libc/reent/readr.c:58: undefined reference to `_read'
Ignoring heap_end
for now, the linker complains that some functions cannot be found. These functions are the system calls that must be implemented depending on the hardware. One example of a minimal implementation of these functions is here.
The “hardware” where I will make the program run is the QEMU emulation of the Versatile Platform Baseboard Cortex-A8 platform. According to the RealView® Platform Baseboard for Cortex™-A8 User Guide, there is a memory-mapped UART serial port at address 0x10009000
, and my intention is to write output and read input using this UART.
The _sbrk
function is used to increase the memory allocated by the program; modifying slightly the minimal implementation I use the memory space between the heap_low
and the heap_top
symbols that I defined inside the linker script. This is my implementation of “syscalls.c
“:
#include <sys/stat.h> enum { UART_FR_RXFE = 0x10, UART_FR_TXFF = 0x20, UART0_ADDR = 0x10009000, }; #define UART_DR(baseaddr) (*(unsigned int *)(baseaddr)) #define UART_FR(baseaddr) (*(((unsigned int *)(baseaddr))+6)) int _close(int file) { return -1; } int _fstat(int file, struct stat *st) { st->st_mode = S_IFCHR; return 0; } int _isatty(int file) { return 1; } int _lseek(int file, int ptr, int dir) { return 0; } int _open(const char *name, int flags, int mode) { return -1; } int _read(int file, char *ptr, int len) { int todo; if(len == 0) return 0; while(UART_FR(UART0_ADDR) & UART_FR_RXFE); *ptr++ = UART_DR(UART0_ADDR); for(todo = 1; todo < len; todo++) { if(UART_FR(UART0_ADDR) & UART_FR_RXFE) { break; } *ptr++ = UART_DR(UART0_ADDR); } return todo; } char *heap_end = 0; caddr_t _sbrk(int incr) { extern char heap_low; /* Defined by the linker */ extern char heap_top; /* Defined by the linker */ char *prev_heap_end; if (heap_end == 0) { heap_end = &heap_low; } prev_heap_end = heap_end; if (heap_end + incr > &heap_top) { /* Heap and stack collision */ return (caddr_t)0; } heap_end += incr; return (caddr_t) prev_heap_end; } int _write(int file, char *ptr, int len) { int todo; for (todo = 0; todo < len; todo++) { UART_DR(UART0_ADDR) = *ptr++; } return len; }
The compilation and emulation is done with the following commands; QEMU will open a graphic window but the terminal will accept key strokes and pass it to the emulated UART:
$ arm-none-eabi-gcc -mcpu=cortex-a8 -I ./newlib-1.18.0/newlib/libc/include -c -o test.o test.c $ arm-none-eabi-as -mcpu=cortex-a8 -o startup.o startup.s $ arm-none-eabi-gcc -mcpu=cortex-a8 -I ./newlib-1.18.0/newlib/libc/include -c -o syscalls.o syscalls.c $ arm-none-eabi-gcc -nostdlib -T test.ld test.o startup.o syscalls.o ./newlib-1.18.0/arm-none-eabi/newlib/libc.a ~/CodeSourcery/Sourcery_G++_Lite/lib/gcc/arm-none-eabi/4.5.1/libgcc.a -o test $ arm-none-eabi-objcopy -O binary test test.bin $ qemu-system-arm -M realview-pb-a8 -serial stdio -kernel test.bin 97: a new alloc of 1 bytes at address 0x1D280 Heap end = 0x1E000 98: b new alloc of 2 bytes at address 0x1D280 Heap end = 0x1E000 99: c new alloc of 4 bytes at address 0x1D280 Heap end = 0x1E000 100: d new alloc of 8 bytes at address 0x1D280 Heap end = 0x1E000 97: a new alloc of 16 bytes at address 0x1D280 Heap end = 0x1E000 98: b new alloc of 32 bytes at address 0x1D280 Heap end = 0x1E000 99: c new alloc of 64 bytes at address 0x1D280 Heap end = 0x1E000 100: d new alloc of 128 bytes at address 0x1D280 Heap end = 0x1E000 97: a new alloc of 256 bytes at address 0x1D280 Heap end = 0x1E000 98: b new alloc of 512 bytes at address 0x1D280 Heap end = 0x1E000 99: c new alloc of 1024 bytes at address 0x1D280 Heap end = 0x1E000 100: d new alloc of 2048 bytes at address 0x1D280 Heap end = 0x1E000 97: a new alloc of 4096 bytes at address 0x1D280 Heap end = 0x20000 98: b new alloc of 8192 bytes at address 0x1D280 Heap end = 0x20000 99: c new alloc of 16384 bytes at address 0x1D280 Heap end = 0x25000 100: d Out of memory! Program halting.
Each keystroke the programs tries to allocate more and more memory until the heap is consumed.
It’s worth noting that the resulting binary’s size, that includes some relevant standard C functions, is about 52KB.
One big part that is missing from the standard C library is the file I/O functions, but in order to work it needs a filesystem implementation and some storage. One small improvement of my example could be the opening of the other serial ports using the open function and some fixed names such as UART0, UART1 and UART2 to associate a file descriptor with a hardware peripheral, and use it accordingly.
See also:
Vignesh
2012/04/22
Hi,
Thanks for your wonderful tutorials . These provide me a start up platform for learning ARM programming . I want to understand more on the Linker file and startup concepts . Is there any good tutorials for further readings
Thanks in advance
Balau
2012/04/22
A good and complete tutorial is here: Building bare metal ARM with GNU
If you want a reference for the linker scripts, I need nothing more than GNU ld manual: Using ld
Bala
2012/05/06
Hi ,
Thanks for the tutorial .I understand that “-I ./newlib-1.18.0/newlib/libc/include” option is used to point the header files included in the file . But to my surprise I could compile the code without this option and still get the result . I want to know how compiler manages this
Balau
2012/05/06
The compiler probably includes the Standard C Library headers in “
/usr/include/
“, which are similar to the ones innewlib
. In my case the compiler doesn’t find the “sys/stat.h
” file, but there’s a similar file in “/usr/include/linux/stat.h
“.tigerjibo
2012/07/25
Thanks for the tutorial . I use the printf(the test float value%f\n,9.8) , test printf function float put.but the prommer run the result is random value; but I dont konw why? whether the newlib library printf function don’t support the float printf.
Balau
2012/07/26
If I add printf(“float: %f\n”, 9.8) to my example it works correctly. What have you done differently? for example:
– changing toolchain
– compiling a different version of newlib that doesn’t have float enabled by default
– reduced stack size
tigerjibo
2012/07/27
Thanks for your reply. I have find the bug. but I dont’t konw whther my project error or the newlib erro. I have already send my project to you by email. Please check in. Thanks for your help!.
Ryan
2012/08/10
Hi,
In your tutorial, I see that you implement _read and _write function to put characters to UART port. But it does not work in my case. In order to use printf through UART, I have to implement _read_r and _write_r. Do you know how are they different? Beside, with that implementation, printf does not print anything without ‘\n’ or using fflush(stdout). Could you explain it?
Thank you,
Ryan
Balau
2012/08/11
The difference between _read and _read_r is that _read_r is “reentrant“, which means that it can be interrupted and called again by the interruption and it will behave correctly.
See also this article: http://www.eetimes.com/discussion/guest-editor/4023922/Embedding-GNU-Newlib-Part-2
I don’t know why you must implement the reentrant functions instead of the normal ones, maybe it’s because during compilation the reentrant versions of the system calls are not implemented. It could be a matter of configuration before compilation or there’s no support for the target architecture.
About the printf, the behavior is normal because the standard output should be buffered. If you would write the same program and compile it with gcc in a Linux environment, you will see that the printf does not print anything until a newline or a flush (which also happens when the program is closed). See here about how to change this behavior: Controlling Buffering
minghuasweblog
2013/05/27
Reblogged this on minghuasweblog and commented:
Really great and very detailed series of artistic work on ARM and emulation.
Karibe
2014/02/18
I am trying to use the standard stdlib functions from arm-none-eabi- installation in MKL05Z project, for rand() , just getting a random number, I get the error
sbrkr.c:(.text._sbrk_r+0xc): undefined reference to `_sbrk’
collect2: error: ld returned 1 exit status
Do you think I should use newlib to help in this situation, or what alternatives do i have for pseudo random number. I have read about using diconnected Analog inputs as random number generators, but that sounds a little far fetched. Do you think there is an easy way to fix this?
Balau
2014/02/18
If you just need a pseudo random number, a very simple solution is to implement the function in this page: Game Programming: Random Number Generation.
Your error is probably caused by the fact that
rand
somehow callsmalloc
. Be aware that linking the “syscalls.c
” file that I show above should take care of this error.seb
2014/04/02
Hi Balau, hi everyone !
i tried to use newlib, but when i use this command “./configure –target arm-none-eabi –disable-newlib-supplied-syscalls”, cygwin gives me this error that i don’t understand :
sebastien_begue@pc-bureau /cygdrive/c/Documents and Settings/sebastien_begue/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_EABI
$ cd newlib-1.18.0/
sebastien_begue@pc-bureau /cygdrive/c/Documents and Settings/sebastien_begue/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_EABI/newlib-1.18.0
$ dir
ChangeLog COPYING.LIB ltgcc.m4 mkdep
compile COPYING.LIBGLOSS ltmain.sh mkinstalldirs
config COPYING.NEWLIB ltoptions.m4 move-if-change
config.guess COPYING3 ltsugar.m4 newlib
config.log COPYING3.LIB ltversion.m4 README
config.rpath depcomp MAINTAINERS README-maintainer-mode
config.sub etc Makefile.def setup.com
config-ml.in install-sh Makefile.in src-release
configure libgloss Makefile.tpl symlink-tree
configure.ac libtool.m4 makefile.vms texinfo
COPYING lt~obsolete.m4 missing ylwrap
sebastien_begue@pc-bureau /cygdrive/c/Documents and Settings/sebastien_begue/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_EABI/newlib-1.18.0
$ ./configure –target arm-none-eabi –disable-newlib-supplied-syscalls
checking build system type… x86_64-unknown-cygwin
checking host system type… x86_64-unknown-cygwin
checking target system type… arm-none-eabi
checking for a BSD-compatible install… /usr/bin/install -c
checking whether ln works… yes
checking whether ln -s works… yes
checking for a sed that does not truncate output… /usr/bin/sed
checking for gawk… gawk
checking to see if cat works as expected… yes
checking for gcc… gcc
checking for C compiler default output file name…
configure: error: in
/cygdrive/c/Documents and Settings/sebastien_begue/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_EABI/newlib-1.18.0':
config.log’ for more details.configure: error: C compiler cannot create executables
See
Could you help me please ?
Regards,
Sébastien.
seb
2014/04/02
hi !
i succeed this last step, but now when i use the command “make”, cygwin gives me this error :
./standards.texi:3963: @include : impossible de trouver make-stds.texi
./standards.texi:4153: @include : impossible de trouver fdl.texi
./standards.texi:3767: @menu pointe vers un nœud « Makefile Conventions » inexistant
Do you know how correct it please ? because the files are in the good folder and i don’t know how standards.texi doesn’t find it !
thanks.
Sébastien.
Balau
2014/04/02
I never tried to compile newlib for Cygwin.
texi seems to be about documentation so it should not be necessary.
I don’t know how you solved the first problem but you can try to “make distclean” from newlib root directory and redo the
./configure
from a clean state to see if something changes.Maybe your best bet is to ask newlib mailing list about it.
Balaji
2014/04/04
Hi Balau,
With objcopy command, iam getting large size .bin than .elf. I am not getting what is the problem. Actually I need to write the boot code executing from ROM memory. Please check an attached linker file for info. I need to place the .data and .bss sections into the SRAM memory and .text ,.rodata sections into ROM. I followed the following link
http://www.bravegnu.org/gnu-eprog/c-startup.html
But the no of object files are more.
Please suggest me if you know.
Thanks
balaji
Balaji
2014/04/04
Hi Balau
Please check an attached .lds file.
MEMORY { .sram : ORIGIN = 0x40200800, LENGTH = (54 * 1024) }
MEMORY { .sdram : ORIGIN = 0x80000000, LENGTH = 0x80000 }
OUTPUT_FORMAT(“elf32-littlearm”, “elf32-littlearm”, “elf32-littlearm”)
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.text :
{
__start = .;
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
} >.sram
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
. = ALIGN(4);
.data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sdram
. = ALIGN(4);
__image_copy_end = .;
_end = .;
.bss :
{
. = ALIGN(4);
__bss_start = .;
*(.bss*)
. = ALIGN(4);
__bss_end = .;
} >.sdram
}
Thanks
balaji
Balau
2014/04/04
To troubleshoot this kind of problems I usually add
-Xlinker -Map=program.map
to the gcc linking phase, so that you can check the map for load addresses (LMA) and execution addresses (VMA).You can try to use
AT>.sram >.sdram
for.data
section.Also,
.bss
probably needs a(NOLOAD)
because you don’t need to put all those zeros in thebin
.For the meaning of these options refer to
ld
documentation.seb
2014/04/05
hi Balau !
just to say you thank you for your answer, because it brought me to the solution ! (in fact .texi are just text, and i modified them a little bit to make them adapted, even if we can just delete them without problems).
regards,
sebastien.
Balaji
2015/10/27
Hi Balau,
I just noticed this: Irrespective of what size we give when calling realloc(), sbrk is always being called with a minimum of 4096 byte increment and 4096 bytes is allocated in the heap. Is this because of byte-alignment? Is there a way to allocate any size less than this value?
Balau
2015/10/28
I don’t know, it depends on malloc/realloc implementation in newlib. It’s not about byte alignment because it seems too big of an alignment. You could use a debugger to understand what’s done inside the realloc, or you could ask newlib developers in their mailing list.
Andy
2017/01/12
Hi Balau,
You made a very interesting knowledge sharing blog.
I had read through couple of your blogs for embedded software developing, especially on STM32. Over years, I had tried couple of free solutions, on which using Eclipse CDT as the base platform and plug-ins with GNU-ARM-GCC and OpenOCD for the bare metal ARM programming. Among them, I’m be little more prefer the Open4STM32 than others, as it provides the plugins can be installed across the OSs of Windows, Linux and MacOS; and also the third party components like OpenOCD and gcc-arm-none-eabi-xx will be automatically installed or updated while install or update the plug-ins.
I did tried couple of experimental projects on couple of STM32 Discovery board, with FreeRTOS and ST HAL lib integrated. They all runs well, but I feel that the support for standard Input/Output and Error has more room to improve. I want to implement a standard IO can be switching dynamically between semihosting,ITM, UARTx etc., at run-time. I had searched over the INTERNET and read through couple of your post, find that you had expressed a similar idea. I’m wondering do you ever pursue the real coding work? If not yet, I want to give a try, and it could be contribute to open source a little bit :).
By searching around and followed the instructions, I had successfully printf through semihosting debug channel, UART1 or 2 etc. on couple of STM32Fx Discovery board respectively. The real work is integrate all those together. The most challenging part is to integrate semihosting properly with others. As I learned from many reports, once the semihosting is enabled and/or initialized, it has to have the debugger connected and setup the semihosting channel properly, or the target MCU will hanging. I had heard someone talking about can get rid of this issue if has a careful NMI handler implemented, but I can’t find the detail.
By the design, I like to provide a minimal subset of syscall, including _open(…) , _close(…) , _read(…) , _write(…) ; which will allow the application code to use “freopen(“/dev/null”,”w”,stdout);” ,“freopen(“/dev/semihosting”,”w”,stdout);”, “freopen(“/dev/ttyS0″,”w”,stdout);”, “freopen(“/dev/ttyS0″,”w”,stdout);” , “freopen(“/dev/ttyS3″,”w”,stdout);”, “freopen(“/dev/itm”,”w”,stdout);” etc. to redirect the standard output. The similar way to redirect stdin and stderr . Where the “/dev/ttySx” will be UARTx in the embedded system.
Do you think this is a viable plan? Any suggestion? I like to hear your opinion.
Thanks,
Andy
Andy
2017/01/17
Hello here,
I had done a quick hack based on the syscalls.c from AC6 and liviu Ionescu’s work. As I said, it is a quick hack, but works well. In the application code, invoke
freopen(“/dev/null”,”w”,stdout);
freopen(“/dev/semihosting”,”w”,stdout);
freopen(“/dev/ttyS2″,”w”,stdout);
freopen(“/dev/ttyS3″,”w”,stdout);
to redirect the standard Output to null, semihosting , UART2 , UART3 etc.
Null means to drop all of the data to standard Output.
I like to upload the code for sharing, but don’t know how.
If anyone is interested, let me know where and how I can sharing the work.
-Andy
Andy
2017/01/17
Ok. I uploaded the zip file for the source code there.
http://www.openstm32.org/tiki-view_forum_thread.php?comments_parentId=4210
Balau
2017/02/04
thanks for the sharing! sorry for not responding but I have health problems in my arms and hands, and I am keeping computer activity to a minimum
cirosantilli
2018/06/26
Here is a fully automated version of this that builds GCC from source with crosstool-ng, thus dispensing the CodeSourcery blobs: https://github.com/cirosantilli/newlib-examples/tree/5f4b9a5ab01bde5b54901e5342ec9e676fd226f9
cirosantilli
2018/06/27
I’ve also found out that if you configure your toolchain properly, which crosstool-ng does for us trivially, you don’t need to pass all those ugly includes -I and .a libs explicitly to gcc: https://github.com/cirosantilli/newlib-examples/blob/dcf94e74db5bfd68a6cb21e6a91b126dfc7a0154/build-sample#L19