Keil Logo

ARMCLANG: Using armclang without the ARM C library


Information in this knowledgebase article applies to:

  • armclang v6.9 and later
  • Safety-critical applications

QUESTION

Some safety qualifications require access to the entire source code of a project, including the programming language library. DO-178 Level A has this requirement. May I have access to the ARM C language library to qualify?

ANSWER

The ARM C/C++ library is proprietary. Contact an ARM sales team, for a quote, if you want access to the source code. The ARM C library may be offered as a standalone product, under certain circumstances.

armclang already has several safety certifications. As of this writing (April 2018), the armclang v6.6 LTM maintenance was qualified for the certifications. MDK-Professional edition comes with a qualification kit, that aids in the approval process.

Without the C library source code, a user can only see the header files. Interestingly, armclang can be used without the ARM C library, by substituting the C library dependencies with code from another C language library. A basic procedure and discussion for C library startup is discussed below.

Advantages

  • Access to the source code of a language library may make it slightly easier to debug applications or control behavior
  • Easier to meet some safety certification requirements

Disadvantages

  • The ARM C library is optimized for ARM devices. There may be a performance loss, with another C library. Sometimes, language libraries have to choose between portability and the efficiencies of a specific architecture.
  • RW data compression is turned off, so RW initial data values may take up additional readonly memory.
  • ARM Support staff can help answer questions about the ARM C library, but if another C library is used with armclang, contact the author of the alternative library for help C library questions.
  • It is time-consuming to re-add a C language source to a project. In many cases, a user is better off copying specific *code* segments, from a C library, (while complying with any license requirements), than manually adding in the language source files.
  • Less information is available in the listing files generated by armlink. (Ex. [Unknown symbol]) Users can still depend on the *.map file, to generate information about the addresses of execution regions as well as most functions, and variables.

PROCEDURE

A crucial library initialization step is to initialize any RW data to their starting values. armlink stores the initial values for RW data, after the normal readonly code and data, usually in internal flash. Then the values are loaded into RAM, during program execution, using a C library function called __main. This function is documented in ARM Compiler C Library Startup and Initialization Application Note 241.

Many applications will reference __main in startup assembly, like the following:

;/***********************************************************************
; * $Id: startup_LPC18xx.s 6471 2011-02-16 17:13:35Z nxp27266 $
; *
; * Project: LPC18xx CMSIS Package
; *
; * Description: Cortex-M3 Core Device Startup File for the NXP LPC18xx
; *              Device Series.
; *
; * Copyright(C) 2011, NXP Semiconductor
; * All rights reserved.
; *
; *                                                      modified by KEIL
; ***********************************************************************

;...


Reset_Handler   PROC
                EXPORT  Reset_Handler           [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

Add a new file to the project. We will add a new, simplified definition for __main(). Paste in the following code:

#include <stdint.h>
#include "cmsis_armclang.h"
typedef uint32_t size_t;


void memcpy( void * dest, void * source, volatile size_t numBytes) ;


void memcpy( void * dest, void * source, volatile size_t numBytes) {

    volatile size_t r;
    volatile char *csrc = source;
    volatile char *cdest = dest;

    for( r = 0; r < numBytes; r++ )
    {
       cdest[r] = csrc[r];
    }

}


extern uint32_t Image$$RW_IRAM1$$Base;
extern uint32_t Image$$RW_IRAM1$$Length;
extern uint32_t Image$$ER_IROM1$$Base;
extern uint32_t Image$$ER_IROM1$$Limit;
extern uint32_t Image$$ER_IROM1$$Length;
extern uint32_t Load$$LR$$LR_IROM1$$Length;

void __main(void) __attribute((noinline));
void __main(void) __attribute((noinline)){





//*Size calculation for copy (i.e. length of RW-init data
//that was added onto the end of flash image)

//Initial values for RW-data must be stored as read-only
//to allow device to startup correctly after a reset

volatile size_t length = ((uint32_t)&Load$$LR$$LR_IROM1$$Length) - ((uint32_t)(&Image$$ER_IROM1$$Length));


if(  length > ((uint32_t)&Image$$RW_IRAM1$$Length) )
  #error "Multiple regions of RAM are used. \ 
This exampe is a blind copy, designed to initialize a *single* RAM region. \ 
Modify this function if you are using multiple RAM regions."

memcpy( &Image$$RW_IRAM1$$Base, &Image$$ER_IROM1$$Limit , length);
        //* Start of RAM
                               //* End of normal RO
                               // (i.e. beginning of RW-init data ROM)




//Some code to replace part of the __rt_entry function,
//to initialize stack pointer, and jump to main()


//Some applications may need to update SCB->VTOR
__set_MSP( ((uint32_t *) &Image$$ER_IROM1$$Base)[0] );


//Go to the more familiar main()...
   __asm (
"LDR     R0, =main;"
"BX     R0");
}

Note that depending on different applications, when compiling calls to memcpy(), Arm C compiler might generate calls to specialized, optimized, library functions __aeabi* instead. When you get the following linker error,

L6218E: Undefined symbol __aeabi_memcpy4

you need to also provide your own implementations of these __aeabi* functions.

The above code will use one CMSIS function, __set_MSP(). You have access to the source code of __set_MSP(). It will also steal a header file from the ARM C library, "stdint.h".

The example tries to use linker symbols as much as possible, to automate the source address and calculation of the length of the copy. As your code base grows in size, the starting address for the copy will also increase, because the RW data is always added at the end. So the symbols save you from an extra build. However, be careful of optimization levels with this memcpy(). The volatile keyword and the noinline attribute are used, to handle this for most optimization levels.

Add the following to armclang command line, using the Misc. Controls field:

-nostdlib -nostdlibinc -fno-builtin    -fno-inline-functions
//The first three options are required //Optional. If you want to
                                       //guarantee that the above
                                       //implementation of memcpy()
                                       //will behave correctly at the
                                       //highest performance optimization
                                       //levels (-Ofast), add
                                       //"-fno-inline-functions" or use
                                       //another implementation of
                                       //memcpy()

Add the following to armlink command line, using the Misc. Controls field:

--no_scanlib  --datacompressor=off  --entry=Reset_Handler  --verbose
//The first three options are required                     //Optional

Go to Options for Target => Target tab, and check if the Microlib option is enabled. If enabled, disable the option. This is a special version of the ARM C library, so we don't have access to it, anymore.

In your startup file, there may be a line, like the following:

                IMPORT  __use_two_region_memory
                EXPORT  __user_initial_stackheap
__user_initial_stackheap

; ==>>
; Comment out the following line of code
                ;IMPORT  __use_two_region_memory
                EXPORT  __user_initial_stackheap
__user_initial_stackheap

This symbol is also specific to the ARM C library (the two region memory model).

Now, we want to check that it works. Go to the source file, where main() is defined:

//...
static int i = 1;
//...
int main(void) {
i++;
//...

If you place breakpoints at the line where main() and __main() are defined, and place variable "&i" in a memory window, you should be able to see the value change from 0 to 1, during the memcpy(), and see the value update from 1 to 2, after program execution enters main().

MORE INFORMATION

SEE ALSO

Last Reviewed: Thursday, January 14, 2021


Did this article provide the answer you needed?
 
Yes
No
Not Sure
 
  Arm logo
Important information

This site uses cookies to store information on your computer. By continuing to use our site, you consent to our cookies.

Change Settings

Privacy Policy Update

Arm’s Privacy Policy has been updated. By continuing to use our site, you consent to Arm’s Privacy Policy. Please review our Privacy Policy to learn more about our collection, use and transfers
of your data.