Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 9 – Interfacing with C, C++ and Rust

Linking, relocations and PIE in depth

Read the relocations an assembler leaves, predict the bytes ld.lld writes into each field, and see why a PIE refuses an absolute address on x86-64.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 20 minutes free
Free: A relocation tells the linker which bytes to patch, and how · An absolute address cannot be linked into a PIE · Practise. With a pass: 3 in-depth parts and 3 interview answers, about 14 minutes.

What you will learn

  • Read relocation entries and predict the four bytes the linker writes into a PC-relative field
  • Explain why an absolute R_X86_64_32S address fails to link into a position-independent executable
  • Compute the fields of an adrp and add pair and of an auipc and addi pair from their relocations
  • Compare how a static executable, a static PIE and a dynamic PIE start
  • Predict when RISC-V linker relaxation turns a two-instruction call into one jal

Before you start

What you’ll learn · Before you start

What you will learn

  • Read relocation entries and predict the four bytes the linker writes into a PC-relative field
  • Explain why an absolute R_X86_64_32S address fails to link into a position-independent executable
  • Compute the fields of an adrp and add pair and of an auipc and addi pair from their relocations
  • Compare how a static executable, a static PIE and a dynamic PIE start
  • Predict when RISC-V linker relaxation turns a two-instruction call into one jal

Before you start

On this page

An assembler that reads lea rsi, [msg] cannot know where msg will be loaded, so it writes four zero bytes into the instruction and a relocation: An entry beside a section's bytes that says which bytes the linker must patch, with which symbol's address, under which formula. Needed whenever the assembler cannot know a final address yet. More in “From source to executable: objects and linking” beside them: a record that names those bytes, a symbol and a formula for the linker to apply. Linking, relocations and PIE in depth come down to one question: which program writes each address into those bytes, and when?

A position-independent executable: An executable whose code reaches other code and data through PC-relative addressing instead of fixed addresses, so it runs correctly at any load address. This is what lets the loader randomise its placement under ASLR. More in “Memory and a process's address space” can be loaded anywhere, so the linker can finish a field only when it holds a distance; an absolute address waits for the loader or is refused. Below, one NASM program’s records are read, one field is worked out byte by byte, and the PIE error is recorded.

A relocation tells the linker which bytes to patch, and how

rel.asm hands greet, a function in a shared library, the address of a 3-byte message. With default rel, [msg] is a distance from rip. The session assembles it, prints the object’s relocations, links a PIE against libgreet.so and disassembles the result:

rel.asm: PC-relative code with default rel ASM · x86_64/rel.asm
; rel.asm: print "hi" through greet, then exit (NASM, ELF64, x86-64 Linux).
; The PC-relative version: every address is a distance from the instruction that uses it.
default rel                             ; [msg] means [rip + distance to msg]
global _start
extern greet                            ; defined in greet.asm: libgreet.so or greet.o

section .data
msg:    db "hi", 10
len     equ $ - msg                     ; 3 bytes

section .text
_start:
        lea     rsi, [msg]              ; address of msg, measured from rip
        mov     edx, len
        call    greet wrt ..plt         ; through the procedure linkage table
        mov     eax, 60                 ; exit(0)
        xor     edi, edi
        syscall

section .note.GNU-stack noalloc noexec nowrite progbits
Two relocations in rel.o, and the bytes ld.lld wrote Python · records_session.py
"""Read the two relocations of rel.o, then link it into a PIE and read the bytes ld.lld wrote into each field.

greet.asm becomes libgreet.so, the shared library the PIE is linked against. Everything here is assembled, linked
and read back; starting the PIE takes an x86-64 Linux system, whose dynamic loader maps libgreet.so beside it.
"""

