feat(rtl,linker): static TLS + mutex stubs — a libc-free static binary now RUNS
Completes the runnable-static-binary milestone: `blaise --static` now produces a fully static, libc-free ELF (statically linked, no NEEDED libc.so.6, no PT_INTERP) that EXECUTES correctly — `hello` prints, arithmetic/WriteLn work. Static TLS (the last blocker — threadvar access via %fs faulted because %fs was unset and static mode emitted no PT_TLS): - linker LayoutSections now lays out .tdata/.tbss as a TLS block and sets FTlsAddr/FTlsSize/FTlsFileSize/FTlsAlign (previously only LayoutDynamic did, so static-mode TPOFF relocs resolved against 0). - EmitExecutable emits a PT_TLS program header when the program has TLS, and LayoutSections reserves a 3rd phdr slot so the first section does not overlap and overwrite it. - runtime.start.static.linux: rewritten with a Pascal core (_BlaiseStartC) that parses argc/argv/envp + the auxv, reads PT_TLS via AT_PHDR, mmaps + inits the TLS block, sets the thread pointer with arch_prctl(ARCH_SET_FS) (variant II: TP past the block, TCB self-pointer at %fs:0), then calls main. runtime.thread.static.linux: no-op single-threaded pthread_mutex_* stubs (correct for a single-threaded process). pthread_create is intentionally unprovided, so a thread-using static program (e.g. `uses classes`) fails to link — the honest signal that the threads leaf (clone/futex) is the one remaining piece. Also fixed: runtime.start.static.linux had its `uses` in the implementation section (Blaise only honours interface-section uses) which made syscall symbols invisible; and em-dashes in comments tripped the lexer (known UTF-8 issue). Full suite green (3829); dynamic native/QBE (incl. TLS + classes) unaffected.
This commit is contained in:
parent
7b6e275053
commit
3e4619a507
|
|
@ -817,12 +817,19 @@ var
|
|||
M: TMergedSection;
|
||||
Addr: Int64;
|
||||
HdrBytes: Int64;
|
||||
IsAlloc, IsExec, IsWrite: Boolean;
|
||||
IsAlloc, IsExec, IsWrite, HasTlsSec: Boolean;
|
||||
begin
|
||||
{ Program-header count is known: PT_LOAD x2 (exec run, write run).
|
||||
Reserve header space so the first section's file offset — which
|
||||
equals (addr - base) — clears the headers. Elf64_Phdr = 56. }
|
||||
HdrBytes := ELF64_EHDR_SIZE + 2 * 56;
|
||||
{ Program headers: PT_LOAD x2 (exec run, write run), plus PT_TLS when the
|
||||
program has any TLS section (.tdata/.tbss). Reserve header space so the
|
||||
first section's file offset (= addr - base) clears the headers; otherwise
|
||||
the first section would overlap and overwrite the PT_TLS phdr. Phdr = 56. }
|
||||
HasTlsSec := False;
|
||||
for I := 0 to FMerger.Merged.Count - 1 do
|
||||
if (FMerger.Merged.Get(I).Flags and SHF_TLS) <> 0 then HasTlsSec := True;
|
||||
if HasTlsSec then
|
||||
HdrBytes := ELF64_EHDR_SIZE + 3 * 56
|
||||
else
|
||||
HdrBytes := ELF64_EHDR_SIZE + 2 * 56;
|
||||
|
||||
{ Executable run: header bytes share its first page. }
|
||||
Addr := FTarget.BaseAddr + HdrBytes;
|
||||
|
|
@ -844,14 +851,16 @@ begin
|
|||
Self.PlaceSection(M, Addr);
|
||||
end;
|
||||
|
||||
{ Writable run starts on a fresh page. }
|
||||
{ Writable run starts on a fresh page. TLS sections (.tdata/.tbss, SHF_TLS)
|
||||
are placed separately below and must be skipped here. }
|
||||
Addr := LkAlignUp(Addr, FTarget.PageSize);
|
||||
for I := 0 to FMerger.Merged.Count - 1 do
|
||||
begin
|
||||
M := FMerger.Merged.Get(I);
|
||||
IsAlloc := (M.Flags and SHF_ALLOC) <> 0;
|
||||
IsWrite := (M.Flags and SHF_WRITE) <> 0;
|
||||
if IsAlloc and IsWrite and (M.ShType <> SHT_NOBITS) then
|
||||
if IsAlloc and IsWrite and (M.ShType <> SHT_NOBITS)
|
||||
and ((M.Flags and SHF_TLS) = 0) then
|
||||
Self.PlaceSection(M, Addr);
|
||||
end;
|
||||
for I := 0 to FMerger.Merged.Count - 1 do
|
||||
|
|
@ -859,9 +868,36 @@ begin
|
|||
M := FMerger.Merged.Get(I);
|
||||
IsAlloc := (M.Flags and SHF_ALLOC) <> 0;
|
||||
IsWrite := (M.Flags and SHF_WRITE) <> 0;
|
||||
if IsAlloc and IsWrite and (M.ShType = SHT_NOBITS) then
|
||||
if IsAlloc and IsWrite and (M.ShType = SHT_NOBITS)
|
||||
and ((M.Flags and SHF_TLS) = 0) then
|
||||
Self.PlaceSection(M, Addr);
|
||||
end;
|
||||
|
||||
{ TLS sections (.tdata then .tbss). Mirror LayoutDynamic so a static ET_EXEC
|
||||
also gets a correct TLS block + FTlsAddr/FTlsSize for TPOFF relocations and
|
||||
the PT_TLS program header (which the freestanding _start reads to set up the
|
||||
thread pointer). }
|
||||
FTlsAddr := 0;
|
||||
FTlsSize := 0;
|
||||
FTlsAlign := 1;
|
||||
FTlsFileSize := 0;
|
||||
M := FMerger.FindMerged('.tdata');
|
||||
if M <> nil then
|
||||
begin
|
||||
Self.PlaceSection(M, Addr);
|
||||
FTlsAddr := Self.MergedAddr(M);
|
||||
FTlsFileSize := M.Size;
|
||||
FTlsSize := M.Size;
|
||||
if M.Align > FTlsAlign then FTlsAlign := M.Align;
|
||||
end;
|
||||
M := FMerger.FindMerged('.tbss');
|
||||
if M <> nil then
|
||||
begin
|
||||
Self.PlaceSection(M, Addr);
|
||||
if FTlsAddr = 0 then FTlsAddr := Self.MergedAddr(M);
|
||||
FTlsSize := FTlsSize + M.Size;
|
||||
if M.Align > FTlsAlign then FTlsAlign := M.Align;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TLinker.FindSymbol(const AName: string): TLinkSymbol;
|
||||
|
|
@ -1804,7 +1840,12 @@ begin
|
|||
Buf := Buf + LkLE(0, 4); { e_flags }
|
||||
Buf := Buf + LkLE(ELF64_EHDR_SIZE, 2); { e_ehsize }
|
||||
Buf := Buf + LkLE(56, 2); { e_phentsize }
|
||||
Buf := Buf + LkLE(2, 2); { e_phnum (2 PT_LOAD) }
|
||||
{ 2 PT_LOAD, plus PT_TLS when the program has threadvars (so the static
|
||||
_start can set up the thread pointer from the PT_TLS init image). }
|
||||
if FTlsSize > 0 then
|
||||
Buf := Buf + LkLE(3, 2) { e_phnum }
|
||||
else
|
||||
Buf := Buf + LkLE(2, 2);
|
||||
Buf := Buf + LkLE(ELF64_SHDR_SIZE, 2); { e_shentsize }
|
||||
Buf := Buf + LkLE(0, 2); { e_shnum (patched later) }
|
||||
Buf := Buf + LkLE(0, 2); { e_shstrndx (patched later) }
|
||||
|
|
@ -1827,6 +1868,20 @@ begin
|
|||
Buf := Buf + LkLE(WriteMemHi - WriteLo, 8); { p_memsz }
|
||||
Buf := Buf + LkLE(PageSz, 8); { p_align }
|
||||
|
||||
{ PT_TLS — the initial TLS image (.tdata file bytes + .tbss zero size). The
|
||||
static _start reads this segment to build the per-thread block and set %fs.
|
||||
Only emitted when the program has threadvars (e_phnum reflects this). }
|
||||
if FTlsSize > 0 then
|
||||
begin
|
||||
Buf := Buf + LkLE(PT_TLS, 4) + LkLE(PF_R, 4);
|
||||
Buf := Buf + LkLE(Self.FileOffset(FTlsAddr), 8); { p_offset }
|
||||
Buf := Buf + LkLE(FTlsAddr, 8); { p_vaddr }
|
||||
Buf := Buf + LkLE(FTlsAddr, 8); { p_paddr }
|
||||
Buf := Buf + LkLE(FTlsFileSize, 8); { p_filesz (.tdata) }
|
||||
Buf := Buf + LkLE(FTlsSize, 8); { p_memsz (.tdata + .tbss) }
|
||||
Buf := Buf + LkLE(FTlsAlign, 8); { p_align }
|
||||
end;
|
||||
|
||||
{ Pad headers out to the first section's file offset (the exec run's
|
||||
first byte sits at Self.FileOffset(ExecLo)). }
|
||||
if Length(Buf) < Self.FileOffset(ExecLo) then
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ unit runtime.cstub;
|
|||
// (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
|
||||
// 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,31 +8,24 @@
|
|||
|
||||
unit runtime.libc.linux;
|
||||
|
||||
// libc-shaped leaves that are MORE than a raw syscall — they need logic the
|
||||
// libc-shaped leaves that are MORE than a raw syscall - they need logic the
|
||||
// kernel does not provide: PATH/env lookup, the libc-style return shapes, etc.
|
||||
// Built on the raw syscalls in runtime.syscall.linux. Linked only in a
|
||||
// --static (libc-free) build (docs/linux-syscall-migration.adoc).
|
||||
//
|
||||
// These DEFINE the bare POSIX names (getcwd, getenv, …) that rtl.platform.posix
|
||||
// These DEFINE the bare POSIX names (getcwd, getenv, ) that rtl.platform.posix
|
||||
// imports via `external name`, so they resolve here instead of libc.so.6.
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
runtime.syscall.linux;
|
||||
|
||||
{ The process environment vector (NULL-terminated array of "KEY=VALUE" PChars).
|
||||
Captured by the freestanding _start (runtime.start.static.linux) from the
|
||||
initial process stack; libc would otherwise own this symbol. getenv/execvp
|
||||
read it. }
|
||||
var
|
||||
environ: Pointer;
|
||||
runtime.syscall.linux; { provides `environ`, captured by _start }
|
||||
|
||||
{ libc-shaped getcwd: returns Buf on success, nil on error (the raw syscall
|
||||
returns a length / -errno). }
|
||||
function getcwd(Buf: PChar; Size: Int64): PChar;
|
||||
|
||||
{ getenv: linear scan of `environ` for "Name=" → returns a pointer to the value
|
||||
{ getenv: linear scan of `environ` for "Name=" returns a pointer to the value
|
||||
(just past '='), or nil if not present. }
|
||||
function getenv(Name: PChar): PChar;
|
||||
|
||||
|
|
@ -47,7 +40,7 @@ function waitpid(Pid: Integer; Status: Pointer; Options: Integer): Integer;
|
|||
NULL-terminated argument vector; the current `environ` is passed as envp. }
|
||||
function execvp(File_: PChar; Argv: Pointer): Integer;
|
||||
|
||||
{ sysconf for the few names the RTL queries — currently only the online CPU
|
||||
{ sysconf for the few names the RTL queries - currently only the online CPU
|
||||
count (_SC_NPROCESSORS_ONLN = 84), via sched_getaffinity + popcount. }
|
||||
function sysconf(Name: Integer): Int64;
|
||||
|
||||
|
|
@ -103,7 +96,7 @@ begin
|
|||
if environ = nil then Exit;
|
||||
NLen := CStrLen(Name);
|
||||
{ environ is a NULL-terminated array of PChar; read the I-th slot as a
|
||||
Pointer at environ + I*8 (PPointer deref, the RTL idiom — no [] on a typed
|
||||
Pointer at environ + I*8 (PPointer deref, the RTL idiom - no [] on a typed
|
||||
pointer). }
|
||||
Off := 0;
|
||||
Entry := PChar(PPointer(Pointer(PChar(environ) + Off))^);
|
||||
|
|
@ -148,7 +141,7 @@ begin
|
|||
if HasSlash then
|
||||
Result := execve(File_, Argv, environ)
|
||||
else
|
||||
{ No PATH search yet — try as-is so the failure is the kernel's ENOENT
|
||||
{ No PATH search yet - try as-is so the failure is the kernel's ENOENT
|
||||
rather than a silent wrong result. }
|
||||
Result := execve(File_, Argv, environ);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ end;
|
|||
|
||||
function localtime_r(T: Pointer; Tm: Pointer): Pointer;
|
||||
begin
|
||||
{ No timezone database in the freestanding RTL — treat local as UTC. }
|
||||
{ No timezone database in the freestanding RTL - treat local as UTC. }
|
||||
Result := gmtime_r(T, Tm);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,43 +8,184 @@
|
|||
|
||||
unit runtime.start.static.linux;
|
||||
|
||||
// Freestanding `_start` for a static, libc-free Linux ET_EXEC (the --static
|
||||
// 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`).
|
||||
// A tiny asm trampoline captures the initial stack pointer and tail-calls the
|
||||
// Pascal _BlaiseStartC, which:
|
||||
// 1. parses argc / argv / envp / the auxv (the kernel's initial stack);
|
||||
// 2. sets up the static TLS block and the thread pointer (%fs) - required
|
||||
// before any threadvar (%fs-relative) access, which a static binary's
|
||||
// kernel does NOT do for us;
|
||||
// 3. captures `environ`;
|
||||
// 4. calls main(argc, argv);
|
||||
// 5. exit_group with main's return (main itself calls exit, so this is a
|
||||
// guard).
|
||||
//
|
||||
// On entry the kernel hands us the initial process stack (System V AMD64,
|
||||
// "Initial Process Stack"):
|
||||
// (%rsp) = argc
|
||||
// 8(%rsp) = argv[0] … argv[argc-1], then a NULL,
|
||||
// 8+(argc+1)*8(%rsp) = envp[0] … then a NULL, then auxv.
|
||||
// So envp = &argv[argc+1] = %rsp + (argc+2)*8. We capture it into the global
|
||||
// `environ` (which getenv/execvp read) before calling main.
|
||||
//
|
||||
// main expects C-`main(argc, argv)` register layout: argc in %edi, argv in %rsi.
|
||||
// x86-64 TLS (variant II): the thread pointer points at the TCB, which sits
|
||||
// ABOVE the TLS block; threadvars are at negative offsets from the TP. %fs:0
|
||||
// must hold the TP value itself (the TCB self-pointer). Layout we build:
|
||||
// [ TLS block: memsz bytes ][ TCB: 8-byte self-pointer ]
|
||||
// tp = block + memsz_aligned ; %fs = tp ; *(void**)tp = tp.
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
runtime.syscall.linux; { syscalls + the `environ` global }
|
||||
|
||||
procedure _start;
|
||||
|
||||
implementation
|
||||
|
||||
type
|
||||
PPointer = ^Pointer;
|
||||
PInt64 = ^Int64;
|
||||
|
||||
const
|
||||
ARCH_SET_FS = $1002;
|
||||
PROT_RW = 3; { PROT_READ or PROT_WRITE }
|
||||
MAP_PRIVANON = $22; { MAP_PRIVATE or MAP_ANONYMOUS }
|
||||
AT_NULL = 0;
|
||||
AT_PHDR = 3;
|
||||
AT_PHENT = 4;
|
||||
AT_PHNUM = 5;
|
||||
PT_TLS = 7;
|
||||
|
||||
{ ELF64 Phdr field offsets. }
|
||||
PH_TYPE = 0; { p_type (u32) }
|
||||
PH_OFFSET = 8; { p_offset (u64) - file offset; == vaddr-base in our images }
|
||||
PH_VADDR = 16; { p_vaddr (u64) }
|
||||
PH_FILESZ = 32; { p_filesz (u64) }
|
||||
PH_MEMSZ = 40; { p_memsz (u64) }
|
||||
PH_ALIGN = 48; { p_align (u64) }
|
||||
|
||||
{ Round X up to the next multiple of A (A a power of two, >= 1). }
|
||||
function AlignUp(X, A: Int64): Int64;
|
||||
begin
|
||||
if A < 1 then A := 1;
|
||||
Result := (X + A - 1) and (not (A - 1));
|
||||
end;
|
||||
|
||||
{ Walk the auxv for PT_TLS via AT_PHDR/AT_PHENT/AT_PHNUM, then build the static
|
||||
TLS block and set the thread pointer. Auxv is an array of (Int64 tag, Int64
|
||||
val) pairs terminated by AT_NULL. No-op when the program has no TLS. }
|
||||
procedure SetupTLS(Auxv: Pointer);
|
||||
var
|
||||
P: PInt64;
|
||||
PhdrAddr, PhEnt, PhNum: Int64;
|
||||
I, Tag, Val: Int64;
|
||||
Ph: PChar;
|
||||
TlsVaddr, TlsFileSz, TlsMemSz, TlsAlign: Int64;
|
||||
PType: ^Integer;
|
||||
Block, Tp: Pointer;
|
||||
BlockSize: Int64;
|
||||
J: Int64;
|
||||
Src, Dst: PChar;
|
||||
TpSlot: PPointer;
|
||||
begin
|
||||
PhdrAddr := 0; PhEnt := 56; PhNum := 0;
|
||||
P := PInt64(Auxv);
|
||||
while True do
|
||||
begin
|
||||
Tag := P^;
|
||||
P := PInt64(Pointer(PChar(P) + 8));
|
||||
Val := P^;
|
||||
P := PInt64(Pointer(PChar(P) + 8));
|
||||
if Tag = AT_NULL then Break;
|
||||
if Tag = AT_PHDR then PhdrAddr := Val;
|
||||
if Tag = AT_PHENT then PhEnt := Val;
|
||||
if Tag = AT_PHNUM then PhNum := Val;
|
||||
end;
|
||||
if PhdrAddr = 0 then Exit;
|
||||
|
||||
{ Find PT_TLS among the program headers. }
|
||||
TlsVaddr := 0; TlsFileSz := 0; TlsMemSz := 0; TlsAlign := 8;
|
||||
I := 0;
|
||||
while I < PhNum do
|
||||
begin
|
||||
Ph := PChar(Pointer(PhdrAddr + I * PhEnt));
|
||||
PType := Pointer(Ph + PH_TYPE);
|
||||
if PType^ = PT_TLS then
|
||||
begin
|
||||
TlsVaddr := PInt64(Pointer(Ph + PH_VADDR))^;
|
||||
TlsFileSz := PInt64(Pointer(Ph + PH_FILESZ))^;
|
||||
TlsMemSz := PInt64(Pointer(Ph + PH_MEMSZ))^;
|
||||
TlsAlign := PInt64(Pointer(Ph + PH_ALIGN))^;
|
||||
Break;
|
||||
end;
|
||||
I := I + 1;
|
||||
end;
|
||||
if TlsMemSz = 0 then Exit;
|
||||
|
||||
{ Allocate block = aligned(memsz) + 8 (TCB self-pointer). mmap zero-fills. }
|
||||
BlockSize := AlignUp(TlsMemSz, TlsAlign) + 16;
|
||||
Block := mmap(nil, BlockSize, PROT_RW, MAP_PRIVANON, -1, 0);
|
||||
if Int64(Block) < 0 then Exit;
|
||||
|
||||
{ Copy the .tdata init image to the start of the block; .tbss stays zero. }
|
||||
Src := PChar(Pointer(TlsVaddr));
|
||||
Dst := PChar(Block);
|
||||
J := 0;
|
||||
while J < TlsFileSz do
|
||||
begin
|
||||
Dst[J] := Src[J];
|
||||
J := J + 1;
|
||||
end;
|
||||
|
||||
{ Thread pointer sits just past the TLS data (variant II); store the TCB
|
||||
self-pointer at *tp and set %fs. }
|
||||
Tp := Pointer(PChar(Block) + AlignUp(TlsMemSz, TlsAlign));
|
||||
TpSlot := PPointer(Tp);
|
||||
TpSlot^ := Tp;
|
||||
arch_prctl(ARCH_SET_FS, Tp);
|
||||
end;
|
||||
|
||||
{ Call the program's `main(argc, argv)` (emitted by the backend) and return its
|
||||
result. The asm thunk tail-jumps to the bare `main` symbol; argc/argv are
|
||||
already in %edi/%rsi (SysV), exactly what main expects. }
|
||||
function MainTrampoline(Argc: Integer; Argv: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
jmp main
|
||||
end;
|
||||
|
||||
{ The C-level entry: SP points at the kernel's initial stack (argc at [SP]). }
|
||||
procedure _BlaiseStartC(SP: Pointer);
|
||||
var
|
||||
Argc: Int64;
|
||||
Argv, Envp, Auxv: Pointer;
|
||||
I: Int64;
|
||||
Ret: Integer;
|
||||
begin
|
||||
Argc := PInt64(SP)^;
|
||||
Argv := Pointer(PChar(SP) + 8);
|
||||
{ envp = &argv[argc+1] }
|
||||
Envp := Pointer(PChar(Argv) + (Argc + 1) * 8);
|
||||
environ := Envp;
|
||||
{ auxv follows envp's NULL terminator. }
|
||||
Auxv := Envp;
|
||||
I := 0;
|
||||
while PPointer(Pointer(PChar(Auxv) + I * 8))^ <> nil do
|
||||
I := I + 1;
|
||||
Auxv := Pointer(PChar(Auxv) + (I + 1) * 8);
|
||||
|
||||
SetupTLS(Auxv);
|
||||
|
||||
Ret := MainTrampoline(Integer(Argc), Argv);
|
||||
_exit(Ret);
|
||||
end;
|
||||
|
||||
{ The kernel entry. Capture %rsp (points at argc), align, and call the Pascal
|
||||
core. Never returns. }
|
||||
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] }
|
||||
leaq 16(%rsp,%rdi,8), %rax { environ = &argv[argc+1] = %rsp+16+argc*8 }
|
||||
movq %rax, environ(%rip)
|
||||
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 }
|
||||
xor %ebp, %ebp
|
||||
movq %rsp, %rdi { SP -> first arg }
|
||||
andq $0xfffffffffffffff0, %rsp
|
||||
call _BlaiseStartC
|
||||
xorl %edi, %edi
|
||||
movq $231, %rax { exit_group(0) guard }
|
||||
syscall
|
||||
hlt
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
unit runtime.syscall.linux;
|
||||
|
||||
// Linux x86_64 direct-syscall kernel leaf — the libc replacement for the thin
|
||||
// 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
|
||||
// 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
|
||||
// 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:
|
||||
|
|
@ -64,7 +64,7 @@ function pipe(Fds: Pointer): Integer;
|
|||
function mmap(Addr: Pointer; Length: Int64; Prot, Flags, Fd: Integer;
|
||||
Offset: Int64): Pointer;
|
||||
function munmap(Addr: Pointer; Length: Int64): Integer;
|
||||
{ mremap(old, oldsz, newsz, flags, newaddr) — 5 args; raw syscall. }
|
||||
{ mremap(old, oldsz, newsz, flags, newaddr) - 5 args; raw syscall. }
|
||||
function mremap(OldAddr: Pointer; OldSize, NewSize: Int64; Flags: Integer;
|
||||
NewAddr: Pointer): Pointer;
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
|||
|
||||
{ Process control + raw primitives the higher-level wrappers (runtime.libc.linux)
|
||||
build on. These are the bare syscalls; the libc-shaped versions (execvp with
|
||||
PATH search, waitpid, system, …) live in the wrapper unit. }
|
||||
PATH search, waitpid, system, ) live in the wrapper unit. }
|
||||
function fork: Integer;
|
||||
function execve(Path: PChar; Argv, Envp: Pointer): Integer;
|
||||
function wait4(Pid: Integer; Status: Pointer; Options: Integer;
|
||||
|
|
@ -88,15 +88,22 @@ function kill(Pid, Sig: Integer): Integer;
|
|||
written there. The kernel syscall handles the out-param itself. }
|
||||
function sys_time(T: Pointer): Int64;
|
||||
{ Raw getcwd(2): fills Buf (up to Size), returns the length INCLUDING the NUL on
|
||||
success or -errno. (libc's getcwd returns the buffer pointer — the wrapper in
|
||||
success or -errno. (libc's getcwd returns the buffer pointer - the wrapper in
|
||||
runtime.libc.linux adapts that.) }
|
||||
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
||||
|
||||
{ _exit(2) — exit_group, terminates all threads, runs NO atexit handlers.
|
||||
{ _exit(2) - exit_group, terminates all threads, runs NO atexit handlers.
|
||||
The bare `exit` (which DOES run atexit handlers, called by main's epilogue) is
|
||||
defined by runtime.libc2.linux alongside the registry, not here. }
|
||||
procedure _exit(Code: Integer);
|
||||
|
||||
{ The process environment vector (NULL-terminated array of "KEY=VALUE" PChars),
|
||||
captured by the freestanding _start from the initial stack. Lives in this
|
||||
base unit (not the higher libc wrapper) so _start can use ONLY this unit and
|
||||
avoid a diamond import; runtime.libc.linux's getenv/execvp read it. }
|
||||
var
|
||||
environ: Pointer;
|
||||
|
||||
implementation
|
||||
|
||||
{ ---- Linux x86_64 syscall numbers (arch/x86/entry/syscalls). ---- }
|
||||
|
|
|
|||
Loading…
Reference in a new issue