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.

CCS GNU C Compiler and Linker Setup

Other Parts Discussed in Thread: AM4378, TM4C123GH6PM, UNIFLASH, EK-TM4C123GXL, SEGGER, TM4C1294NCPDT, TM4C129ENCPDT

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

  • 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

    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:
    malloc not working?

    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.

    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.

  • Hello Chester,

    I did spend considerable time today with CCSv6.1.1 and v4.8.4 compiler today and it seems that there were 2 issues

    1. malloc was not being linked in Thumb2 but in ARM mode. This one I was able to rectify
    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.

    Regards
    Amit
  • 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.

    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:

    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)
    

  • 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.

    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".

    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.

  • Hello Chester,

    Wishing you a happy new year, and thanks a lot for the details note. I am working on it now (but don't know if I can complete it first or see 2016 first). However I am going to go back to GNUC compiler to make sure it works in CCS.

    Regards
    Amit
  • 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

    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.

    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]

  • Hello Chester,

    I have started a forum thread on CCS as well for the issues I ran into. I have marked this thread there as well, so that CCS Team can take the changes into account for the next release.

    Regards
    Amit
  • 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.

    The _start runtime library function in the v4.8.4 compiler for the Cortex-M4 devices performs the following actions:

    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.

  • It's sad that even after over a year the changes have not been incorporated in the CCS V7, even after the excellent work of Chester Gillon. I'm experiencing the same issues with the TM4C1294. and the

    I made all the suggested changes, but get now a hardfault in __libc_init_array().
    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)

    @Chester Gillon: Do you have any idea what could be still wrong? Can I P.M. you the project?

    @TI: We are about to roll a new HW revision and if I don't get a reliable GCC development environment in the next 48 hours I will have to suggest the management to dump TI completely and use a CPU from a different vendor that actually manages to get a GCC toolchain up & fully supported (so basically *ANY* other vendor)

    Sorry for bad attitude but I spend more timing looking for issues with the IDE then with actually writing code, that is not how it should be.

    Markus
  • Markus Rudolf said:
    @Chester Gillon: Do you have any idea what could be still wrong?

    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:
    Can I P.M. you the project?

    If the above doesn't work, you can P.M. me with the project.

  • It has the ENTRY stuff, I merged your linkerscript into the provided TM4C1294 one.
  • Chester, Markus, and Amit,
    It looks like a lot of work has already been done on debugging the issues of using GCC on TM4C in CCS. I apologize that we missed getting this into the last release of CCS. 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.
  • Chester did an amazing job fixing the linker script and sent me an updated one which for now works like a charm and is in "torture test" right now. Yes, please get it fixed upstream, I'm pretty sure it will save developers days of head scratching.
  • 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)

    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.

    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 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.

    Bob, just adding some information on the investigation I performed into why Markus's program was failing to start.

    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

  • Thanks Chester.
  • I have to join this thread because we're having loads of problems with this too. Our team recently started a TM4C project and we haven't gotten far at all because of the compiler/linker/startup setup that CCS and TivaWare provide for GCC.

    We were surprised that this is such a big problem because we've worked extensively with GCC on several embedded platforms and have never had to look at the linker setup or startup code.

    Our platform is TM4C129ENCPDT. We're working with CCS version 7.1.0.00016 and the gcc-arm-none-eabi-4_9-2015q3 that installed with CCS.

    Right now, our project is configured with the tm4c129encpdt.lds file that CCSv7 automatically copies into a project when you set up a GCC build configuration. As everyone here already knows, that file (which comes directly from the TivaWare directory) is missing some pretty important details, such as sizing and locating the stack correctly. Then the stack is defined as an ordinary variable in tm4c129encpdt_startup_gcc.c, that probably ends up in .bss or who knows where.

    It occurred to me that if, for some bizarre reason, you truly want to declare your stack that way, then as an absolute minimum there should be an __attribute__ that puts it at some reasonable absolute 8-byte aligned address. Now I did read something somewhere to the effect that the startup code contains some kludge to deal with a misaligned stack but if that's true then it smells fishy because the stack you get would end up smaller than the stack you asked for.

    But never mind all of that. My point is that the problems evidently run deeper than just dropping in a corrected linker command file. By reading through the other posts in this thread, it becomes apparent that this setup leaves various aspects of a proper C/C++ environment missing. If it doesn't cause problems right away, it will cause problems eventually, later in the project. In a way, I'm glad we're having these problems early in the project. So, I would like to put together a checklist of all the things a "proper" C/C++ environment should support, from the very basic things like clearing .bss to the more involved like supporting malloc(), printf() with redirection, floating point, calling C++ constructors and destructors, etc., and how each item needs to be addressed (linker setup, startup code, library selection, etc). That checklist would help everyone tremendously. Because right now, the information is scattered all over the place and it takes hours to hunt down each thing.

    I agree with others here that the fixes that have already been discussed here should really be incorporated into CCS to eliminate these problems for new users down the road.

    Also, the following wiki page really needs to be updated with the additional information: processors.wiki.ti.com/.../Using_GCC_with_Tiva_in_CCSv6

    In researching the issues, I came across the following resources which contain various tidbits of information. They're a bit older but still valuable:

    kernelhacks.blogspot.com/.../the-complete-tutorial-for-stellaris_23.html

    www.bravegnu.org/.../c-startup.html

    eehusky.wordpress.com/.../