import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run(
    """
# The object: two relocations, and four zero bytes at each offset they name.
nasm -f elf64 -o x86_64/rel.o x86_64/rel.asm
llvm-readelf -r x86_64/rel.o
llvm-objdump -d -r -M intel x86_64/rel.o
# Link a PIE against a shared library that defines greet, then look up where msg and _start ended up.
nasm -f elf64 -o x86_64/greet.o x86_64/greet.asm
ld.lld -shared -soname libgreet.so -o x86_64/libgreet.so x86_64/greet.o
ld.lld -pie --dynamic-linker /lib64/ld-linux-x86-64.so.2 -o x86_64/rel_pie x86_64/rel.o x86_64/libgreet.so
llvm-readelf -s x86_64/rel_pie | grep -E ' (msg|_start)$'
# The same instructions, with their fields filled in.
llvm-objdump -d -M intel --disassemble-symbols=_start x86_64/rel_pie
"""
)

Output

# The object: two relocations, and four zero bytes at each offset they name.
$ nasm -f elf64 -o x86_64/rel.o x86_64/rel.asm
$ llvm-readelf -r x86_64/rel.o

Relocation section '.rela.text' at offset 0x3d0 contains 2 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000000000003  0000000200000002 R_X86_64_PC32          0000000000000000 .data - 4
000000000000000d  0000000700000004 R_X86_64_PLT32         0000000000000000 greet - 4
$ llvm-objdump -d -r -M intel x86_64/rel.o

x86_64/rel.o:	file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <_start>:
       0: 48 8d 35 00 00 00 00         	lea	rsi, [rip]              # 0x7 <_start+0x7>
		0000000000000003:  R_X86_64_PC32	.data-0x4
       7: ba 03 00 00 00               	mov	edx, 0x3
       c: e8 00 00 00 00               	call	0x11 <_start+0x11>
		000000000000000d:  R_X86_64_PLT32	greet-0x4
      11: b8 3c 00 00 00               	mov	eax, 0x3c
      16: 31 ff                        	xor	edi, edi
      18: 0f 05                        	syscall
# Link a PIE against a shared library that defines greet, then look up where msg and _start ended up.
$ nasm -f elf64 -o x86_64/greet.o x86_64/greet.asm
$ ld.lld -shared -soname libgreet.so -o x86_64/libgreet.so x86_64/greet.o
$ ld.lld -pie --dynamic-linker /lib64/ld-linux-x86-64.so.2 -o x86_64/rel_pie x86_64/rel.o x86_64/libgreet.so
$ llvm-readelf -s x86_64/rel_pie | grep -E ' (msg|_start)$'
     2: 0000000000003410     0 NOTYPE  LOCAL  DEFAULT    11 msg
     6: 00000000000012f0     0 NOTYPE  GLOBAL DEFAULT     7 _start
# The same instructions, with their fields filled in.
$ llvm-objdump -d -M intel --disassemble-symbols=_start x86_64/rel_pie

x86_64/rel_pie:	file format elf64-x86-64

Disassembly of section .text:

00000000000012f0 <_start>:
    12f0: 48 8d 35 19 21 00 00         	lea	rsi, [rip + 0x2119]     # 0x3410 <msg>
    12f7: ba 03 00 00 00               	mov	edx, 0x3
    12fc: e8 1f 00 00 00               	call	0x1320 <greet@plt>
    1301: b8 3c 00 00 00               	mov	eax, 0x3c
    1306: 31 ff                        	xor	edi, edi
    1308: 0f 05                        	syscall

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 records_session.py

Each record has an offset into .text, a type, and a symbol with an addend: A constant stored in a relocation entry that the linker adds to the symbol's address when it computes the value to write. The processor ABIs call it A in their formulas, as in S + A - P.. The relocation type: The part of a relocation entry that names the formula the linker applies and the size of the field it writes, such as R_X86_64_PC32 for a 32-bit distance or R_X86_64_32S for a 32-bit absolute address. picks the formula, which the x86-64 psABI writes with S for the symbol’s value, A for the addend and P for the place being patched. R_X86_64_PC32 is S + A - P, the distance from the field to its target. R_X86_64_PLT32 does the same subtraction towards the function’s PLT entry. On AArch64 and RISC-V, one address takes two records: the high part for adrp or auipc, the low 12 bits for add or addi. In rel.o both fields hold zeros; in rel_pie the lea’s field holds 19 21 00 00. Step through the arithmetic that produced it:

