Purpose of linker files in placing code in internal flash [closed]

How linker file has to be programmed to place a code in internal flash?how linker filer files are different for different compilers?

closed as too broad by PeterJ, Chetan Bhargava, nidhin, Chris Stratton, Tom CarpenterFeb 22 '16 at 4:41

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

• Please ask a more specific question – Robherc KV5ROB Feb 21 '16 at 5:00

This boils down to what the binary representation of a program ought to be. You might for instance think that your program is a series of bytes, and that all you have to do is to transfer those bytes into the internal flash. However, there's a problem with that, namely addressing of data.

If, in your program, you rely on a piece of data, e.g.:

static int my_variable = 1;


Then any code that wants to use this variable must know the actual address of the variable. If you put the executable binary in location 0x1000_0000, and the offset within the binary is 0xFF0, then the system needs to know that it will end up at 0x1000_0FF0.

The program that has the task of grouping all code and data snippets into one binary file is called the linker. The purpose of the linker script is to aid the linker to figure out where these addresses are going to end up at execution time. But of course, there are additional things that the linker-script does for us:

• It can divide the binary into different sections, and handle those independently. This is useful, when you want certain code or data to reside somewhere special.
• It understands sections such as the "bss". This is a special memory region where the linker will assume is all zeros at initialization. The code below [1], will most likely end up being in the .bss section.
• It can also understand copied (duplicated) sections, where it will assume that it will be copied somewhere else. For instance, non-zero data is usually copied from flash memory to RAM at initialization time. Code [2] below shows some data that might be copied to RAM, hence needing a different memory address at compile time.
• The linker script can also define variables at specific locations. This is helpful in order to figure out the sizes of the different sections.

[1] .bss data:

static int my_table[4] = { 0, 0, 0, 0 };


[2] .data:

static float pi = 3.14159287;


The linker file is responsible for specifying the layout of the processor's memory to the compiler, so it can allocate both data and the program code in the right places. I'm going to use a fairly complicated example, since it shows the full capabilities of this scheme.

The 8-bit MC9S08AZ1128 microcontroller from Freescale uses a Von Neumann architecture, that is both the variables and program code are allocated within the same address space. The processor has a 16-bit address space addressing 64K bytes (0x0000-0xFFFF).

This processor has two special addressing features -- 8-bit Freescale processors (dating way back to the original 6800 from 40 years ago) have a section for "zero or direct page" variables and I/O registers, that can be accessed with just an 8-bit address using a special addressing mode for this purpose. This allows for a full range of two-byte instructions in addition to the three-byte ones which would have a 16-bit address.

Secondly, even with the 16-bit address space, the processor actually can have programs that are up to 128K bytes long. It handles this using a paging register, which maps 8 16K pages onto one 16K address block in memory.

Here is the memory map for this microcontroller:

So the compiler needs to know where to allocate the variables and code to the various sections of memory. It does so with the linker file, which breaks up the chunks of memory into named segments:

SEGMENTS
Z_RAM                    =  READ_WRITE   0x0080 TO 0x00FF;
RAM                      =  READ_WRITE   0x0100 TO 0x17FF;
RAM1                     =  READ_WRITE   0x1900 TO 0x217F;
/* unbanked FLASH ROM */
ROM                      =  READ_ONLY    0x4000 TO 0x7FFF;
ROM1                     =  READ_ONLY    0x2180 TO 0x3BFF;
ROM2                     =  READ_ONLY    0xC000 TO 0xFF7F;
EEPROM                   =  READ_ONLY    0x3C00 TO 0x3FFF;
INTVECTS                 =  READ_ONLY    0xFF80 TO 0xFFFF;
/* banked FLASH ROM */
PPAGE_0                  =  READ_ONLY    0x008000 TO 0x00A17F; /* PAGE partially contained in ROM segment */
PPAGE_0_1                =  READ_ONLY    0x00BC00 TO 0x00BFFF;
PPAGE_2                  =  READ_ONLY    0x028000 TO 0x02BFFF;
PPAGE_3                  =  READ_ONLY    0x038000 TO 0x03BFFF;
PPAGE_4                  =  READ_ONLY    0x048000 TO 0x04BFFF;
PPAGE_5                  =  READ_ONLY    0x058000 TO 0x05BFFF;
PPAGE_6                  =  READ_ONLY    0x068000 TO 0x06BFFF;
PPAGE_7                  =  READ_ONLY    0x078000 TO 0x07BFFF;

