If you already do gcc based crossdevelopment, then you probably will not find anything new on this page. If you are familiar with gcc et al as native tools, then you will find most of the material trivial, although there are a few important differences between native and crossdevelopment. If you are relatively new to doing things the unix command line way, then read on.
What you find here is how to do crosscompilation to a bare target (i.e. reset starts executing your code and not some automagically created entry point). Although it was written for ARM7TDMI based development, most of what you find here is the same for other architectures. This is what you will find on this page:
The ARM7TDMI core can operate in two modes. One is ARM and the other is THUMB. ARM instructions are 32 bits wide. THUMB instructions are 16 bits wide, except long jumps, which remain 32 bits (actually, they are synthetised from two consecutive 16-bit instructions). Apart from the long jumps the THUMB instruction set is a proper subset of the ARM set, it simply contains the most often used ARM instructions, encoded in 16 bits. That 16 bits gets extended to a full 32-bit ARM instruction and that's what gets into the CPU's execution unit.
Naturally, the instructions being half as wide increases your code density. However, you loose a lot of useful stuff that the ARM mode has. Thus, as a rule of thumb (no pun intended), THUMB code is about 2/3 of the size of the equivalent ARM code and runs about 2/3 of the speed of the ARM code. These figures vary significantly depending on what you do, so the numbers above are only informative; your actual experience might be rather different.
The processor can switch between the two modes on the fly using a special jump instruction. That sounds pretty cool, because you can compile the bits that can make use of the extra features of the ARM set for ARM and the more mundane things for THUMB and have the best of both worlds.
Indeed that is the case, but you need to tell the compiler that you want to do that, because it has to generate the special mode switch code snipets whenever you call an ARM function from THUMB code or vice versa. That is called interworking.
If you have very little code space and you do not want to use ARM functions at all, then you can also tell the compiler that you do not use interworking. In that case the compiler will know that it doesn't have to care about it and will not generate those little code snipets that would otherwise ensure that a function can be called from any mode and guarantee that it returns to the caller in the correct mode. That will save you some codespace (not much, actually, but if things get really tight, every word counts).
In general, when you compile a single C file into an object file the gcc command line looks something like this:
gcc options -c -o filename.o filename.c
In case of crosscompiling usually you have arm-eabi-gcc or similar instead of simply gcc, of course. There are hundreds of options you can pass to gcc. Most of them do not concern you, but there are a few that you have to deal with.
<path>. You can issue as many of these as you wish. You do not have to specify the path for the include files that come with gcc (stdarg.h, stddef.h, float.h and a few others) because the compiler knows that. On the other hand, you have to use this switch to tell the compiler about the location of the headers for this library. If you installed the library at the default location, you should say-I /usr/local/armlib/include.-O0, tells the compiler to turn all optimisation off. It is useful for debugging, because the optimiser transforms your code in ways which may cause your variables or even functions to disappear. If you turn optimisation off, everything works exactly as you've written it. Note that your code size, stack usage and execution time will increase greatly with -O0.make understands. That is, you can generate the dependencies for your makefile automatically. The dependency file is located at and called the same as the output file except that it has the extension of ".d" instead of ".o".-pedantic but then you can not use anonymous structures and unions. These are very useful constructs and often save a lot of typing. However, if you do not want to use them and strict C standard compliance is important, then specify the -pedantic switch as well.-mcpu=arm7tdmi-s switch, if you want. The -s suffix indicates that it is a synthesisable (soft) core. There are some differences between the two core versions, but the instruction set and instruction execution times are identical. From the compiler's point of view the two cores are the same.We will use gcc to assemble the source. If you pass a source file that has the filename extension of ".s", then gcc treats it as assembly source and passes it to the assembler. I you use the filename extension ".S", note the capital 'S', then gcc will run the source through the C preprocessor before handling it to the assembler. That way you can share #define-s between assembly and C source.
If you use gcc as your assembler, the command line is the same as in case of a C compilation but only a subset of the C compiler options listed should be used. Namely:
The specifying of the C standard might seem superfluous, but don't forget that gcc might have to run your assembly through the C preprocessor and the switch makes sense there.
Assuming that you let gcc to run the linker for you, your linker command line should look something like this:
gcc options -T <linker script file> -Wl,-Map,<mapfile> -o <result> <object files> <libraries> -lgcc
The following is the list of the options you should specify for linking:
libgcc.a, the compiler support library. This switch tells the linker whether it should use the ARM or the THUMB version of that library.-fno-common C compiler option, if you don't know what the common section is, just use the switch, if you do know, they you will know when not to use it.-Werror compiler switch.libgcc.a.path. You can use as many of these as you wish. The directories will be searched in the same order as you specified them. You don't have to tell the linker about the location of libgcc.a but you have to tell about the libraries in this package. If you installed them in the default location, you will have to specify-L /usr/local/armlib/libIf you specify the -Wl,-Map,<mapfile> switch (note that there are no spaces in it!), the linker will generate a mapfile that contains the locations of all global symbols. It can come useful, especially if you have no symbolic debugger for your target.
You specify the filename where the final, linked objectfile will go. It will have all addresses resolved and finalised. If you specified the -g option, it will also contain all debug information that you will need to use with gdb.
Under normal circumstances the linker has built-in knowledge about the system it links for. Therefore, allocation of memory regions happens automatically. With your bare-metal embedded system you are the only one who knows what is mapped where, how much RAM/FLASH you have, how you want to organise memory and so on. Therefore, you have to tell it to the linker. So you write a linker script file. Here is a short introduction, but for further details you should consult the linker's info page.
The linker script file is a textfile, that has some resemblance to C in that it uses { and } as block delimiters, you can use C block comments and that you can write expressions. The similarities end there, though. The first item in your linker control file is the specification of the format of the output file. It looks like this:
OUTPUT_FORMAT(elf32-littlearm)
It means that the format is ELF, 32 bit address space, little endian byte order for both headers and content and that it is for the ARM architecture. Note that you have to specify ELF even if you use EABI.
The next step is to define your memory regions. As an example, I will use an NXP chip. In the NXP LPC2xxx microcontrollers the FLASH starts at 0 and the built-in RAM starts at 0x40000000. Let's assume that we have 128K FLASH and 32K RAM. You can give arbitrary names for memory regions. I will call the FLASH 'rom' and the RAM 'ram'. Here it is:
MEMORY
{
rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x20000
ram (rwx) : ORIGIN = 0x40000000, LENGTH = 0x08000
}
The (rx) and (rwx) mean that the FLASH is readable and executable and the RAM is read, write, execute. It doesn't matter much, because there's no operating system to enforce the rules, but it is neat to specify it.
The next, and most important part of the linker control file is the section placement. The general format is:
SECTIONS
{
section1_definition
section2_definition
...
sectionN_definition
}
A section definition can be very complex. Here only the bare minimum is explained. For an in-depth understanding, you need to consult the info page of ld.
A section definition has a skeleton like this:
section_name address (type) : attributes
{
section1_command
section2_command
...
sectionN_command
} > region
The various items are the following:
section_name.text or .data. You can use any identifier you wish, but customarily the executable code is in .text, initialise data goes to .data and uninitialised data resides in .bss.address(type)(NOLOAD). This applies to the .bss section, for example.attributesAT ( exression ) attribute tells the linker that it should place the content of the section at the address specified by the expression. Note that symbols within the section will still have the value relative to the address of the section, it's just that when the linker is done and it writes the output file, the content of this section will be at the specified location in the output. You can use this feature to put the initialiser for the .data segment just after the code. The expression can contain all sorts of things, but for us only two operators are important: ADDR ( section_name )SIZEOF ( section_name ) > regionThe remaining item is section_command. There are a few possibilities here. The most important one tells the linker what sections of the object (and library) files to put into the section. It has the general format of
file_name(section section ... section)
The file_name is the name of the object file that you want to extract the section(s) from. There are all sorts of complicated possibilities there, but for our minimalistic purposes the wildchar filename of '*' is sufficient, since we want to process all files. The section names between the parenthesis are the names of the sections that you want to pull from the object files. Again, there are all sorts of complicated possibilities with which you can control your linking at a very fine-grained level, but we just simply want to lump identical sections together. So, to collect all code (which gcc puts into the .text segment) from all object files you would write this:
*(.text)
Alas, that's not enough. Sometimes gcc has to generate extra code and it puts extensions to the section name that identifies what that little fragment is all about. So you will find sections called .text.something and not just simply .text. Fortunately, the wildcards work with section names as well, so you just modify your section specification to:
*(.text*)
Now you have to deal with the possibility that a segment is prefixed. For example, you can tell the ARM library to prefix each segment with the name of the library, so the text segment in the math library would be .libm.text. To also cater for that you can use the idiom
*(*.text*)
The compiler can generate all sorts of sections, but if you are compiling straight C code, you can expect the following:
.text Contains the actual code.rodata Read-only data.data Initialised data.bss Uninitialised dataFurthermore, the linker can generate two more segment names, used to resolve interworking issues:
.glue_something.v4_bxThey should go to the code segment.
When you collect things that go into one section you can do it two ways. Take the section that goes to the FLASH for example. You want all the code to go there plus all the constant data to be there. As the linker processes the object files, it can either put the .text and .rodata sections ane after the other as they come, or it can collect all .text sections first and all .rodata sections next. It is up to you which one you prefer. If you specify more than one section between the parenthesis, then you will get the sections you listed intermingled in the output:
*(*.text* *.rodata*)
If you want all code collected first, followed by all data, then you write two input section commands:
*(*.text*) *(*.rodata*)
The other section_command that you want to use is the assignment. If you want to clear the uninitialised data segment and initialise the initialised one at boot, you have to know where these sections start and how large they are. Furthermore, you need to know where to find the initialisers. The linker allows you to define symbols that can be referred to from C code. The general format is
symbol = expression ;
There are all sorts of expression possibilities, but we will use only two here. The period, '.' means the current location. That is, if you write:
*(*.text*) boundary = . ; *(*.rodata*)
then the linker will define the symbol 'boundary' and it will be the address where the code finishes and the read-only data starts in your FALSH.
The other expression you often use is in the format of
symbol = ALIGN( 8 ) ;
This also assigns the current location to the symbol, but it aligns the current location to the next 8-byte boundary before doing that. So, if you want to know where the .bss segment starts and ends and in the same time you want the section to finish on a double-word boundary (you need 8-byte alignment because of EABI's requirements regarding to long long and double) you'd write:
_bss_beg = .; *(*.bss*) _bss_end = ALIGN( 8 );
It's very important to realise that these symbols are addresses, not data values. So how can you tell the C compiler that you refer to an object but the name implies an address and not a value stored in memory? Simple. You decleare the object as an array. The name if the array is actually the address of the first element of the array. So if you want to clear your .bss specified with the above section commands, you could write:
extern char _bss_beg[]; extern char _bss_end[]; memset( _bss_beg, 0, _bss_end - _bss_beg );
So now we can put the complete linker script together:
/*
* Output format
*/
OUTPUT_FORMAT(elf32-littlearm)
/*
* Memory regions
*/
MEMORY
{
rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x20000
ram (rwx) : ORIGIN = 0x40000000, LENGTH = 0x08000
}
/*
* Section definitions
*/
SECTIONS
{
/* The stuff that we want to go into the FLASH */
.text 0x0 :
{
/* The vector table must be the first. It was placed into the
section called ".vectors". Get that, followed by the the
actual code in .text and read-only data in .rodata.
Don't forget the glue code either. */
*(.vectors)
*(*.text*)
*(*.rodata*)
*(*.glue*)
*(*.v4_bx)
/* Align and make note of the end of the section */
_text_end = ALIGN( 8 );
/* This section goes to the FLASH */
} > rom
/* The initialised data segment starts at where the RAM starts.
However, we want the actual initialisers to go just after the
code and read-only data. So the data section lives in the RAM
but the initialisers are stored in the FLASH. */
.data 0x40000000 : AT( _text_end )
{
_data_beg = . ;
*(*.data*)
_data_end = ALIGN( 8 );
} > ram
/* The uninitialised data is just after the initialised one in the RAM.
It does not have to be loaded. If there would be common sections, they
should also come here, but we don't use them. */
.bss (NOLOAD) :
{
_bss_beg = . ;
*(*.bss*)
_bss_end = ALIGN( 8 );
} > ram
}
with that then you can clear your .bss and initialise your .data like this (note that your stack at the moment of the execution of this program fragment must not be in the .bss!):
extern char _code_end[]; extern char _data_beg[], _data_end[]; extern char _bss_beg[], _bss_end[]; memset( _bss_beg, 0, _bss_end - _bss_beg ); memcpy( _data_beg, _code_end, data_end - data_beg );
You just simply list all the object files in your code. They will be parsed by the linker in the order you specify them.
You list the libraries you want to link against. The format is
-lname_stemfor each library you want to scan for undefined symbols. The linker will put libin front of the name_stem and will put a .a suffix after it. Thus, if you want to link against libfoo.a, you write -lfoo and the linker supplies the rest. The linker will look for libfoo.a in all the directories you listed with the -L command line switch. If you want, you can also specify the full pathname of a library, in which case you can not omit the 'lib' and the '.a':
-l~/mylibs/libmagic.a
The libraries are searched for undefined symbols in the order you specify them, so if multiple libraries define the same symbol, the library order is important. That way a library specified earlier can overload symbols defined in a library specified later.
Usually the tools that can burn the FLASH in your micro expect a hexfile, a file that contains the binary image in ASCII format (as hexadecimal numbers). Two common formats are in use, they are called Motorola S-record and Intel hex. The former comes from Motorola and is called S-record because every line in it starts with the character 'S'. The latter is from Intel. Since in that format every line starts with the ':' character and 'Intel colon-record' doesn't sound really appealing, it was simply called 'hex'.
The binutils package installed a handy utility called objcopy. That program can read an object file in one format and write it in an other. It can do all sorts of other things as well, but you don't need them to create a hexfile. The command to generate Motorola S-record from the linking result is:
arm-eabi-objcopy -O srec linker_output_file s-record_file
Similarly, if you want Intel hex format, the command to use is:
arm-eabi-objcopy -O ihex linker_output_file intel-hex_file
In some cases you want to get the raw binary image itself, for example if you want to process it and calculate some checksum on it. You can use objcopy to obtain that as well:
arm-eabi-objcopy -O binary linker_output_file raw_binary_file
All of these commands will strip all debug info from the file, the only thing remaining in the output is the actual binary image that you should burn into the FLASH.
From the above you can create a makefile to build your project. Let's assume that you put the linker control file into mystuff.ld and you want to create a Motorola S-record file called mystuff.hex at the end. You have reset.S containing the reset vectors and the boot code and main.c containing your actual application code. You want to use the C library and the gcc runtime replecement routines from this library, that you installed at the default /usr/local/armlib location. We also assume that you use EABI and thus the tool prefix is arm-eabi-.
# Project files
FILES = reset.S main.c
# Libraries we use
LIBS = c gccs
# Tool prefix.
TOOLPFX = arm-eabi-
# The tools. We use gcc as assembler and linker as well
CC = $(TOOLPFX)gcc
AS = $(CC)
LD = $(CC)
OC = $(TOOLPFX)objcopy
# Location of the ARM library
LIBDIR = /usr/local/armlib
# C, assembler and link flags.
# We compile for THUMB and enable interworking, optimise for size
CFLAGS = -g -std=gnu99 -pipe -MD -c -ffreestanding -Os \
-I $(LIBDIR)/include -I . \
-mcpu=arm7tdmi-s -mthumb -mthumb-interwork \
-fno-delete-null-pointer-checks -fno-trapping-math -fno-math-errno \
-fwrapv -fno-strict-aliasing -fno-inline-small-functions \
-fno-unroll-loops -fno-common \
-Wall -Wextra -Werror -Wundef -Wmissing-declarations -Wshadow \
-Wwrite-strings -Wmissing-field-initializers
AFLAGS = -g -std=gnu99 -pipe -MD -c \
-I $(LIBDIR)/include -I . \
-mcpu=arm7tdmi-s -mthumb -mthumb-interwork \
-Wall -Wextra -Werror
LFLAGS = -g -mcpu=arm7tdmi-s -mthumb -mthumb-interwork -nostdlib \
-static -static-libgcc -Wl,--warn-common,--fatal-warnings \
-L $(LIBDIR)/lib
# The object files
OBJS = $(patsubst %,%.o,$(basename $(FILES)))
# The default target
mystuff.hex: mystuff.abs
$(OC) -O srec $< $@
# The clean-up target
clean:
rm -f *.o *.d mystuff.abs mystuff.map mystuff.hex
# How to link the absolute file
mystuff.abs: $(OBJS) mystuff.ld
$(LD) $(LFLAGS) -T mystuff.ld -Wl,-Map,mystuff.map \
-o $@ $(OBJS) $(patsubst %,-l %-tis,$(LIBS)) -l gcc
# Rules to compile and assemble files
%.o: %.c
$(CC) $(CFLAGS) -o $@ $<
%.o: %.S
$(AS) $(AFLAGS) -o $@ $<
# Load the automatically generated dependencies, if they exist
-include $(patsubst %,%.d,$(basename $(FILES))
1.7.1