Interactive · step by step The lea's PC32 field, from zeros to 19 21 00 00 Step through how ld.lld fills the four bytes of the lea in rel_pie. The left column is the object and the formula; the right column is where the link put things.
The lea's PC32 field, from zeros to 19 21 00 00, step 7 of 7. Showing lea in rel.o, Relocation record, S + A - P (= 0x2119), _start in rel_pie (P = 0x12f3), msg in rel_pie (S = 0x3410), The patched field (19 21 00 00). In focus: The patched field, msg in rel_pie.names the fieldPSwriteslea in rel.o00 00 00 00Relocation recordPC32 .data - 4S + A - P= 0x2119_start in rel_pieP = 0x12f3msg in rel_pieS = 0x3410The patched field19 21 00 00

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Four zero bytes. The assembler cannot know where msg will be, so the lea it writes ends in a 4-byte field of zeros, at offset 3 of .text, and a record beside it names that offset.
  2. What the record says. The type, R_X86_64_PC32, picks the formula S + A - P. The symbol is .data, whose first byte is msg, and the addend A is -4.
  3. The linker places _start. ld.lld puts _start at 0x12f0 in rel_pie. The field starts 3 bytes in, so P, the place being patched, is 0x12f3.
  4. And places msg. .data, and with it msg, lands at 0x3410. That is S, the value of the symbol the record names.
  5. The formula. S + A - P = 0x3410 - 4 - 0x12f3 = 0x2119. The result fits 32 signed bits, so the linker can write it.
  6. Written lowest byte first. x86-64 stores the lowest byte first, so 0x2119 goes into the field as 19 21 00 00: the bytes llvm-objdump prints in the lea of rel_pie.
  7. At run time. The CPU adds the field to rip, the address of the next instruction: 0x12f7 + 0x2119 = 0x3410, which is msg. Loaded at any other address, the code and msg move together, so the same four bytes still reach msg.

Key idea

A PC32 field holds a distance, 0x2119 here, which stays right wherever the file is loaded because the code and msg move together, so the linker finishes it and leaves nothing for the loader.

Checkpoint

Check: Relocation records and the PC32 patch

  1. Question 1 of 2 In another PIE, a lea's PC32 field is at P = 0x1203, the record says msg - 4, and msg is at 0x4000. Which four bytes does the linker write?

    Choose one answer.

    A hint

    Work out S + A - P first, then write it lowest byte first.

    Show the answer: Question 1 of 2

    Answer: f9 2d 00 00

    PC32 is S + A - P: 0x4000 + (-4) - 0x1203 = 0x2df9. The linker writes it as four bytes, lowest first: f9 2d 00 00. At run time the CPU adds it to the address of the next instruction, P + 4 = 0x1207, and reaches 0x4000.

  2. Question 2 of 2 llvm-readelf -r prints the lea's record in rel.o as 0000000000000003 R_X86_64_PC32 .data - 4. What is the 3?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The offset in .text of the field to patch

    A record gives the place (the offset of the field), the type (the formula) and the symbol with its addend. In rel.o the lea's field is bytes 3 to 6, after the opcode bytes 48 8d 35, so the record's offset is 3.

An absolute address cannot be linked into a PIE

abs.asm changes one line, default abs, so the assembler stores [msg] as an absolute address in a 32-bit field. ld.lld refuses that object as a PIE and accepts it for an executable linked at a fixed address:

abs.asm: the same program with default abs ASM · x86_64/abs.asm
; abs.asm: the same program as rel.asm, with one line changed (NASM, ELF64, x86-64 Linux).
; default abs makes [msg] an absolute address, stored in the instruction as 32 signed bits.
default abs
global _start
extern greet

section .data
msg:    db "hi", 10
len     equ $ - msg

section .text
_start:
        lea     rsi, [msg]              ; the address of msg itself, as a 32-bit field
        mov     edx, len
        call    greet wrt ..plt
        mov     eax, 60
        xor     edi, edi
        syscall

