This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
Hello Forum,
In better interest of the forum (and to some extent mine) has anyone tried to create a project in CCS with GNU C compiler and got it to work without running into issue like compilation and link switches (stack and heap) that need to be setup from scratch, malloc not working?
I do remember @Chester Gillon's posts and forum replies on the topic of GNU C sometimes in Code Composer Forum and on TM4C as well, so thought it would be a good start to see how we can streamline project creation in CCS IDE for GNU C
Regards
Amit
For reference, the thread Uninitialized variables using GCC contains my latest list of known problems with the CCS 6.1.x default linker script (.lds) and startup C files (*_startup_css_gcc.c) files when used with the gcc-arm-none-eabi-4_8-2014q3 (v4.8.4) compiler for Stellaris and Tiva C devices.Amit Ashara said:I do remember @Chester Gillon's posts and forum replies on the topic of GNU C sometimes in Code Composer Forum and on TM4C as well
I hadn't previously tried testing the use of malloc on Tiva C devices when using the gcc-arm-none-eabi-4_8-2014q3 (v4.8.4) compiler. Having tried that found that malloc() was always returning NULL.Amit Ashara said:malloc not working?
When you say "malloc not working" do you mean malloc() returning NULL, or some other problem?
When CCS 6.1.2 was used to create a program for the Cortex-A9 in an AM4378 using the gcc-arm-none-eabi-4_8-2014q3 (v4.8.4) compiler an initial test of malloc() worked, in that malloc() returned a non-NULL buffer. Will investigate why malloc() worked for a Cortex-A9 program, but failed for a Tiva C Cortex-M4F program.
The newlib in the v4.8.4 compiler runtime library uses the the following function to grow the heap as required when malloc is called:Amit Ashara said:2. The second was malloc returns a NULL value. This is something I am not able to trace yet for the cortex M4. One possible reason could be that the heap is not getting allocated.
caddr_t _sbrk (int incr) { extern char end asm ("end"); /* Defined by the linker. */ static char * heap_end; char * prev_heap_end; if (heap_end == NULL) heap_end = & end; prev_heap_end = heap_end; if (heap_end + incr > stack_ptr) { /* Some of the libstdc++-v3 tests rely upon detecting out of memory errors, so do not abort here. */ #if 0 extern void abort (void); _write (1, "_sbrk: Heap and stack collision\n", 32); abort (); #else errno = ENOMEM; return (caddr_t) -1; #endif } heap_end += incr; return (caddr_t) prev_heap_end; }
Note that as of CCS 6.1.2 the v4.8.4 installation doesn't contain the source code for the newlib runtime library, so the source code was downloaded from gcc-arm-none-eabi-4_8-2014q3-20140805-src.tar.bz2.
The implementation of _sbrk assumes the following memory layout for the heap and stack:
end /* Base address of heap */ | | /* Heap expands at increasing addresses */ \_/ _ / \ | /* Stack expands at decreasing addresses */ | | /* Initial stack pointer */
i.e. _sbrk expects the heap and stack to be adjacent in memory, with the heap at a lower address than the stack. When a new CCS 6.1.2 project was created for a TM4C123GH6PM the combination of the tm4c123gh6pm_startup_ccs_gcc.c source file and tm4c123gh6pm.lds linker script was such that the stack was at a lower address than the heap, which caused the _sbrk function to think there was insufficient memory for the heap and malloc() to return NULL with errno set to ENOMEM.
The attached TM4C123_GNU_malloc.zip CCS 6.1.2 project for a TM4C123GH6PM uses the v4.8.4 compiler and has been configured to allow malloc() to work. For simplicity the program uses printf() report the results to the CCS CIO console, so the same source file could be run on non-Tiva devices.
The steps to configure the project in CCS 6.1.2 were:
1) Create a new project for a Tiva TM4C123GH6PM device, selecting the GNU v4.8.4 (Linaro) compiler and the "Empty Project (with main.c)" template.
2) Populate the main.c source file with the malloc() test code, which only uses ANSI C include files.
3) To allow the printf output to be displayed in the CCS CIO console, under CCS Project Properties -> CCS Build -> GNU Linker -> Libraries changed the "nosys" library to "rdimon".
[The "nosys" library doesn't support the GNU Semihosting required for stdio I/O to be redirected to the CCS CIO console, and with "nosys" the printf() doesn't appear on the CCS console]
4) In the tm4c123gh6pm.lds linker script add the following after the REGION_ALIAS() definitions to ensure CCS runs to main when starting a debug session:
ENTRY(ResetISR)
[This was an existing known issue with the default Tiva and Stellaris linker scripts in CCS]
5) To support the heap and stack memory layout to allow malloc() to work:
In the tm4c123gh6pm.lds linker script add references to the HEAPSIZE and STACKSIZE symbols to be able to set the size of the heap and stack respectively, and create a _StackTop symbol to define the initial stack pointer:
.heap : { __heap_start__ = .; end = __heap_start__; _end = end; __end = end; . = . + HEAPSIZE; KEEP(*(.heap)) __heap_end__ = .; __HeapLimit = __heap_end__; } > REGION_HEAP .stack : ALIGN(0x8) { _stack = .; __stack = .; . = . + STACKSIZE; __StackTop = . ; KEEP(*(.stack)) } > REGION_STACK
In the tm4c123gh6pm_startup_ccs_gcc.c source file replace the existing pui32Stack[] array with a reference to the top of the stack defined in the linker file:
extern char *__StackTop;
And in g_pfnVectors replace the reference to pui32Stack with __StackTop:
void (* const g_pfnVectors[])(void) = { (void (*)(void))&__StackTop, // The initial stack pointer
Under CCS Project Properties -> CCS Build -> GNU Linker -> Symbols add the following to set the size of the heap and stack:
HEAPSIZE=0x2000 STACKSIZE=0x800
When the program runs successfully it reports that it has allocated (and freed) 10 different size buffers using malloc:
This is a text string in buffer of size 256 malloced at 0x200009e0 This is a text string in buffer of size 80 malloced at 0x200009e0 This is a text string in buffer of size 128 malloced at 0x200009e0 This is a text string in buffer of size 512 malloced at 0x20000ef0 This is a text string in buffer of size 368 malloced at 0x20000ef0 This is a text string in buffer of size 1024 malloced at 0x20000ef0 This is a text string in buffer of size 100 malloced at 0x200009e0 This is a text string in buffer of size 512 malloced at 0x20000ef0 This is a text string in buffer of size 2048 malloced at 0x20000ef0 This is a text string in buffer of size 1024 malloced at 0x20000ef0
As an example of how the relative order of the stack and heap can cause malloc() to fail, if the tm4c123gh6pm.lds linker script is edited to place the .stack section before the .heap section then the program reports that all malloc() calls fail:
Malloc(256) failed errno=12 (Not enough space) Malloc(80) failed errno=12 (Not enough space) Malloc(128) failed errno=12 (Not enough space) Malloc(512) failed errno=12 (Not enough space) Malloc(368) failed errno=12 (Not enough space) Malloc(1024) failed errno=12 (Not enough space) Malloc(100) failed errno=12 (Not enough space) Malloc(512) failed errno=12 (Not enough space) Malloc(2048) failed errno=12 (Not enough space) Malloc(1024) failed errno=12 (Not enough space)
Attached is a variation of the previous project TM4C123_GNU_malloc_uartprintf.zip, in which the program has been changed to output the results on UART1 at 115200 baud, and links "nosys" rather than "rdimon".Chester Gillon said:For simplicity the program uses printf() report the results to the CCS CIO console, so the same source file could be run on non-Tiva devices.
This was used to test that malloc() would still work in a program which boots from flash without the CCS debugger connected. Also tested that could create a "small" bin file (40 Kb) which can be flashed using UniFlash into an EK-TM4C123GXL.
The test results output on UART1 are:
This is a text string in buffer of size 256 malloced at 0x20001078 This is a text string in buffer of size 80 malloced at 0x20001078 This is a text string in buffer of size 128 malloced at 0x20001078 This is a text string in buffer of size 512 malloced at 0x20001078 This is a text string in buffer of size 368 malloced at 0x20001078 This is a text string in buffer of size 1024 malloced at 0x20001078 This is a text string in buffer of size 100 malloced at 0x20001078 This is a text string in buffer of size 512 malloced at 0x20001078 This is a text string in buffer of size 2048 malloced at 0x20001078 This is a text string in buffer of size 1024 malloced at 0x20001078 Test complete
Compared to the previous test, all test buffers are now allocated at the same address. This is because printf() is no longer called, and previously printf() was mallocing a 512 byte buffer on the first call which "fragmented" the heap during the previous tests as the size of the test buffer allocated was increased.
I have found another problem with GNU v4.8.4 C++ code when creating a project for a TM4C in CCS 6.1.2, in that a global constructor was not called.Amit Ashara said:I do remember @Chester Gillon's posts and forum replies on the topic of GNU C sometimes in Code Composer Forum and on TM4C as well, so thought it would be a good start to see how we can streamline project creation in CCS IDE for GNU C
If CCS 6.1.2 is used to create a Cortex-A9 project for an AM4378 using the GNU v4.8.4 compiler, change to link "rdimon" rather than "nosys" and add the following as main.cpp the program produces the expected results on the Cortex-A9:
/* * main.cpp */ #include <stdio.h> class test_class { public: test_class(); unsigned int get_initialised (void) const; private: unsigned int initialised; }; test_class::test_class() { initialised = 0xfeedabba; } unsigned int test_class::get_initialised() const { return initialised; } test_class global_instance; int main(void) { test_class stack_instance; printf ("global_instance.initialised=0x%x\n", global_instance.get_initialised()); printf ("stack_instance.initialised=0x%x\n", stack_instance.get_initialised()); return 0; }
The correct results on the Cortex-A9 show the test_class::test_class() constructor was called for both a global instance and stack instance of the class:
global_instance.initialised=0xfeedabba stack_instance.initialised=0xfeedabba
Whereas if the same C++ code is run on a TM4C123GH6PM project also created in CCS 6.1.2 with the GNU v4.8.4 compiler, and applying the same project configuration changes as in the previous posts, then the program fails to produce the correct output because the constructor for the global_instance is not called:
global_instance.initialised=0x0 stack_instance.initialised=0xfeedabba
From a quick look think this is due to a difference in the start-up code used in the default CCS projects for the Cortex-A9 and TM4C devices.
The CCS Cortex-A9 default startup_ARMCA9.S initializes the Vector Base Address Register and then branches to the _start runtime library function. Where _start in the runtime library calls global constructors, performs other runtime environment initialization and finally calls main.
Whereas the CCS TM4C default startup tm4c123gh6pm_startup_ccs_gcc.c copies the data segment initializers from flash to SRAM, zeros the .bss segment, enables the floating-point unit and then calls main. i.e. the runtime environment initialization which calls the global constructors is not called.
Need to determine if the CCS TM4C default startup files NOT calling the _start runtime library function is an omission, or due to the v4.8.4 compiler runtime library _start function not being supported on Cortex-M4 devices.
[The CCS MSP432, i.e. another Cortex-M4F device, default startup file msp432_startup_ccs_gcc.c also omits to call the _start runtime library function]
The _start runtime library function in the v4.8.4 compiler for the Cortex-M4 devices performs the following actions:Chester Gillon said:Need to determine if the CCS TM4C default startup files NOT calling the _start runtime library function is an omission, or due to the v4.8.4 compiler runtime library _start function not being supported on Cortex-M4 devices.
a) Initializes the stack pointer to the value of the __stack symbol
b) Zeros the .bss segment, over the range given by the symbols __bss_start__ ... __bss_end__
c) Calls C++ global constructors.
d) Calls main(). If main returns, then _start continues with termination actions as follows.
e) Calls functions registered by atexit()
f) Calls C++ global destructors.
g) Enters an infinite loop in the _exit function.
The above list of actions is not necessarily complete, and is the standard run time library actions I tested / investigated.
_start doesn't perform the following:
- Copy the .data segment initializers from flash to RAM
- Enable the Cortex-M4F floating point unit.
Therefore, to allow _start to be used to perform the GCC run time library functions modified the linker script and *_startup_ccs_gcc.c files to:
1) Define the symbol __stack for the top of the stack in the linker script. Change the g_pfnVectors[] array to reference __stack instead of __StackTop to set the initial stack pointer.
2) Remove the code which zeroed the .bss segment from the ResetISR() function, since _start() zeros the .bss segment
3) Change ResetISR() to call _start() instead of main(). _start() will then call main().
4) Remove unused symbols from the linker script, and add comments to explain the use of the symbols which are defined.
The attached project TM4C123_GNU_global_constructors.zipcontains the changes to make use of _start in the GCC run time library, and can be run on a EK-TM4C123GXL launchpad. The project demonstrates the correct operation of:
1) C++ constructors called for global classes, classes allocated on the stack and classes allocated on the heap, with the constructor now called for global classes.
2) Use of C++ new operator tests dynamic memory allocation via malloc.
3) Global C++ destructor called once main() returns.
4) A function registered by atexit() being called once main() returns.
5) Use of hardware floating point.
6) A variable in the .bss segment being zeroed and a variable in the .data segment being initialized, before main is called. This is done by causing the program to run twice. The first time displays the values of the variables as initialized by the start-up code, changes the variable values and then executes a watchdog reset to run a second time and check that the variables are re-initialized by the start-up code.
[This was a sanity check to verify the initial values were left in memory from a previous test]
7) Generates a .bin file which can be programmed by UniFlash, and output results via UART0. This shows can run from flash as well as when downloaded by CCS.
A successful run produced the following output:
zero_initialised = 0 This is an initialised string in the data segment at address 0x20000004 global_instance at 0x2000101c initialised=0xfeedabba stack_instance at 0x20004258 initialised=0xfeedabba heap_instance at 0x20001a80 initialised=0xfeedabba 25.122999 * 25.122999 = 631.165100 Called destructor for 0x20001a80 Generating a watchdog reset to check bss / data section re-initialisation (reset cause = 0x2) zero_initialised = 0 This is an initialised string in the data segment at address 0x20000004 global_instance at 0x2000101c initialised=0xfeedabba stack_instance at 0x20004258 initialised=0xfeedabba heap_instance at 0x20001a80 initialised=0xfeedabba 25.122999 * 25.122999 = 631.165100 Called destructor for 0x20001a80 Called destructor for 0x20004258 Halting test after reset_cause = 0x8 Called destructor for 0x2000101c
Suggest consider getting the proposed modifications into the default startup files for new CCS projects.
Check if the linker script has ENTRY(ResetISR), since for the reasons described in Known issue when using CCS 6.1/Linaro GCC 4.8.4 that can lead to a hard-fault.Markus Rudolf said:@Chester Gillon: Do you have any idea what could be still wrong?
If the above doesn't work, you can P.M. me with the project.Markus Rudolf said:Can I P.M. you the project?
There is outstanding bug CCBT-2049 on the issue that the CCS debugger shows incomplete stack backtraces when the GCC ARM compiler is used. CCBT-2049 is targeted for being fixed in CCS 7.2.0.Markus Rudolf said:For getting a proper call stack trace I had to install a Eclipse Mars based IDE, as the CCS V7 is not able to provide a proper call stack trace with my SEGGER J-Link for unknown reasons. (The hardfault occurs in both, CCS and Eclipse, but in Eclipse I can at least single step to the instruction causing the issue)
CCS 6.2.0.00050 doesn't display complete stack backtrace when a Cortex-A15 SYS/BIOS program using the GNU compiler terminates due to an error or calling BIOS_exit() contains test cases which show the problem of incomplete stack backtraces, for Cortex-A or Cortex-M devices. The test cases were for SYS/BIOS programs, but hopefully the fix for CCBT-2049 will correct the stack backtrace for all types of programs.
Bob, just adding some information on the investigation I performed into why Markus's program was failing to start.Bob Crosby said:I will work on getting Chester's suggestions implemented. If I may, I might request some feedback on the implementation details as we work them out.
A hard fault occurred in the _init() function during the run-time startup. The _init() function in which was causing the hard-fault didn't appear to be valid function, since it contained hard-coded instructions which write to address zero which don't make any sense. The start of the _init() function is pulled in by the linker from C:/ti_ccs7_1_0/ccsv7/tools/compiler/gcc-arm-none-eabi-4_9-2015q3/lib/gcc/arm-none-eabi/4.9.3/armv7e-m/fpu/crti.o and disassembling that object file just shows function prologues:
Disassembly of section .init: 00000000 <_init>: 0: b5f8 push {r3, r4, r5, r6, r7, lr} 2: bf00 nop Disassembly of section .fini: 00000000 <_fini>: 0: b5f8 push {r3, r4, r5, r6, r7, lr} 2: bf00 nop
Comparing the contents of the tm4c1294ncpdt.lds against the CCS example in 66AK2Gxx.lds (for a Cortex-A15) showed differences in the handling of .ctors, .dtors, preinit data, init data and finit data. I think the problematic _init function is not coded as a single C function but the linker is supposed to concatenate:
- A function prologue coded in assembler
- One or more optional fragments of assembler
- A function epilogue coded in assembler
I merged the handling of .ctors, .dtors, preinit data, init data and finit data from the 66AKGxx.lds into the tm4c1294ncpdt.lds, and the _init function was then a valid empty function. That overcome the failure which Markus was suffering from, and allowed the program to reach main and run.
I haven't yet validated all the changes, but the CCS 66AKGxx.lds example seemed more complete than the tm4c1294ncpdt.lds example (which has already been shown to have omissions). I have been making piece-meal changes to the Tiva GCC linker script files, but to do a complete job need to understand all the possible sections the GCC ARM compiler can generate, and the GCC run time library expects. It would also be beneficial to create a test C (or C++?) example program to exercise the run time libraries which are supplied with the GCC ARM compiler to be able to check an updated GCC linker script files allows the run time libraries features to be used successfully.
E.g. I haven't yet tested support for handling of C++ exceptions.
The tm4c1294ncpdt.lds script with the most recent changes is attached. 2161.tm4c1294ncpdt.zip