feat(rtl): direct-syscall kernel-leaf foundation + --static link mode (Linux, WIP)
First increment of the libc-free direct-syscall RTL (Linux as the proving ground for FreeBSD; docs/linux-syscall-migration.adoc to follow). The default build is unchanged — libc-backed dynamic ELF; everything here is behind the new --static flag. New kernel-leaf units (selected by EnsureRTLObjects when --static): - runtime.syscall.linux — raw `syscall` stubs DEFINING the bare POSIX names the RTL imports (open/read/write/lseek/close/fstat/stat/mkdir/rmdir/unlink/rename/ chdir/getpid/dup2/pipe/mmap/munmap/nanosleep/clock_gettime/exit/_exit). `write`/`exit` are Pascal-reserved, so their Pascal names are _sys_* and the asm body emits a bare label alias. 4th syscall arg moved %rcx->%r10. - runtime.cstub — freestanding memcpy/memset/memcmp/strlen (no Char type; PChar byte indexing). - runtime.start.static.linux — freestanding _start (argc/argv off %rsp, call main, exit_group) replacing the libc __libc_start_main entry. Wiring: --static -> TBackendOpts.Static; EnsureRTLObjects swaps runtime.start for runtime.start.static.linux and adds the syscall+cstub leaves; the native internal linker calls SetDynamic(False) for a non-PIE ET_EXEC. Internal linker static-mode fixes (these are correct in any ET_EXEC, not just PIE — they were wrongly gated to dynamic-only): - R_X86_64_64 against a defined symbol resolves to the symbol's absolute address (no dynamic reloc); only PIE/dynamic mode also emits the .rela.dyn RELATIVE. - R_X86_64_TPOFF32 (Local-Exec TLS) resolves at link time — it IS the static TLS model. Updated TestReloc_Quad64_* to assert the now-correct resolution. A --static link currently reaches symbol resolution and reports the remaining libc leaves to implement (mremap/getcwd/fork/execvp/waitpid/time/getenv/sysconf/ gmtime_r/localtime_r/timegm/__cxa_atexit/abort/mkstemp/system/pthread_mutex_*) — the next increments. Full suite green (3829); dynamic native/QBE unaffected.
This commit is contained in:
parent
93ac17bb62
commit
be0e519084
|
|
@ -236,6 +236,8 @@ begin
|
|||
Inc(I);
|
||||
AOpts.RTLSrcDir := ParamStr(I);
|
||||
end
|
||||
else if Arg = '--static' then
|
||||
AOpts.Static := True
|
||||
else if Arg = '--emit-ir' then
|
||||
AFront.EmitIR := True
|
||||
else if Arg = '--emit-asm' then
|
||||
|
|
|
|||
|
|
@ -92,6 +92,12 @@ type
|
|||
Overrides the binary/CWD-relative lookup, for
|
||||
a relocated/release binary whose RTL source
|
||||
lives elsewhere (empty = use default lookup) }
|
||||
Static: Boolean; { --static: link a freestanding, libc-free ELF
|
||||
(native internal linker only). The kernel
|
||||
leaf (runtime.syscall.<os> + runtime.cstub +
|
||||
runtime.start.static.<os>) replaces libc; the
|
||||
linker emits a non-PIE ET_EXEC with no
|
||||
PT_INTERP. docs/linux-syscall-migration.adoc }
|
||||
end;
|
||||
|
||||
TBackendDriver = class
|
||||
|
|
@ -430,7 +436,7 @@ var
|
|||
I, J, ExitCode: Integer;
|
||||
Args: TStringList;
|
||||
Msg: string;
|
||||
ProvidedSyms, MySyms: TStringList;
|
||||
ProvidedSyms, MySyms, Units: TStringList;
|
||||
Redundant: Boolean;
|
||||
begin
|
||||
Result := '';
|
||||
|
|
@ -481,10 +487,27 @@ begin
|
|||
ForceDirectories(BuildDir);
|
||||
BlaiseBin := ParamStr(0);
|
||||
|
||||
{ The unit list to build/link, in leaf-first order. For a --static link the
|
||||
kernel leaf replaces libc: drop runtime.start (libc/__libc_start_main entry)
|
||||
in favour of the freestanding runtime.start.static.<os>, and add the raw
|
||||
syscall stubs (runtime.syscall.<os>) + the freestanding C functions
|
||||
(runtime.cstub). A dynamic (libc) link uses the plain RTL_UNITS. }
|
||||
Units := TStringList.Create();
|
||||
for I := 0 to High(RTL_UNITS) do
|
||||
if AOpts.Static and SameText(RTL_UNITS[I], 'runtime.start') then
|
||||
Units.Add('runtime.start.static.linux')
|
||||
else
|
||||
Units.Add(RTL_UNITS[I]);
|
||||
if AOpts.Static then
|
||||
begin
|
||||
SrcFile := IncludeTrailingPathDelimiter(SrcDir) + RTL_UNITS[I] + '.pas';
|
||||
ObjFile := IncludeTrailingPathDelimiter(CacheDir) + RTL_UNITS[I] + '.o';
|
||||
Units.Add('runtime.syscall.linux');
|
||||
Units.Add('runtime.cstub');
|
||||
end;
|
||||
|
||||
for I := 0 to Units.Count - 1 do
|
||||
begin
|
||||
SrcFile := IncludeTrailingPathDelimiter(SrcDir) + Units.Strings[I] + '.pas';
|
||||
ObjFile := IncludeTrailingPathDelimiter(CacheDir) + Units.Strings[I] + '.o';
|
||||
if not FileExists(SrcFile) then
|
||||
Exit('RTL unit source missing: ' + SrcFile);
|
||||
|
||||
|
|
@ -495,7 +518,7 @@ begin
|
|||
carry inline asm, so the native backend + internal assembler is mandatory. }
|
||||
if (not FileExists(ObjFile)) or (FileAge(ObjFile) < FileAge(SrcFile)) then
|
||||
begin
|
||||
TmpObj := IncludeTrailingPathDelimiter(BuildDir) + RTL_UNITS[I] + '.o';
|
||||
TmpObj := IncludeTrailingPathDelimiter(BuildDir) + Units.Strings[I] + '.o';
|
||||
Args := TStringList.Create();
|
||||
try
|
||||
Args.Add('--backend'); Args.Add('native');
|
||||
|
|
@ -509,7 +532,7 @@ begin
|
|||
Args.Free();
|
||||
end;
|
||||
if ExitCode <> 0 then
|
||||
Exit('failed to build RTL unit ' + RTL_UNITS[I] +
|
||||
Exit('failed to build RTL unit ' + Units.Strings[I] +
|
||||
' (exit ' + IntToStr(ExitCode) + '): ' + Msg);
|
||||
{ Publish atomically. RenameFile within the same directory tree is an
|
||||
atomic rename(2); a concurrent builder doing the same is harmless. }
|
||||
|
|
@ -520,7 +543,7 @@ begin
|
|||
{ runtime.start defines the bare _start entry — include it only for the
|
||||
native internal linker (Blaise owns the entry); the cc link line gets
|
||||
_start from libc and must omit it to avoid a multiple-definition. }
|
||||
if SameText(RTL_UNITS[I], 'runtime.start') and (not AIncludeStartup) then
|
||||
if SameText(Units.Strings[I], 'runtime.start') and (not AIncludeStartup) then
|
||||
Continue;
|
||||
{ Skip this RTL object if EVERY symbol it defines is already defined by the
|
||||
caller's objects — i.e. the program compiled this RTL unit itself. A
|
||||
|
|
@ -549,12 +572,13 @@ begin
|
|||
cache now; the build dir holds only intermediate .bif/.o stragglers. Remove
|
||||
the known per-unit artefacts and the (now-empty) directory. Best-effort —
|
||||
a leftover dir is harmless. }
|
||||
for I := 0 to High(RTL_UNITS) do
|
||||
for I := 0 to Units.Count - 1 do
|
||||
begin
|
||||
DeleteFile(IncludeTrailingPathDelimiter(BuildDir) + RTL_UNITS[I] + '.o');
|
||||
DeleteFile(IncludeTrailingPathDelimiter(BuildDir) + RTL_UNITS[I] + '.bif');
|
||||
DeleteFile(IncludeTrailingPathDelimiter(BuildDir) + Units.Strings[I] + '.o');
|
||||
DeleteFile(IncludeTrailingPathDelimiter(BuildDir) + Units.Strings[I] + '.bif');
|
||||
end;
|
||||
_driver_rmdir(PChar(ExcludeTrailingPathDelimiter(BuildDir)));
|
||||
Units.Free();
|
||||
finally
|
||||
ProvidedSyms.Free();
|
||||
MySyms.Free();
|
||||
|
|
|
|||
|
|
@ -215,8 +215,11 @@ begin
|
|||
RTLObjs := TStringList.Create();
|
||||
try
|
||||
{ Native internal linker: Blaise owns the entry point, so include
|
||||
runtime.start (its bare _start). Pass the program's prebuilt deps so an
|
||||
RTL unit it compiled itself is not supplied twice. }
|
||||
runtime.start (its bare _start). In --static mode EnsureRTLObjects swaps
|
||||
runtime.start for the freestanding runtime.start.static.<os> and adds the
|
||||
syscall + cstub leaves, so AIncludeStartup is irrelevant there. Pass the
|
||||
program's prebuilt deps so an RTL unit it compiled itself is not supplied
|
||||
twice. }
|
||||
Result := Self.EnsureRTLObjects(AOpts, True, AExtraObjects, RTLObjs);
|
||||
if Result <> '' then Exit;
|
||||
|
||||
|
|
@ -225,7 +228,10 @@ begin
|
|||
Lk := TLinker.Create(LinkTarget);
|
||||
try
|
||||
try
|
||||
Lk.SetDynamic(True);
|
||||
{ --static: freestanding non-PIE ET_EXEC, no libc/PT_INTERP (the kernel
|
||||
leaf supplies open/read/write/… + _start). Default: dynamic PIE
|
||||
linked against libc. }
|
||||
Lk.SetDynamic(not AOpts.Static);
|
||||
|
||||
Obj := ReadElfObjectFile(AObjFile);
|
||||
Lk.AddOwnedObject(Obj);
|
||||
|
|
|
|||
|
|
@ -1676,19 +1676,25 @@ begin
|
|||
|
||||
R_X86_64_64:
|
||||
begin
|
||||
if not FDynamic then
|
||||
raise ELinker.Create(Ctx + ': R_X86_64_64 relocation against `'
|
||||
+ Sym.Name + ''' needs dynamic linking');
|
||||
S := Self.ResolveSymbolAddr(Obj, Rel.SymIndex, Ctx);
|
||||
Val := S + Rel.Addend;
|
||||
if M.ShType = SHT_NOBITS then
|
||||
raise ELinker.Create(Ctx
|
||||
+ ': relocation into a NOBITS section');
|
||||
{ Patch the absolute 64-bit target address. In a non-PIE ET_EXEC
|
||||
(static mode) every symbol has a fixed load address, so this value
|
||||
is final and needs no run-time fixup. In PIE/dynamic mode the
|
||||
image base is unknown until load, so the same slot also gets an
|
||||
R_X86_64_RELATIVE entry in .rela.dyn for the loader to re-add the
|
||||
base. }
|
||||
LkPatch64(M.Data, PFileOff, Val);
|
||||
RE := TRelaDynEntry.Create();
|
||||
RE.VAddr := PAddr;
|
||||
RE.Addend := Val;
|
||||
FRelaDyn.Add(RE);
|
||||
if FDynamic then
|
||||
begin
|
||||
RE := TRelaDynEntry.Create();
|
||||
RE.VAddr := PAddr;
|
||||
RE.Addend := Val;
|
||||
FRelaDyn.Add(RE);
|
||||
end;
|
||||
end;
|
||||
|
||||
R_X86_64_32, R_X86_64_32S:
|
||||
|
|
@ -1719,10 +1725,12 @@ begin
|
|||
end;
|
||||
|
||||
R_X86_64_TPOFF32:
|
||||
{ Local-Exec TLS: the offset from the thread pointer (which points at
|
||||
the END of the static TLS block) down to this threadvar. This is the
|
||||
STATIC TLS model — it is resolvable at link time in both static and
|
||||
PIE/dynamic executables (the threadvar's position within the initial
|
||||
TLS block is fixed at link), so it needs no dynamic relocation. }
|
||||
begin
|
||||
if not FDynamic then
|
||||
raise ELinker.Create(Ctx
|
||||
+ ': TLS relocation needs dynamic linking');
|
||||
S := Self.ResolveSymbolAddr(Obj, Rel.SymIndex, Ctx);
|
||||
Val := S - FTlsAddr + Rel.Addend - FTlsSize;
|
||||
LkPatch32(M.Data, PFileOff, Val and $FFFFFFFF);
|
||||
|
|
|
|||
105
compiler/src/main/pascal/runtime.cstub.pas
Normal file
105
compiler/src/main/pascal/runtime.cstub.pas
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
unit runtime.cstub;
|
||||
|
||||
// Freestanding C-library leaves the compiler and RTL emit calls to but which
|
||||
// are not syscalls: memcpy / memset / memcmp / strlen. In a libc-backed build
|
||||
// these come from libc; in a --static build this unit supplies them so no libc
|
||||
// is needed (docs/linux-syscall-migration.adoc).
|
||||
//
|
||||
// Architecture-neutral byte-at-a-time implementations. Correctness over speed:
|
||||
// these are the fallback for the freestanding build; if profiling shows the
|
||||
// allocator/string paths need it, an SSE/AVX memcpy can replace the loop later
|
||||
// (the inline-asm assembler already encodes the vector ops).
|
||||
//
|
||||
// Signatures match the C ABI the codegen assumes:
|
||||
// void* memcpy(void* dst, const void* src, size_t n) — returns dst
|
||||
// void* memset(void* dst, int c, size_t n) — returns dst
|
||||
// int memcmp(const void* a, const void* b, size_t n)
|
||||
// size_t strlen(const char* s)
|
||||
|
||||
interface
|
||||
|
||||
function memcpy(Dst, Src: Pointer; N: Int64): Pointer;
|
||||
function memset(Dst: Pointer; C: Integer; N: Int64): Pointer;
|
||||
function memcmp(A, B: Pointer; N: Int64): Integer;
|
||||
function strlen(S: PChar): Int64;
|
||||
|
||||
implementation
|
||||
|
||||
{ PChar indexing reads/writes a single byte as an Integer (see runtime.str), so
|
||||
these loops need no Char type (Blaise has none). }
|
||||
|
||||
function memcpy(Dst, Src: Pointer; N: Int64): Pointer;
|
||||
var
|
||||
D, S: PChar;
|
||||
I: Int64;
|
||||
begin
|
||||
D := PChar(Dst);
|
||||
S := PChar(Src);
|
||||
I := 0;
|
||||
while I < N do
|
||||
begin
|
||||
D[I] := S[I];
|
||||
I := I + 1;
|
||||
end;
|
||||
Result := Dst;
|
||||
end;
|
||||
|
||||
function memset(Dst: Pointer; C: Integer; N: Int64): Pointer;
|
||||
var
|
||||
D: PChar;
|
||||
I: Int64;
|
||||
B: Integer;
|
||||
begin
|
||||
D := PChar(Dst);
|
||||
B := C and $FF;
|
||||
I := 0;
|
||||
while I < N do
|
||||
begin
|
||||
D[I] := B;
|
||||
I := I + 1;
|
||||
end;
|
||||
Result := Dst;
|
||||
end;
|
||||
|
||||
function memcmp(A, B: Pointer; N: Int64): Integer;
|
||||
var
|
||||
PA, PB: PChar;
|
||||
I: Int64;
|
||||
CA, CB: Integer;
|
||||
begin
|
||||
PA := PChar(A);
|
||||
PB := PChar(B);
|
||||
I := 0;
|
||||
while I < N do
|
||||
begin
|
||||
CA := PA[I] and $FF;
|
||||
CB := PB[I] and $FF;
|
||||
if CA <> CB then
|
||||
begin
|
||||
Result := CA - CB;
|
||||
Exit;
|
||||
end;
|
||||
I := I + 1;
|
||||
end;
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
function strlen(S: PChar): Int64;
|
||||
var
|
||||
I: Int64;
|
||||
begin
|
||||
I := 0;
|
||||
while (S[I] and $FF) <> 0 do
|
||||
I := I + 1;
|
||||
Result := I;
|
||||
end;
|
||||
|
||||
end.
|
||||
51
compiler/src/main/pascal/runtime.start.static.linux.pas
Normal file
51
compiler/src/main/pascal/runtime.start.static.linux.pas
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
unit runtime.start.static.linux;
|
||||
|
||||
// Freestanding `_start` for a static, libc-free Linux ET_EXEC (the --static
|
||||
// kernel-leaf swap; docs/linux-syscall-migration.adoc).
|
||||
//
|
||||
// The libc-backed runtime.start calls __libc_start_main, which only exists when
|
||||
// linking libc. This variant is the drop-in replacement linked instead when a
|
||||
// program is built --static: it sets up argc/argv from the raw process stack and
|
||||
// jumps straight to `main` (which the backend emits to call _SetArgs/_BlaiseInit,
|
||||
// run the body, and `exit`).
|
||||
//
|
||||
// On entry the kernel hands us the initial process stack (System V AMD64,
|
||||
// "Initial Process Stack"):
|
||||
// (%rsp) = argc
|
||||
// 8(%rsp) = argv[0], argv[1], … then a NULL, then envp, then auxv.
|
||||
// %rsp is 16-byte aligned at the kernel's `_start` only AFTER we account for the
|
||||
// implicit return address a normal `call` would have pushed — i.e. on entry
|
||||
// (%rsp) points at argc and the ABI's "16-byte aligned at call site" means after
|
||||
// our own alignment the stack is correct for the `call main`.
|
||||
//
|
||||
// main expects C-`main(argc, argv)` register layout: argc in %edi, argv in %rsi.
|
||||
|
||||
interface
|
||||
|
||||
procedure _start;
|
||||
|
||||
implementation
|
||||
|
||||
procedure _start; assembler; nostackframe;
|
||||
asm
|
||||
endbr64
|
||||
xor %ebp, %ebp { clear frame pointer — outermost frame }
|
||||
movq (%rsp), %rdi { %rdi = argc }
|
||||
leaq 8(%rsp), %rsi { %rsi = &argv[0] }
|
||||
andq $0xfffffffffffffff0, %rsp { 16-byte align before the call }
|
||||
call main
|
||||
xorl %edi, %edi { main terminates via exit; guard with exit_group(0) }
|
||||
movq $231, %rax { SYS_exit_group }
|
||||
syscall
|
||||
hlt
|
||||
end;
|
||||
|
||||
end.
|
||||
287
compiler/src/main/pascal/runtime.syscall.linux.pas
Normal file
287
compiler/src/main/pascal/runtime.syscall.linux.pas
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
unit runtime.syscall.linux;
|
||||
|
||||
// Linux x86_64 direct-syscall kernel leaf — the libc replacement for the thin
|
||||
// 1:1 syscall wrappers (docs/linux-syscall-migration.adoc).
|
||||
//
|
||||
// This unit DEFINES the bare POSIX names (open, read, write, …) that
|
||||
// rtl.platform.posix.pas references via `external name 'open'` etc. When it is
|
||||
// linked into a program (the --static link-time swap), those symbols resolve
|
||||
// here as raw `syscall` stubs instead of being imported from libc.so.6 — so the
|
||||
// resulting binary is a freestanding static ET_EXEC with no libc, no PT_INTERP.
|
||||
//
|
||||
// System V AMD64 -> Linux syscall ABI:
|
||||
// * syscall number in %rax; the `syscall` instruction returns in %rax.
|
||||
// * Argument registers DIFFER between the C ABI and the kernel ABI on the 4th
|
||||
// argument: C passes arg4 in %rcx, the kernel expects it in %r10 (the
|
||||
// `syscall` instruction itself clobbers %rcx). Wrappers with >= 4 args
|
||||
// therefore `movq %rcx, %r10` first. Args 1-3 (%rdi,%rsi,%rdx) and 5-6
|
||||
// (%r8,%r9) are already in the right registers.
|
||||
// * The kernel returns -errno on error (a small negative value). libc would
|
||||
// translate that to a -1 return + `errno`; the existing Pascal call sites
|
||||
// test `if result < 0` / `if Fd < 0`, which a raw -errno also satisfies, so
|
||||
// no errno table is needed for these leaves.
|
||||
//
|
||||
// `nostackframe` lets each asm body own the frame so the registers hold the raw
|
||||
// incoming arguments unmodified.
|
||||
|
||||
interface
|
||||
|
||||
{ File descriptors. }
|
||||
function open(Path: PChar; Flags: Integer; Mode: Integer): Integer;
|
||||
function close(Fd: Integer): Integer;
|
||||
function read(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
{ `write` collides with the Write/WriteLn builtin as a Pascal identifier, so the
|
||||
Pascal name is _sys_write; the asm body additionally emits a bare `write`
|
||||
label (.globl) so the linker symbol matches rtl.platform.posix's
|
||||
`external name 'write'`. Every other leaf's name is a valid Pascal identifier
|
||||
and needs no alias. }
|
||||
function _sys_write(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
function lseek(Fd: Integer; Offset: Int64; Whence: Integer): Int64;
|
||||
|
||||
{ Filesystem. }
|
||||
function fstat(Fd: Integer; Buf: Pointer): Integer;
|
||||
function stat(Path: PChar; Buf: Pointer): Integer;
|
||||
function mkdir(Path: PChar; Mode: Integer): Integer;
|
||||
function rmdir(Path: PChar): Integer;
|
||||
function unlink(Path: PChar): Integer;
|
||||
function rename(OldPath, NewPath: PChar): Integer;
|
||||
function chdir(Path: PChar): Integer;
|
||||
|
||||
{ Process. }
|
||||
function getpid: Integer;
|
||||
function dup2(OldFd, NewFd: Integer): Integer;
|
||||
function pipe(Fds: Pointer): Integer;
|
||||
|
||||
{ Memory. }
|
||||
function mmap(Addr: Pointer; Length: Int64; Prot, Flags, Fd: Integer;
|
||||
Offset: Int64): Pointer;
|
||||
function munmap(Addr: Pointer; Length: Int64): Integer;
|
||||
|
||||
{ Time. }
|
||||
function nanosleep(Req, Rem: Pointer): Integer;
|
||||
function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
||||
|
||||
{ Process exit — exit_group(2), terminates all threads. Neither runs atexit
|
||||
handlers (that lives in the atexit-registry leaf, a later step); for now the
|
||||
bare `exit` (called by main's epilogue) and `_exit` both terminate directly.
|
||||
`exit` is a reserved word in Pascal, so the Pascal proc is _sys_exit and the
|
||||
asm body emits a bare `exit` label (like `write` -> _sys_write). }
|
||||
function _sys_exit(Code: Integer): Integer;
|
||||
procedure _exit(Code: Integer);
|
||||
|
||||
implementation
|
||||
|
||||
{ ---- Linux x86_64 syscall numbers (arch/x86/entry/syscalls). ---- }
|
||||
const
|
||||
SYS_read = 0;
|
||||
SYS_write = 1;
|
||||
SYS_open = 2;
|
||||
SYS_close = 3;
|
||||
SYS_stat = 4;
|
||||
SYS_fstat = 5;
|
||||
SYS_lseek = 8;
|
||||
SYS_mmap = 9;
|
||||
SYS_munmap = 11;
|
||||
SYS_pipe = 22;
|
||||
SYS_dup2 = 33;
|
||||
SYS_nanosleep = 35;
|
||||
SYS_getpid = 39;
|
||||
SYS_rename = 82;
|
||||
SYS_mkdir = 83;
|
||||
SYS_rmdir = 84;
|
||||
SYS_unlink = 87;
|
||||
SYS_chdir = 80;
|
||||
SYS_clock_gettime = 228;
|
||||
SYS_exit_group = 231;
|
||||
{ The asm bodies use literal immediates (the assembler needs a literal, not a
|
||||
symbol); the const block above documents the number-to-name mapping. }
|
||||
|
||||
function open(Path: PChar; Flags: Integer; Mode: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $2, %rax { SYS_open }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function close(Fd: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $3, %rax { SYS_close }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function read(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $0, %rax { SYS_read }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function _sys_write(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
.globl write
|
||||
write:
|
||||
movq $1, %rax { SYS_write }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function lseek(Fd: Integer; Offset: Int64; Whence: Integer): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $8, %rax { SYS_lseek }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function fstat(Fd: Integer; Buf: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $5, %rax { SYS_fstat }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function stat(Path: PChar; Buf: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $4, %rax { SYS_stat }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function mkdir(Path: PChar; Mode: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $83, %rax { SYS_mkdir }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function rmdir(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $84, %rax { SYS_rmdir }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function unlink(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $87, %rax { SYS_unlink }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function rename(OldPath, NewPath: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $82, %rax { SYS_rename }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function chdir(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $80, %rax { SYS_chdir }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function getpid: Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $39, %rax { SYS_getpid }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function dup2(OldFd, NewFd: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $33, %rax { SYS_dup2 }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function pipe(Fds: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $22, %rax { SYS_pipe }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
{ mmap has 6 args; arg4 (Flags) arrives in %rcx (C ABI) but the kernel wants it
|
||||
in %r10. Move it before the syscall. }
|
||||
function mmap(Addr: Pointer; Length: Int64; Prot, Flags, Fd: Integer;
|
||||
Offset: Int64): Pointer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq %rcx, %r10
|
||||
movq $9, %rax { SYS_mmap }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function munmap(Addr: Pointer; Length: Int64): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $11, %rax { SYS_munmap }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function nanosleep(Req, Rem: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $35, %rax { SYS_nanosleep }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $228, %rax { SYS_clock_gettime }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
{ exit_group(2) terminates the whole process (all threads) and does not return.
|
||||
The bare `exit` (called by main's epilogue) and `_exit` are the same raw
|
||||
terminator until the atexit-registry leaf lands. Code arrives in %edi;
|
||||
exit_group reads its status from %rdi, so no marshalling is needed. }
|
||||
function _sys_exit(Code: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
.globl exit
|
||||
exit:
|
||||
movq $231, %rax { SYS_exit_group }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
procedure _exit(Code: Integer);
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $231, %rax { SYS_exit_group }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -58,7 +58,7 @@ type
|
|||
procedure TestSym_SynthesisedSymbolsDefined;
|
||||
{ Static relocations }
|
||||
procedure TestReloc_PC32CrossObjectCall;
|
||||
procedure TestReloc_Quad64_Raises;
|
||||
procedure TestReloc_Quad64_ResolvesToAbsoluteAddr;
|
||||
{ Executable structure }
|
||||
procedure TestExe_ElfHeaderIsExec;
|
||||
procedure TestExe_EntryPointMatchesSymbol;
|
||||
|
|
@ -786,29 +786,30 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TLinkerTests.TestReloc_Quad64_Raises;
|
||||
procedure TLinkerTests.TestReloc_Quad64_ResolvesToAbsoluteAddr;
|
||||
var
|
||||
Lk: TLinker;
|
||||
Bytes: string;
|
||||
Raised: Boolean;
|
||||
begin
|
||||
{ An absolute 64-bit pointer to a symbol needs dynamic linking under
|
||||
a real PIE; Phase B rejects R_X86_64_64 explicitly. }
|
||||
Raised := False;
|
||||
Lk := nil;
|
||||
{ An absolute 64-bit pointer to a symbol (`.quad target`, R_X86_64_64) is
|
||||
resolvable at link time in a non-PIE ET_EXEC: every symbol has a fixed load
|
||||
address. The static linker must patch the slot with target's absolute
|
||||
address (no dynamic relocation) — this is what makes a freestanding static
|
||||
--static link possible (docs/linux-syscall-migration.adoc). }
|
||||
Lk := LinkObjs(
|
||||
['.data' + LineEnding + 'ptr:' + LineEnding + '.quad target' + LineEnding +
|
||||
'.text' + LineEnding + '.globl target' + LineEnding + 'target:' +
|
||||
LineEnding + 'ret' + LineEnding +
|
||||
'.globl _start' + LineEnding + '_start:' + LineEnding + 'ret' + LineEnding],
|
||||
'_start', Bytes);
|
||||
try
|
||||
Lk := LinkObjs(
|
||||
['.data' + LineEnding + 'ptr:' + LineEnding + '.quad target' + LineEnding +
|
||||
'.text' + LineEnding + '.globl target' + LineEnding + 'target:' +
|
||||
LineEnding + 'ret' + LineEnding +
|
||||
'.globl _start' + LineEnding + '_start:' + LineEnding + 'ret' + LineEnding],
|
||||
'_start', Bytes);
|
||||
except
|
||||
on E: ELinker do
|
||||
Raised := True;
|
||||
{ The link must succeed and produce a valid ET_EXEC. }
|
||||
AssertTrue('static R_X86_64_64 link produced no output', Length(Bytes) >= 64);
|
||||
AssertEquals('e_type ET_EXEC', 2,
|
||||
(Ord(Bytes[16]) and $FF) or ((Ord(Bytes[17]) and $FF) shl 8));
|
||||
finally
|
||||
Lk.Free();
|
||||
end;
|
||||
Lk.Free();
|
||||
AssertTrue('R_X86_64_64 must raise ELinker in Phase B', Raised);
|
||||
end;
|
||||
|
||||
procedure TLinkerTests.TestExe_ElfHeaderIsExec;
|
||||
|
|
|
|||
Loading…
Reference in a new issue