section .note.GNU-stack noalloc noexec nowrite progbits
abs.o refused as a PIE, then linked at a fixed address Python · pie_link_session.py
"""Assemble abs.asm, read its relocations, and link it two ways: as a PIE, which ld.lld refuses, and as a static
executable for one fixed address, which it accepts.

The sed filter keeps the instructions of _start and drops the padding bytes the linker placed after them.
"""

import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

session.run(
    """
# The absolute object: the lea's field is now R_X86_64_32S, an address in 32 signed bits.
nasm -f elf64 -o x86_64/abs.o x86_64/abs.asm
llvm-readelf -r x86_64/abs.o
# As a PIE, which may be loaded at any address, the link fails.
nasm -f elf64 -o x86_64/greet.o x86_64/greet.asm
ld.lld -shared -soname libgreet.so -o x86_64/libgreet.so x86_64/greet.o
ld.lld -pie --dynamic-linker /lib64/ld-linux-x86-64.so.2 -o x86_64/abs_pie x86_64/abs.o x86_64/libgreet.so
# Linked for one fixed address (static, not a PIE), the same object is accepted and the field holds msg itself.
ld.lld -o x86_64/abs_static x86_64/abs.o x86_64/greet.o
llvm-readelf -s x86_64/abs_static | grep -E ' msg$'
llvm-objdump -d -M intel --disassemble-symbols=_start x86_64/abs_static | sed -n '/<_start>:/,/syscall/p'
"""
)

Output

# The absolute object: the lea's field is now R_X86_64_32S, an address in 32 signed bits.
$ nasm -f elf64 -o x86_64/abs.o x86_64/abs.asm
$ llvm-readelf -r x86_64/abs.o

Relocation section '.rela.text' at offset 0x3d0 contains 2 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000000000004  000000020000000b R_X86_64_32S           0000000000000000 .data + 0
000000000000000e  0000000700000004 R_X86_64_PLT32         0000000000000000 greet - 4
# As a PIE, which may be loaded at any address, the link fails.
$ nasm -f elf64 -o x86_64/greet.o x86_64/greet.asm
$ ld.lld -shared -soname libgreet.so -o x86_64/libgreet.so x86_64/greet.o
$ ld.lld -pie --dynamic-linker /lib64/ld-linux-x86-64.so.2 -o x86_64/abs_pie x86_64/abs.o x86_64/libgreet.so
ld.lld: error: relocation R_X86_64_32S cannot be used against local symbol; recompile with -fPIC
>>> defined in x86_64/abs.o
>>> referenced by x86_64/abs.asm
>>>               x86_64/abs.o:(.text+0x4)
[exit status 1]
# Linked for one fixed address (static, not a PIE), the same object is accepted and the field holds msg itself.
$ ld.lld -o x86_64/abs_static x86_64/abs.o x86_64/greet.o
$ llvm-readelf -s x86_64/abs_static | grep -E ' msg$'
     2: 0000000000202190     0 NOTYPE  LOCAL  DEFAULT     2 msg
$ llvm-objdump -d -M intel --disassemble-symbols=_start x86_64/abs_static | sed -n '/<_start>:/,/syscall/p'
0000000000201160 <_start>:
  201160: 48 8d 34 25 90 21 20 00      	lea	rsi, [0x202190]
  201168: ba 03 00 00 00               	mov	edx, 0x3
  20116d: e8 0e 00 00 00               	call	0x201180 <greet>
  201172: b8 3c 00 00 00               	mov	eax, 0x3c
  201177: 31 ff                        	xor	edi, edi
  201179: 0f 05                        	syscall

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pie_link_session.py

R_X86_64_32S is S + A, the address itself, which the linker must check sign-extends from 32 bits. Linked without -pie, abs_static puts msg at 0x202190, which fits: the field holds 90 21 20 00. A PIE has no address when it is linked, because the loader picks one at start, so S is unknown, and 32 bits cannot reach all of a 64-bit address space. The psABI’s small PIC model says “all addresses have to be relative to the instruction pointer”. In NASM the fix is default rel, or [rel msg] for one operand. The three records of the two objects, side by side:

