Linker - wgrzegorz/c5xtools GitHub Wiki
Normative source: SPRU018D, chapter 8 (Linker Description). The directive and
option syntax described here is the linker command language; c5xlnk
implements it.
The linker takes one or more COFF object files, resolves the references between
them, lays their sections out in target memory, and writes a single executable
COFF module. It also accepts archive libraries and linker command files as
input. SPRU018D names its linker dsplnk; the invocation examples below use
c5xlnk, the tool this wiki documents, but the options and command-language
constructs are exactly those of the normative source.
The command line has the form:
c5xlnk [-options] file1 ... filen
The input files can be object files, linker command files, or archive
libraries. The default input extension is .obj; any other extension must be
written out. The linker inspects each file and decides for itself whether it is
an object file (which it links) or an ASCII command file (which it reads for
further commands). The default output filename is a.out.
Files and options can be given three ways, and the three can be mixed:
- On the command line. This links two object files and names the output
link.out:
c5xlnk file1.obj file2.obj -o link.out
-
Interactively. Run the linker with no arguments and it prompts for object files, command files, an output file, and options in turn.
-
From a command file. The linker reads and processes a command file the moment it meets the filename, so files pull in from the command file in the order they appear there, before any files that follow on the command line:
c5xlnk -m link.map linker.cmd file3.obj
Options can sit anywhere on the command line or in a command file. Order does
not matter except for -l and -i (see Libraries below).
An option is introduced by a hyphen and may be separated from its argument by a space.
| Option | Meaning |
|---|---|
-a |
Produce an absolute, executable module. This is the default when neither -a nor -r is given. |
-ar |
Produce an executable module that still carries relocation entries. |
-b |
Do not merge duplicate symbolic debugging entries (faster, less memory). |
-c |
Use the C compiler ROM autoinitialization linking conventions. |
-cr |
Use the C compiler RAM autoinitialization linking conventions. |
-e global_symbol |
Set the program entry point to global_symbol (must be external). |
-f fill_value |
Default fill value for holes in output sections; a 16-bit constant. |
-h |
Make all global symbols static (used with partial linking). |
-heap size |
Set the C heap (.sysmem) size in words and define a size symbol. Default 1K words. |
-i dir |
Add dir to the front of the library search path. Must precede -l. |
-l filename |
Name an archive library as input. |
-m filename |
Write a link map (memory configuration, section allocation, symbol addresses) to filename. |
-o filename |
Name the output module. Default a.out. |
-q |
Quiet run; suppress the banner (effective when first on the line or in the file). |
-r |
Produce a relocatable output module (retains relocation entries). |
-s |
Strip the symbol table and line-number entries from the output. |
-stack size |
Set the C stack (.stack) size in words and define a size symbol. Default 1K words. |
-u symbol |
Enter symbol as unresolved so a library member defining it is pulled in. |
-v0 |
Generate the older version 0 COFF format. |
-w |
Warn when an output section not named in SECTIONS is created. |
-x |
Reread libraries until no more references resolve (resolves back references). |
Relocation choice: -a alone gives an absolute module with no relocation
information and no unresolved references; -r alone retains relocation entries
and may leave unresolved references (an unexecutable file suitable for
relinking); -ar gives an executable module that still carries relocation
entries.
Entry point: the linker chooses, in order, the -e symbol, then _c_int0,
then _main, then zero.
A command file is an ASCII file that collects linking information you would otherwise retype. It may hold input filenames, options (written exactly as on the command line), the MEMORY and SECTIONS directives, and symbol assignment statements. The MEMORY and SECTIONS directives can only appear in a command file, never on the command line.
Comments are delimited by /* and */. Blanks and blank lines are
insignificant except as separators. One command file may call another as its
last statement (nesting up to 16 levels); the linker does not return to the
calling file.
a.obj /* First input file */
b.obj /* Second input file */
-o prog.out /* Output file */
-m prog.map /* Map file */
The following words are reserved as directive keywords and must not be used as
symbol or section names: align/ALIGN, attr/ATTR, block/BLOCK,
COPY, DSECT, f/fill/FILL, group/GROUP, l/len/length/LENGTH,
load/LOAD, MEMORY, NOLOAD, o/org/origin/ORIGIN, page/PAGE,
range, run/RUN, SECTIONS, spare, type/TYPE, and UNION.
Constants in a command file may be written in assembler form (for example 32,
40q, 20h) or in C form (32, 040, 0x20).
MEMORY describes the target's physical memory so the linker knows where object code may go. Memory you name is configured; memory you leave out is unconfigured, and the linker never places code there.
MEMORY
{
PAGE 0 : name1 [(attr)] : origin = constant , length = constant ;
PAGE n : nameN [(attr)] : origin = constant , length = constant ;
}
| Field | Meaning |
|---|---|
PAGE |
A memory space, up to 255 of them. PAGE 0 is usually program memory, PAGE 1 data memory. Omitting PAGE means PAGE 0. Each page is an independent address space and may overlap another page. |
name |
One to eight characters from A-Z a-z $ . _. Names only label ranges; they are internal to the linker and are not written to the output. Within a page names must be unique and ranges must not overlap. |
attr |
One to four optional attributes in parentheses: R readable, W writable, X can hold executable code, I can be initialized. A range with no attributes has all four. |
origin |
Start address; may be written origin, org, or o. A 16-bit constant. |
length |
Range length; may be written length, len, or l. A 16-bit constant. |
Annotated example: 4K words of ROM at 0 in program memory, plus two data-memory blocks:
MEMORY
{
PAGE 0 : ROM : origin = 0h , length = 1000h /* program memory */
PAGE 1 : B2 : origin = 60h , length = 20h /* data memory */
B0B1 : origin = 200h , length = 200h /* data memory */
}
Ranges written outside a PAGE heading default to PAGE 0.
SECTIONS says how input sections are gathered into output sections, where those output sections go, and may rename them. Each entry begins with a name and a list of properties:
SECTIONS
{
name : [ property , property , ... ]
}
The properties of an output section are:
- Load allocation:
load = allocation, or justallocation, or> allocation. - Run allocation:
run = allocation, orrun > allocation. - Input sections:
{ input_sections }. - Section type:
type = COPY,type = DSECT, ortype = NOLOAD. - Fill value:
fill = value, or a= valueafter the braces.
Each parameter is a keyword, an = or > sign, and a value that may be put in
parentheses.
| Parameter | Keyword | Example |
|---|---|---|
| Binding | an address | .text: load = 0x1000 |
| Named memory |
> name or an attribute set |
.text: load > ROM |
| Alignment | align |
.text: align = 0x80 |
| Blocking | block |
.text: block(0x80) |
| Page | PAGE |
.text: PAGE 0 |
For the usual (load) allocation you may drop the load keyword and write just
> ROM or > 0x1000. Parameters may be strung together, optionally in
parentheses for readability:
.text: > ROM align 16 PAGE 2
.text: load = (ROM align(16) PAGE (2))
Binding fixes a section at a 16-bit address; a bound section cannot also use alignment or named memory. Named memory places a section into a MEMORY range by name or by attribute set; within the range the linker fills from low addresses and avoids fragmentation, but you cannot choose the exact spot (use binding for that). Alignment starts a section on an n-word boundary (n a power of 2). Blocking is weaker: the section fits inside one n-word block, or else starts on a block boundary. Alignment and blocking cannot be combined.
An input-section list names the pieces that form an output section; they are concatenated in the order given. A file with no section list contributes all its sections. Input sections of a given name not mentioned anywhere are appended to the matching output section automatically.
SECTIONS
{
.text :
{
f1.obj(.text) /* .text from f1.obj */
f2.obj(sec1) /* sec1 from f2.obj */
f3.obj /* every section from f3.obj */
f4.obj(.text,sec2) /* .text and sec2 from f4.obj */
}
}
The bare form .text: is shorthand for .text: { *(.text) }, where *(.text)
means every not-yet-allocated .text input section from all input files.
SECTIONS
{
.text: load = ROM /* combine all .text into ROM */
.const: load = ROM run = 0800h /* load in ROM, relocate to run 0800h */
.bss: load = RAM /* uninitialized, placed in RAM */
vectors: load = 0h /* bind at address 0 */
{
t1.obj(.intvec1)
t2.obj(.intvec2)
endvec = .; /* symbol at end of the section */
}
.data: align = 16 /* placed anywhere, 16-word aligned */
}
A section has a load location (where a loader deposits its raw data) and a run location (the address all references to the section resolve to). Usually they are the same. To separate them, give both:
.fir: load = ROM, run = RAM
The application itself must copy the section from its load address to its run
address; the linker does not. Everything after load qualifies the load
address until run appears, after which everything qualifies the run address;
run may also come first. The two allocations are independent, so aligning one
does not affect the other:
.data: load = ROM, align = 32, run = RAM /* align applies to load */
.data: load = (ROM align 32), run = RAM /* identical */
Giving both load and run reserves space for the section twice; the two copies
cannot overlap each other or anything else (UNION is the way to overlay). An
uninitialized section (such as .bss) is never loaded: if you specify both
addresses the linker warns and ignores the load address, and a single address is
always treated as the run address.
A section's load address can be reached at runtime through a symbol defined with
the .label assembler directive; ordinary labels relocate to the run address,
.label symbols to the load address.
Both conserve memory, in opposite ways.
UNION allocates several sections at the same run address, so the union takes only as much room as its largest member. Its members stay independent sections. Union affects run allocation only; sections are never overlaid for loading, so an initialized member needs its own load address.
SECTIONS
{
.text: load = ROM
UNION: run = RAM
{
.bss1: { file1.obj(.bss) }
.bss2: { file2.obj(.bss) }
}
.bss3: run = RAM { globals.obj(.bss) }
}
For initialized union members, specify a load address per member:
UNION: run = RAM
{
.text1: load = ROM, { file1.obj(.text) }
.text2: load = ROM, { file2.obj(.text) }
}
GROUP forces several output sections to be allocated one after another in memory. The group as a whole can be bound, aligned, or placed in named memory like a single section:
SECTIONS
{
.text
.bss
GROUP 1000h : /* .data at 1000h, term_rec right after */
{
.data
term_rec
}
}
A group may also have one run address with a separate load address for each initialized member:
GROUP run = ONCHIP
{
.text : load = ROM1
.data : load = ROM2
.bss /* uninitialized, no load address */
}
Pages are a distinct feature. Each page is a full, independent 16-bit address space defined in MEMORY, so two sections can occupy the same address on different pages. Without a PAGE qualifier, initialized sections default to PAGE 0 and uninitialized sections to PAGE 1. UNION overlays sections inside one address space; pages define several address spaces.
Without MEMORY and SECTIONS, the linker uses a device-specific default model (read from the device field in the object header). For a 'C5x device it is:
MEMORY
{
PAGE 0: PROG: origin = 800h length = 0F800h
PAGE 1: DATA: origin = 800h length = 0F800h
}
SECTIONS
{
.text: PAGE = 0
.data: PAGE = 0
.cinit: PAGE = 0 /* for -c and -cr */
.bss: PAGE = 1
}
All .text input sections concatenate into .text, all .data into .data
(both on PAGE 0); all .bss into .bss on PAGE 1. Initialized named sections
follow .data in program memory; uninitialized named sections follow .bss in
data memory.
An output section is formed either by a SECTIONS definition, or by combining all like-named input sections that SECTIONS does not mention. Once contents are known, the linker places sections to reduce fragmentation:
- Sections you bound to an address go there.
- Sections tied to a named range or to attributes go into the first free space in that area, honoring alignment.
- Everything else is placed in definition order (sections not in SECTIONS, in the order met) into the first free space, honoring alignment.
If you give no PAGE for a section it goes on PAGE 0, even when PAGE 0 is full and another page has room; to use another page name it explicitly.
A type is placed in parentheses after the section definition:
SECTIONS
{
sec1 2000h (DSECT) : {f1.obj}
sec2 4000h (COPY) : {f2.obj}
sec3 6000h (NOLOAD) : {f3.obj}
}
| Type | Effect |
|---|---|
DSECT |
A dummy section. It gets no memory and is absent from the map, may overlay other sections and unconfigured memory, and its contents, relocation, and line-number information are not written out. Its global symbols still relocate as if it had been loaded at its given address and may be referenced from elsewhere; undefined externals in it still trigger library searches. |
COPY |
Like DSECT, but its contents and associated information are written to the output module. The C compiler's .cinit table uses this under the RAM model. |
NOLOAD |
The linker allocates space for it and lists it in the map, but its contents, relocation, and line-number information are not written to the output. |
Assignment statements define global symbols and give them allocation-dependent values. They are processed after allocation, so a symbol used in an expression holds its final output address. Each statement ends with a semicolon:
| Form | Effect |
|---|---|
symbol = expression; |
assign |
symbol += expression; |
add to |
symbol -= expression; |
subtract from |
symbol *= expression; |
multiply |
symbol /= expression; |
divide |
If the symbol is not already external, the linker creates and enters it. The
special symbol . is the current value of the section program counter during
allocation; it may be used only in assignments inside a SECTIONS directive, and
it refers to the current run address.
SECTIONS
{
.text: {}
.data: { Dstart = .; } /* Dstart = start of .data */
.bss: {}
}
Expression rules: operands are global symbols, constants, and the C operators
below; all arithmetic is 32-bit; constants follow the assembler's rules
(decimal by default, H/h hex, Q/q octal, C prefixes 0 octal and 0x
hex, hex must start with a digit, no binary); a symbol contributes only its
address, with no type checking; an expression is relocatable if it contains any
relocatable symbol, otherwise absolute.
| Precedence | Operators |
|---|---|
| 1 (highest) |
! ~ - (unary) |
| 2 |
* / %
|
| 3 |
+ -
|
| 4 |
>> <<
|
| 5 |
== != > < <= >=
|
| 6 | & |
| 7 | ` |
| 8 | && |
| 9 | ` |
| 10 (lowest) |
= += -= *= /=
|
An extra align operator aligns the SPC on the next n-word boundary (n a power
of 2), usable only where . is, that is inside SECTIONS:
. = align(16);
The linker defines these symbols automatically (reachable from assembly with
.global): .text and etext (start and end of .text), .data and edata
(start and end of .data), .bss and end (start and end of .bss). With
-c or -cr it also defines __STACK_SIZE and __MEMORY_SIZE.
An output section holds either raw data for all of it or none at all. A hole is empty space the linker is forced to leave inside an initialized output section; because the section has raw data, the hole must be given raw data too. Holes exist only inside output sections; the gaps between output sections are not holes and cannot be filled.
Create a hole by moving the SPC forward with an assignment to .:
SECTIONS
{
outsect:
{
file1.obj(.text)
. += 100h; /* 256-word hole */
file2.obj(.text)
. = align(16); /* hole to the next 16-word boundary */
file3.obj(.text)
}
}
Assignments to . are relative to the start of the section (as if it began at
address 0, even when bound). Decrementing . is illegal; the usual operators
are += and align. A hole also appears when an uninitialized section is
combined with an initialized one: the uninitialized part becomes a hole and gets
raw data. Several uninitialized sections combined together stay uninitialized.
The linker fills a hole with a replicated 16-bit value, chosen in this order:
- A value written after an uninitialized member that became a hole, as
file2.obj(.bss) = 0FFh. - A
fill = valuegiven for the whole output section after its name. - The
-fvalue from the command line. - Zero.
You can also force an uninitialized section to become raw data by giving it a
fill value, for example .bss: fill = 1234h. Filling a large section or hole
makes the output file large, since raw data is then generated for all of it.
Every filled hole is noted in the link map with the value used.
A linked output file can be linked again with more modules, which lets a large application be built in parts. Guidelines:
- Link intermediate files with
-rso they keep relocation information. - Do not use
-son intermediate files; symbolic information must survive. - Keep intermediate steps to forming output sections only. Do all allocation, binding, and MEMORY work in the final link.
- Use
-hon an intermediate file to make its like-named globals static so they do not clash in the final link. - When linking C code, use
-cor-cronly in the final step, since each use makes the linker try to create an entry point.
c5xlnk -r -o tempout1 file1.com
c5xlnk -r -o tempout2 file2.com
c5xlnk -m final.map -o final.out tempout1.out tempout2.out
An object library is an archive of complete object files. When a library is
given as input, the linker pulls in only those members that resolve currently
undefined symbols. Order matters: a library satisfies only references that are
undefined when it is reached, so a library may need to be listed more than once,
or -x used to reread libraries until nothing more resolves. -u symbol
injects an undefined symbol to force a defining member in.
The search path for a -l library is: directories from -i options (which
must precede -l), then C_DIR, then A_DIR if C_DIR is unset, then the
current directory.
-e SETUP /* program entry point */
-o demo.out /* output file */
-m demo.map /* map file */
demo.obj
fft.obj
tables.obj
MEMORY
{
PAGE 0: ROM: origin = 00000h , length = 01000h
RAM_B0: origin = 0FF00h , length = 0100h
PAGE 1: IO: origin = 00000h , length = 06h
RAM_B2: origin = 00060h , length = 020h
RAM_B1: origin = 00300h , length = 0100h
RAM: origin = 00400h , length = 0FC00h
}
SECTIONS
{
.text: load = ROM, page = 0 /* code into ROM */
int_vecs: load = 0, page = 0 /* vectors bound at 0 */
.data: fill = 07A1Ch, load = RAM_B0, page = 0
{
tables.obj(.data)
fft.obj(.data)
. = 100h; /* hole to the end of block */
} /* filled with 7A1Ch */
fftvars: load = RAM_B2, page = 1 /* new section in B2 */
{
fft.obj(.bss)
}
.bss: load = RAM_B1, page = 1, fill = 0FFFFh
}
Running c5xlnk demo.cmd links the three objects into demo.out and writes
demo.map. The map lists the memory configuration keyed by page, a section
allocation map (output sections with their page, origin, length, and
contributing input sections, with any --HOLE-- and its fill value marked), and
the global symbols sorted both by name and by address.
c5xlnk is this toolchain's implementation of the linker command language
above. It parses the MEMORY directive (named regions with origin/length/
attributes) and the SECTIONS directive (output-section specs, input-section
lists, load and run allocation including the > region form, align, block, and
fill). It supports PAGE 0 and PAGE 1 as independent address spaces, GROUP for
contiguous allocation and UNION for shared run addresses, separate load and run
addresses (load != run), and the special section types NOLOAD, COPY, and DSECT.
It performs link-time symbol assignment, including the . SPC symbol and the
align operator, and the automatic symbols (.text/etext, .data/edata,
.bss/end). It handles archive libraries with the -l, -i, -u, and -x
options and the documented search order. Its output is a COFF .out module,
and with -m it writes the link map.
MIT - (c) 2026 Grzegorz Worona [email protected]. See LICENSE.