ld(1)
ld [opts] files...
-T <script> use <script> as linker script
--trace report each file the linker touches
--start-group archives --end-group
search archives repearepeatedly until no new
undefined references are created
(eg helpfull with list of static libraries)
Linker Script
output
sections are defined as follows (full description at output
section and input section).
section_name [vaddr] : [AT(paddr)] {
file_pattern (section_pattern)
}
The following gives an example of an output
section with two input
section rules.
.foo : {
abc.o (.foo)
*.o (.foo.*)
}
Example: virtual vs physical (load) address
Sometimes code is initially located at a different location as when being run. For example in embedded cases, where code may initially resides in a rom and startup code will copy a section with writable data into ram. Code accessing the writable data accesses the data in the ram.
In this case we need different addresses for the same data.
- The
virtual
or runtime address, this is the address used when the linker resolves accesses to the data. Hence, this is the address the data will have when the code is running. - The
physical
or load address, this is the address the data is stored at initially. Startup code typically copies the initial values from thephysical
to thevirtual
address.
The following shows an example linker script which uses virtual and physical addresses. The full source files can be found here.
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_entry)
SECTIONS {
/* Set the initial location counter (vaddr) */
. = 0x00800000;
/* Create .text output section at current vaddr */
.text : {
*(.text*)
}
ASSERT(. == 0x00800000 + SIZEOF(.text), "inc loc counter automatically")
/* Create .data section at location counter aligned to the next 0x100 (vaddr) */
/* Set the load address to 0x00100000 (paddr) */
.data ALIGN(0x100) : AT(0x00100000) {
HIDDEN(_data_vaddr = .);
HIDDEN(_data_paddr = LOADADDR(.data));
*(.data*)
}
/* Create .rodata with explicit vaddr */
/* Re-adjust the paddr location counter */
.rodata 0x00804000 : AT(ADDR(.rodata)) {
*(.rodata*)
}
ASSERT(. == 0x00804000 + SIZEOF(.rodata), "inc loc counter automatically")
.stack ALIGN (0x1000) : {
. += 0x1000;
HIDDEN(_stack_top = .);
}
/DISCARD/ : {
*(.*)
}
}
/* Some example assertions */
ASSERT(ADDR(.data) != LOADADDR(.data), "DATA vaddr and paddr must be different")
ASSERT(SIZEOF(.stack) == 0x1000, "STACK section must be 0x1000")
We can use the following assembly snippet to explore the linker script.
.section .text, "ax", @progbits
.global _entry
_entry:
mov $_stack_top, %rsp
mov $asm_array, %rax
mov (asm_len), %eax
hlt
jmp _entry
.section .data.asm, "aw", @progbits
asm_array:
.4byte 0xa
.4byte 0xb
.4byte 0xc
.4byte 0xd
.rept 4
.4byte 0xff
.endr
.section .rodata.asm, "a", @progbits
asm_len:
.4byte 8
gcc -c data.S && ld -o link-nomem -T link-nomem.ld data.o
The elf load segments show the difference in physical and virtual address
for the segment containing the .data
section.
> readelf -W -l link-nomem
# There are 4 program headers, starting at offset 64
#
# Program Headers:
# Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
# LOAD 0x001100 0x0000000000800100 0x0000000000100000 0x000020 0x000020 RW 0x1000
# LOAD 0x002000 0x0000000000800000 0x0000000000800000 0x000018 0x000018 R E 0x1000
# LOAD 0x003000 0x0000000000804000 0x0000000000804000 0x000004 0x000004 R 0x1000
# LOAD 0x000000 0x0000000000805000 0x0000000000805000 0x000000 0x001000 RW 0x1000
#
# Section to Segment mapping:
# Segment Sections...
# 00 .data
# 01 .text
# 02 .rodata
# 03 .stack
Startup code could copy data from _data_paddr
to _data_vaddr
.
> nm link-nomem
# 0000000000800100 d asm_array
# 0000000000804000 r asm_len
# 0000000000100000 a _data_paddr
# 0000000000800100 d _data_vaddr
# 0000000000800000 T _entry
# 0000000000806000 b _stack_top
The linker resolves symbols to their virtual address, this can be seen by the
access to the asm_array
variable.
> objdump -d link-nomem
# Disassembly of section .text:
#
# 0000000000800000 <_entry>:
# 800000: 48 c7 c4 00 60 80 00 mov $0x806000,%rsp
# 800007: 48 c7 c0 00 01 80 00 mov $0x800100,%rax ;; mov $asm_array, %rax
# 80000e: 8b 04 25 00 40 80 00 mov 0x804000,%eax
# 800015: f4 hlt
# 800016: eb e8 jmp 800000 <_entry>
The following linker script shows an example with the MEMORY
command.
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_entry)
MEMORY {
ROM : ORIGIN = 0x00100000, LENGTH = 0x4000
RAM : ORIGIN = 0x00800000, LENGTH = 0x4000
}
SECTIONS {
/* Create .text output section at ROM (vaddr) */
.text : {
*(.text*)
} > ROM
ASSERT(. == ORIGIN(ROM) + SIZEOF(.text), "inc loc counter automatically")
/* Create .data output section at RAM (vaddr) */
/* Set load addr to ROM, right after .text (paddr) */
.data : {
HIDDEN(_data_vaddr = .);
HIDDEN(_data_paddr = LOADADDR(.data));
*(.data*)
} > RAM AT > ROM
/* Append .rodata output section at ROM (vaddr) */
.rodata : {
*(.rodata*)
} > ROM
/* Append .stack output section at RAM (vaddr) aligned up to next 0x1000 */
.stack : ALIGN (0x1000) {
. += 0x1000;
HIDDEN(_stack_top = .);
} > RAM
/DISCARD/ : {
*(.*)
}
}
/* Some example assertions */
ASSERT(ADDR(.data) != LOADADDR(.data), "DATA vaddr and paddr must be different")
ASSERT(ADDR(.rodata) == LOADADDR(.rodata), "RODATA vaddr and paddr must be euqal")
ASSERT(ADDR(.stack) == ORIGIN(RAM) + 0x1000, "STACK section must aligned to 0x1000")
ASSERT(SIZEOF(.stack) == 0x1000, "STACK section must be 0x1000")