Record Formula The field holds In a PIE
R_X86_64_PC32 S + A - P a distance finished by the linker
R_X86_64_PLT32 L + A - P a distance to a PLT entry finished by the linker
R_X86_64_32S S + A an address in 32 bits refused

Looking at a PIE in depth, from its object file to its first instruction, turns up four more choices that decide where each record ends up:

  • Static or dynamic. static linking: Linking that copies the code of every function a program calls into the executable itself, from object files and static libraries, so that no shared library has to be found when the program starts. copies the code of every function a program calls into the executable, while a dynamic one names a program interpreter: The program a dynamically linked ELF executable names for the kernel to start first: the dynamic loader, which maps the shared libraries the executable needs before its _start runs. On x86-64 Linux it is /lib64/ld-linux-x86-64.so.2, the path that ld.lld's --dynamic-linker option names. that maps its shared libraries before _start runs.
  • PIE or fixed address. A fixed-address executable is finished by the linker, while a PIE leaves dynamic relocation: A relocation that a linker leaves in an executable or a shared library for the dynamic loader to apply at load time, because its value depends on where the file or a library ends up in memory. for every pointer whose value depends on the load address.
  • RISC-V relaxation. linker relaxation: A link-time rewrite that replaces a code sequence with a shorter one once the final addresses are known, such as a RISC-V auipc and jalr pair turned into one jal when the target is within about 1 MiB. An R_RISCV_RELAX relocation marks each place where it is allowed. More in “Calls, returns and the link register” lets the linker shorten code once the addresses are known, such as an auipc and jalr call that becomes one jal.
  • Mach-O. Apple’s format has relocation types of its own for adrp, add, bl and pointers, and dyld applies what the linker leaves for load time.

Checkpoint

Check: Absolute addresses and PIE links

  1. Question 1 of 2 ld.lld links abs.o into abs_static but refuses it with -pie. Why does the same R_X86_64_32S record work in one link and not the other?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: abs_static is linked for a known address, 0x202190 for msg, which fits 32 bits; a PIE gets its address only at load time

    R_X86_64_32S stores S + A itself. Linked at a fixed address, msg is at 0x202190 and the linker writes 90 21 20 00. A PIE's address is chosen by the loader at start, so there is no value to write at link time, and a 32-bit field cannot reach all of a 64-bit address space.

  2. Question 2 of 2 You must make abs.asm link as a PIE and may change one line of the source. Which change works, and why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Replace default abs with default rel, so [msg] becomes [rip + distance] and the record becomes PC32

    The fix is to stop storing an address in the code. default rel (or [rel msg] for one operand) makes the lea RIP-relative, the assembler emits R_X86_64_PC32 .data - 4, and the PIE link succeeds, as rel.asm shows.

Practise

Work on copies of rel.asm and abs.asm, and write each prediction down before you run the session again.

  1. Put the line times 256 db 0 just before msg: in rel.asm, so msg moves 256 bytes later. Predict the four bytes of the lea’s field in rel_pie; the programmer calculator does the hex arithmetic.
  2. In abs.asm, keep default abs and write lea rsi, [rel msg]. Predict the relocation type llvm-readelf -r prints for the lea, and whether the PIE link succeeds now.

Practice that adapts to you

Practise this lesson

