fix(linker): resolve absolute 32-bit relocations in static mode

A static FreeBSD link with --debug-opdf failed with 'absolute 32-bit
relocation unsupported in static mode': the OPDF emitter writes a
32-bit section-offset reference (.long label, R_X86_64_32) into the
.opdf section, and the internal linker's static mode rejected all
abs32 forms.  The guard was over-conservative - in a non-PIE ET_EXEC
linked at base 0x400000 every mapped address has a final link-time
value that fits in 32 bits, so the relocation is directly resolvable
(this is what GNU ld does, erroring only on overflow).

Static mode now patches S+A with GNU-ld-style range checks:
R_X86_64_32 must fit zero-extended, R_X86_64_32S sign-extended.
Dynamic/PIE behaviour is unchanged (the patched value remains the
unslid link-time address; the OPDF reader applies the run-time slide).

Linux never hit this because Linux binaries link in PIE/dynamic mode;
the bug only surfaced on the first FreeBSD-hosted pdr smoke test.

Guard: TestReloc_Abs32_ResolvesInStaticMode links a .long target data
reference statically and asserts the slot is patched with the symbol's
absolute address.

Verified: all four fixpoints green (FIXPOINT_OK, NATIVE_FIXPOINT_OK,
NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK); full suite OK (4035) under
both the QBE-fixpoint-built and native-built test runners; cross-
compiled freebsd-x86_64 --debug-opdf hello links and carries a
correctly patched .opdf section.
This commit is contained in:
Graeme Geldenhuys 2026-07-02 17:44:49 +01:00
parent 5e9ec4eeec
commit 8d1130852f
2 changed files with 69 additions and 6 deletions

View file

@ -1669,9 +1669,10 @@ begin
raise ELinker.Create('undefined reference to `' + Sym.Name + ''''); raise ELinker.Create('undefined reference to `' + Sym.Name + '''');
end; end;
{ Patch the merged section bytes for every relocation. In static { Patch the merged section bytes for every relocation. Static mode
mode (Phase B) only PC-relative forms are supported; in dynamic mode (Phase B) supports PC-relative and absolute forms (every symbol has a
(Phase C) absolute, GOT-relative and TLS relocations are handled. } final link-time address in a non-PIE ET_EXEC); dynamic mode (Phase C)
additionally handles GOT-relative and emits RELATIVE fixups. }
procedure TLinker.ApplyRelocations; procedure TLinker.ApplyRelocations;
var var
Oi, Ri: Integer; Oi, Ri: Integer;
@ -1744,11 +1745,29 @@ begin
R_X86_64_32, R_X86_64_32S: R_X86_64_32, R_X86_64_32S:
begin begin
if not FDynamic then
raise ELinker.Create(Ctx
+ ': absolute 32-bit relocation unsupported in static mode');
S := Self.ResolveSymbolAddr(Obj, Rel.SymIndex, Ctx); S := Self.ResolveSymbolAddr(Obj, Rel.SymIndex, Ctx);
Val := S + Rel.Addend; Val := S + Rel.Addend;
if M.ShType = SHT_NOBITS then
raise ELinker.Create(Ctx
+ ': relocation into a NOBITS section');
{ Resolvable in BOTH modes: static ET_EXEC links at a fixed base
(0x400000) where every mapped address fits in 32 bits, and in
PIE mode the patched value is the unslid link-time address
(the OPDF reader applies the run-time slide itself; code never
uses abs32 in PIE objects). Range-check like GNU ld does:
R_X86_64_32 must fit zero-extended, R_X86_64_32S sign-extended. }
if Rel.RelocType = R_X86_64_32 then
begin
if (Val < 0) or (Val > $FFFFFFFF) then
raise ELinker.Create(Ctx
+ ': R_X86_64_32 overflow against ' + Sym.Name);
end
else
begin
if (Val < -2147483648) or (Val > 2147483647) then
raise ELinker.Create(Ctx
+ ': R_X86_64_32S overflow against ' + Sym.Name);
end;
LkPatch32(M.Data, PFileOff, Val and $FFFFFFFF); LkPatch32(M.Data, PFileOff, Val and $FFFFFFFF);
end; end;

View file

@ -59,6 +59,7 @@ type
{ Static relocations } { Static relocations }
procedure TestReloc_PC32CrossObjectCall; procedure TestReloc_PC32CrossObjectCall;
procedure TestReloc_Quad64_ResolvesToAbsoluteAddr; procedure TestReloc_Quad64_ResolvesToAbsoluteAddr;
procedure TestReloc_Abs32_ResolvesInStaticMode;
{ Executable structure } { Executable structure }
procedure TestExe_ElfHeaderIsExec; procedure TestExe_ElfHeaderIsExec;
procedure TestExe_EntryPointMatchesSymbol; procedure TestExe_EntryPointMatchesSymbol;
@ -840,6 +841,49 @@ begin
end; end;
end; end;
procedure TLinkerTests.TestReloc_Abs32_ResolvesInStaticMode;
var
Lk: TLinker;
Bytes: string;
Addr, Got: Int64;
I, Slot: Integer;
begin
{ An absolute 32-bit data reference (.long target, R_X86_64_32) is
resolvable at link time in a non-PIE ET_EXEC: the image is linked at a
fixed base (0x400000) and every mapped address fits in 32 bits. The
OPDF emitter uses exactly this form (.long label) inside the .opdf
section, so a static FreeBSD link with debug info must accept it.
A marker word (0xDEADBEEF as decimal) precedes the slot so the test can
locate the patched bytes in the flat image. }
Lk := LinkObjs(
['.data' + LineEnding +
'marker:' + LineEnding + '.long 3735928559' + LineEnding +
'ptr32:' + LineEnding + '.long target' + LineEnding +
'.text' + LineEnding + '.globl target' + LineEnding + 'target:' +
LineEnding + 'ret' + LineEnding +
'.globl _start' + LineEnding + '_start:' + LineEnding + 'ret' + LineEnding],
'_start', Bytes);
try
Addr := Lk.AddrOfSymbol('target');
AssertTrue('target resolved above base', Addr > $400000);
{ Find the marker; the patched abs32 slot follows it. }
Slot := -1;
for I := 0 to Length(Bytes) - 9 do
if (OrdAt(Bytes, I) = $EF) and (OrdAt(Bytes, I + 1) = $BE) and
(OrdAt(Bytes, I + 2) = $AD) and (OrdAt(Bytes, I + 3) = $DE) then
begin
Slot := I + 4;
Break;
end;
AssertTrue('marker found in image', Slot >= 0);
Got := OrdAt(Bytes, Slot) or (OrdAt(Bytes, Slot + 1) shl 8) or
(OrdAt(Bytes, Slot + 2) shl 16) or (OrdAt(Bytes, Slot + 3) shl 24);
AssertEquals('abs32 slot patched with target address', Addr, Got);
finally
Lk.Free();
end;
end;
procedure TLinkerTests.TestExe_ElfHeaderIsExec; procedure TLinkerTests.TestExe_ElfHeaderIsExec;
var var
Lk: TLinker; Lk: TLinker;