/* PPAGE_1                  =  READ_ONLY    0x018000 TO 0x01BFFF; PAGE already contained in segment at 0x4000-0x7FFF */
/* PPAGE_3                  =  READ_ONLY    0x038000 TO 0x03BFFF; PAGE already contained in segment at 0xC000-0xFFFF */
END


The programmer still has to tell the compiler what variables are to be allocated to the zero page, and which routines are to banked. Interrupt routines, for example, can't be banked since they can occur at any time. Same for main(). Non-banked memory is also useful for functions that may be called from more than one segment.

There is a special instruction (CALL) that is used instead the normal JSR instruction to call from page 0 to a bank or from one bank to the next.

The programmer uses "pragmas" to specify whether a routine is to be places in non-banked or banked memory:

#pragma CODE_SEG NON_BANKED
void __interrupt VectorNumber_nnnn isr_name(void)
{
/* code for interrupt routine which must be in non-bank memory
}
#pragma CODE_SEG DEFAULT

/* resume default code which may be banked or not */


It can also specify whether variables go into the zero page, or into the default segment:

#pragma DATA_SEG __SHORT_SEG unsigned int myDirectPageVar1, myVar2;
#pragma DATA_SEG DEFAULT
/* resume placing data in segments requiring a 16-bit address */


There also ways of specifying whether data is to be initialized or not (the latter being the .BSS section, historically named Block Started by Symbol in an assembler program back in the 1950'). If initialized, then whether it is contained as part of the program code const vaariable) or copied to RAM (static variable) depends on the compiler. For example, the GCC compiler puts all const variables in the code segment.

Code Segment (or Text segment) - stores only code and maybe const variables
Data segment - stores uninitialized global and static variables, both read-write
(BSS (or Block Started by Symbols), and static read-only (see below).
Uninitialized static variables are actually initialized to 0.


Read-only static and const variables can be allocated in either the data segment or code segment, depending on the compiler (the C standard leaves this up to the implementer). If allocated to the RAM segment, their initialization values are stored in the code or text segment and copied down on startup.

The GCC compiler, for example, puts all const variables in the code segment. Other compilers only put const variables in the code segment if preceded by the non-standard "code" keyword. The reason for this, is that it takes more instructions to read data from the code segment compared to the data segment for many microcontrollers. But putting an read-only array, for example, in the data segment wastes valuable RAM space, which maybe very limited in a microcontroller. For example, the memory map shown above shows only a little over 8K of regular RAM plus 128 bytes of zero or direct page RAM.

Understand when you compile say a hello world program on your laptop with one gcc call, there are multiple steps, compile to assembly language, then assembler then makes an object out of that then the linker is called.

The compile step does not know anything about the memory/address layout of the target, it most likely knows the target architecture (x86, ARM, MIPS, pdp-11, etc) but does not at this point know what address the code it is building will live. The linker is responsible for sewing the objects together and building the final binary. It resolves external branches and other external to an object connections (global variables, etc).

For one toolchain (MSVC, Borland, GNU, Keil, etc) where there have been one to many versions over time. The way you communicate with that one linker changes slowly over time but to some extent is the same. Between toolchains though there is no reason to expect any portability, how you tell one compiler where your flashes are and where your sram spaces are is different from one toolchain to the next.

You have to tell the toolchain/linker where your flash is if you expect to be able to build a binary that actually works. Likewise if you have any variables that change and cannot fit in registers alone, then you need some ram and you have to tell the linker where that ram is. Your code itself most likely has directly or indirectly specified the exact addresses for peripherals so you dont normally use the linker script for that.

It is often possible to compile position independent code such that the program itself can actually move and still execute as compiled (relative addressing vs direct addressing) but you still have to place the bootstrap code in the right place to boot the processor and you still have to tell it where to put the binary initially and where ram is for any variables. You may later copy/move that program to a different address to run it but the variables maybe not. You still need a linker even with position independent code for microcontrollers and other embedded bare metal work.

The linker script is often its own programming language if you will with its own syntax. You can spend as much or as little time as you want making as complicated as you want linker script. But remember the linker scripts are not often portable from one toolchain to the next so if you end up changing toolchains for the same project your source code might port as is, but the linker script might be a complete rewrite.