7 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 What does an R_X86_64_PC32 relocation store in its 4-byte field?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: S + A - P: the distance from the field to the target, in 32 signed bits

    The x86-64 psABI defines R_X86_64_PC32 as S + A - P. The result is a distance, so the same bytes reach the target wherever the loader puts the file, as long as the code and the target move together.

  2. Question 2 of 7 A PC32 field must hold 0x1d0. Which bytes does llvm-objdump show at the end of the instruction?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: d0 01 00 00

    The field is a 32-bit little-endian value. 0x1d0 is 0x000001d0, written lowest byte first as d0 01 00 00, the way the session shows 0x2119 as 19 21 00 00.

  3. Question 3 of 7 rel_pie is loaded 0x10000 bytes higher in one run than in another. What does the lea's field hold in memory in the second run?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: 0x2119, as in the file: the code and msg moved together

    PC32 stores the distance from the field to msg. Loading the file at another address shifts the lea and msg by the same amount, so 0x2119 still leads from the next instruction to msg.

  4. Question 4 of 7 What does an R_X86_64_32S relocation store?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: S + A, an absolute address, in 32 bits that must sign-extend back to it

    R_X86_64_32S writes S + A into a 32-bit field that the CPU sign-extends to 64 bits, so the address must fit. In abs_static, msg at 0x202190 fits and the field holds 90 21 20 00.

  5. Question 5 of 7 abs.o is linked twice: ld.lld -o abs_static abs.o greet.o and ld.lld -pie --dynamic-linker /lib64/ld-linux-x86-64.so.2 -o abs_pie abs.o libgreet.so. Which links succeed?
    Show the answer: Question 5 of 7

    Answer: Only the first, the executable linked at a fixed address

    A fixed-address link knows every address, so S + A is a number it can write and check. A PIE gets its address only at load time, so ld.lld refuses the R_X86_64_32S record of the lea.

  6. Question 6 of 7 The PIE error ends with abs.o:(.text+0x4). Which instruction holds that byte?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: The lea: its 32-bit field starts at offset 4, after 48 8d 34 25

    Each record names a place. abs.o's lea is 8 bytes, 48 8d 34 25 and a 32-bit address, so its field starts at offset 4, the place the error message points at.

  7. Question 7 of 7 Which of these values in a PIE can the linker not finish, so that it is left for load time?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: The address of msg, stored in an 8-byte table in .data

    Only values whose numbers depend on where the file is loaded are left to the loader: stored addresses, such as a table of pointers. Distances inside the file and plain constants are finished at link time.

Applying eight relocations by hand, checked against the linker Pro: opens with a Pro, Premium or Ultimate pass

Every field a linker writes follows from three numbers, S, A and P, and the formula of its type. The lesson’s programs hold eight such fields on x86-64, AArch64 and RISC-V, and a short program checks each one against the bytes ld.lld wrote.

In this part:

  • Working out the call and the absolute field on x86-64, with numbers
  • Splitting an address into a page and an offset on AArch64
  • Rounding the RISC-V high part with 0x800, and the 4 KiB miss without it

3 runnable examples (Python, S) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

How static and dynamic executables start, read from their headers Pro: opens with a Pro, Premium or Ultimate pass

One object can become a static executable, a static PIE or a dynamic PIE, and each file tells the kernel something different about how to start it. Its header, its program headers and the relocations it keeps show who works before the first instruction.

In this part:

  • One object linked three ways, and what each file asks of the kernel
  • Relative relocations carry the load address into a pointer table
  • A pointer table in read-only data needs a text relocation
  • On macOS, every arm64 executable is a PIE that dyld starts

1 diagram · 7 runnable examples (ASM, Python, S) · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

RISC-V linker relaxation, before and after Pro: opens with a Pro, Premium or Ultimate pass

A RISC-V linker may rewrite the code it links as well as patch it: once the addresses are known, a call that the assembler wrote as two instructions can shrink to one. Two links of one object, with and without --no-relax, show the change and the relocations that allow it.

In this part:

  • A call that shrinks from eight bytes to four
  • The same rewriting behind the gp crash of static data

2 runnable examples (Python, S) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

What do R_X86_64_PC32 and R_X86_64_32S ask the linker to write, and why can only one of them go into a PIE?

PC32 asks for a distance: the symbol’s address plus the addend minus the address of the field, S + A - P, in 32 signed bits. 32S asks for the address itself, S + A, checked to sign-extend from 32 bits. A PIE’s code and data move together, so a distance holds at every load address and the linker can finish the field. An absolute address depends on where the loader puts the file, unknown at link time, so ld.lld refuses R_X86_64_32S and suggests -fPIC. In NASM, default rel turns [msg] into a RIP-relative operand and the record into PC32.

Three harder questions follow, each set in a small project of its own.

  1. Fresher to senior

    An object's .text has R_X86_64_PC32 counter - 4 at offset 0x15 and R_X86_64_PLT32 log_line - 4 at offset 0x22. In the linked PIE, .text starts at 0x1100, counter is at 0x4008 and log_line's PLT entry at 0x1180. Which bytes does the linker write into each field, and how do you check them?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A hand-written x86-64 routine keeps a table of eight string addresses in .rodata. The static test binary links, but the release build, linked with -pie, stops with eight "cannot be used against local symbol; recompile with -fPIC" errors, one per entry. A teammate proposes -z notext. What is wrong, and what would you do instead?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    A small x86-64 Linux tool written in assembly has its own _start and no C library. It must run in an empty container image and should load at a different address on each run. Would you link it as a static executable, a static PIE or a dynamic PIE, and what must its own code do at start?

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does a relocation entry name, and what decides the value written?

    A field to patch, a relocation type and a symbol with an addend. The type's formula, such as S + A - P, decides what the linker writes into the field.

  2. What does R_X86_64_PC32 ask the linker to write, and why is it safe in a PIE?

    S + A - P: the symbol's address plus the addend minus the field's own address, a 32-bit distance. Code and data move together in a PIE, so the distance stays right at any load address and the linker can finish the field.

  3. Why does a PIE link refuse R_X86_64_32S, and how does NASM avoid it?

    32S asks for the symbol's absolute address in 32 signed bits, which depends on where the loader puts the file, so ld.lld refuses it and suggests -fPIC. default rel makes NASM write a RIP-relative operand and a PC32 record instead.

  4. What is the addend of a relocation?

    A constant stored in the relocation that the linker adds to the symbol's address, the A of the formulas in the processor ABIs, as in S + A - P.

  5. ld.lld finished the PC32 field of a lea with the bytes 19 21 00 00. What value is that, and why could the linker finish it?

    The distance 0x2119, stored little-endian, which the formula S + A - P gave. The code and its data move together in a PIE, so that distance is final at link time and nothing is left for the loader to patch.

Key takeaways

  • A relocation names a field, a type and a symbol with an addend, and the type’s formula decides what the linker writes there.
  • R_X86_64_PC32 stores S + A - P, a distance that stays right at any load address, which is why ld.lld could finish the lea of rel_pie with 19 21 00 00.
  • R_X86_64_32S stores an address in 32 signed bits, so a PIE link refuses it, and default rel turns it into a PC32 record.
  • Static or dynamic linking, a PIE or a fixed address, RISC-V relaxation and Mach-O’s own types decide where the remaining records end up and who applies them.

Terms in this lesson

addend
A constant stored in a relocation entry that the linker adds to the symbol's address when it computes the value to write. The processor ABIs call it A in their formulas, as in S + A - P.
dynamic relocation
A relocation that a linker leaves in an executable or a shared library for the dynamic loader to apply at load time, because its value depends on where the file or a library ends up in memory.
lazy binding
Filling the GOT slot of a library function the first time the function is called, through its PLT entry, instead of when the program starts. The LD_BIND_NOW environment variable asks the dynamic loader to fill every slot at start.
program interpreter
The program a dynamically linked ELF executable names for the kernel to start first: the dynamic loader, which maps the shared libraries the executable needs before its _start runs. On x86-64 Linux it is /lib64/ld-linux-x86-64.so.2, the path that ld.lld's --dynamic-linker option names.
relocation type
The part of a relocation entry that names the formula the linker applies and the size of the field it writes, such as R_X86_64_PC32 for a 32-bit distance or R_X86_64_32S for a 32-bit absolute address.
static linking
Linking that copies the code of every function a program calls into the executable itself, from object files and static libraries, so that no shared library has to be found when the program starts.
text relocation
A relocation that patches a read-only part of a program, such as its code or its constant data, so that whatever applies it must write into pages that were meant to stay read-only. macOS arm64 links refuse them.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.