feat(rtl): FreeBSD Tier-2 libc leaves + mremap/getrandom (Step 4b)
The Step-4b RTL leaves for a static FreeBSD build. memcpy/memset/memcmp/ strlen are already target-invariant (runtime.cstub, pure Pascal — shared), and the x86-64 jmp_buf is ABI-invariant (runtime.setjmp — shared), so this step is small: * runtime.syscall.freebsd gains getrandom (FreeBSD SYS 563, not Linux's 318) and an mremap stub. FreeBSD has no in-place-remap syscall, so mremap is a plain function returning MAP_FAILED (-1); runtime.mem's large-realloc path already treats an mremap failure as "grow the slow way" (alloc a fresh region, memcpy, free the old), so exporting a failing mremap gives the allocator its no-mremap fallback with zero allocator changes. * runtime.libc2.freebsd — the FreeBSD sibling of runtime.libc2.linux: the atexit registry + `exit`, __cxa_atexit, abort, freestanding UTC civil-calendar gmtime_r/localtime_r/timegm, mkstemp, and system. The calendar math, atexit registry, and struct tm layout are target-invariant (tm is identical on Linux/FreeBSD amd64), so the only differences from the Linux unit are the syscall leaf it builds on (runtime.syscall.freebsd) and the FreeBSD O_* flag values (O_CREAT=$0200, O_EXCL=$0800 — not Linux's $40/$80). Both units are standalone (nothing in the Linux build graph uses them), so the Linux self-host is untouched — confirmed by the clean fixpoints/suite. Test TestLink_FreeBSDSyscallLeaf_GetrandomAndMremapStub asserts getrandom uses 563 (and NOT Linux's 318) and the mremap stub returns -1. Threads (thr_new/_umtx_op) are Step 4c.
This commit is contained in:
parent
2662535579
commit
bded5294d5
296
compiler/src/main/pascal/runtime.libc2.freebsd.pas
Normal file
296
compiler/src/main/pascal/runtime.libc2.freebsd.pas
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
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.libc2.freebsd;
|
||||||
|
|
||||||
|
// FreeBSD sibling of runtime.libc2.linux — the Tier 2 libc leaves that need real
|
||||||
|
// logic, not a raw syscall:
|
||||||
|
// * the atexit registry + `exit` (runs handlers then _exit)
|
||||||
|
// * __cxa_atexit (register a handler)
|
||||||
|
// * abort (raise SIGABRT, then _exit 134)
|
||||||
|
// * gmtime_r / localtime_r / timegm (freestanding civil-calendar math, UTC)
|
||||||
|
// * mkstemp (random-named O_CREAT|O_EXCL file)
|
||||||
|
// * system (fork + execve /bin/sh -c + wait4)
|
||||||
|
//
|
||||||
|
// The civil-calendar math, the atexit registry, and the tm layout are all
|
||||||
|
// target-invariant (struct tm is identical on Linux/FreeBSD amd64), so the only
|
||||||
|
// differences from the Linux unit are the syscall leaf it builds on
|
||||||
|
// (runtime.syscall.freebsd) and the FreeBSD O_* flag values. Linked only in a
|
||||||
|
// --static (libc-free) FreeBSD build (docs/freebsd-x86_64-backend-design.adoc,
|
||||||
|
// Step 4b).
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
runtime.syscall.freebsd;
|
||||||
|
|
||||||
|
{ __cxa_atexit(func, arg, dso_handle): register func(arg) to run at exit, LIFO.
|
||||||
|
Returns 0 on success. dso_handle is ignored (single program, no DSO unload). }
|
||||||
|
function __cxa_atexit(Func, Arg, DsoHandle: Pointer): Integer;
|
||||||
|
|
||||||
|
{ exit(code): run the registered handlers (LIFO), then _exit(code). This is the
|
||||||
|
bare `exit` main's epilogue calls; it is a reserved word in Pascal, so the
|
||||||
|
Pascal proc is _libc2_exit and the asm body publishes `exit`. }
|
||||||
|
procedure _RunAtExitAndExit(Code: Integer);
|
||||||
|
|
||||||
|
{ abort(): raise SIGABRT to self; if the signal is caught/ignored, force-exit
|
||||||
|
with status 134 (128 + SIGABRT). }
|
||||||
|
procedure abort;
|
||||||
|
|
||||||
|
{ struct tm split/join (UTC). T points at a time_t (Int64 epoch seconds). }
|
||||||
|
function gmtime_r(T: Pointer; Tm: Pointer): Pointer;
|
||||||
|
function localtime_r(T: Pointer; Tm: Pointer): Pointer;
|
||||||
|
function timegm(Tm: Pointer): Int64;
|
||||||
|
|
||||||
|
{ mkstemp(template): template ends with "XXXXXX"; replace with random chars and
|
||||||
|
open O_CREAT|O_EXCL|O_RDWR mode 0600. Returns the fd or -1. }
|
||||||
|
function mkstemp(Template: PChar): Integer;
|
||||||
|
|
||||||
|
{ system(cmd): fork, child execve "/bin/sh -c <cmd>", parent wait4; returns the
|
||||||
|
child's raw wait status (the RTL extracts the exit code). }
|
||||||
|
function system(Cmd: PChar): Integer;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
type
|
||||||
|
PInt64 = ^Int64;
|
||||||
|
PInteger = ^Integer;
|
||||||
|
|
||||||
|
const
|
||||||
|
SIGABRT = 6;
|
||||||
|
O_RDWR = 2;
|
||||||
|
{ FreeBSD open(2) flag bits — differ from Linux ($40 / $80). See
|
||||||
|
rtl.platform.layout.freebsd (O_CREAT=$0200, O_TRUNC=$0400, O_APPEND=$0008);
|
||||||
|
O_EXCL is $0800 (sys/fcntl.h). }
|
||||||
|
O_CREAT = $0200;
|
||||||
|
O_EXCL = $0800;
|
||||||
|
MAX_ATEXIT = 64;
|
||||||
|
|
||||||
|
{ struct tm field byte offsets (SysV layout — identical on Linux and FreeBSD
|
||||||
|
amd64; the RTL's TTm mirrors it):
|
||||||
|
Sec@0 Min@4 Hour@8 MDay@12 Mon@16 Year@20 WDay@24 YDay@28 IsDST@32 (all int). }
|
||||||
|
const
|
||||||
|
TM_SEC=0; TM_MIN=4; TM_HOUR=8; TM_MDAY=12; TM_MON=16; TM_YEAR=20;
|
||||||
|
TM_WDAY=24; TM_YDAY=28; TM_ISDST=32;
|
||||||
|
|
||||||
|
{ --- atexit registry --- }
|
||||||
|
|
||||||
|
var
|
||||||
|
GAtExitFns: array[0..MAX_ATEXIT - 1] of Pointer;
|
||||||
|
GAtExitArgs: array[0..MAX_ATEXIT - 1] of Pointer;
|
||||||
|
GAtExitCount: Integer;
|
||||||
|
|
||||||
|
function __cxa_atexit(Func, Arg, DsoHandle: Pointer): Integer;
|
||||||
|
begin
|
||||||
|
if GAtExitCount >= MAX_ATEXIT then
|
||||||
|
begin
|
||||||
|
Result := -1;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
GAtExitFns[GAtExitCount] := Func;
|
||||||
|
GAtExitArgs[GAtExitCount] := Arg;
|
||||||
|
GAtExitCount := GAtExitCount + 1;
|
||||||
|
Result := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Call a registered handler: void (*func)(void* arg). The handler is invoked
|
||||||
|
with its arg in %rdi (a 0-arg Pascal handler simply ignores it). }
|
||||||
|
procedure CallHandler(Func, Arg: Pointer); assembler; nostackframe;
|
||||||
|
asm
|
||||||
|
movq %rsi, %rax { Arg -> scratch; Func is in %rdi }
|
||||||
|
movq %rdi, %r11 { Func }
|
||||||
|
movq %rax, %rdi { Arg -> first C arg }
|
||||||
|
jmp *%r11
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure _RunAtExitAndExit(Code: Integer);
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
{ LIFO. }
|
||||||
|
I := GAtExitCount - 1;
|
||||||
|
while I >= 0 do
|
||||||
|
begin
|
||||||
|
if GAtExitFns[I] <> nil then
|
||||||
|
CallHandler(GAtExitFns[I], GAtExitArgs[I]);
|
||||||
|
I := I - 1;
|
||||||
|
end;
|
||||||
|
_exit(Code);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Publish the bare `exit` symbol as a tail-call into _RunAtExitAndExit (Code is
|
||||||
|
already in %edi). }
|
||||||
|
procedure _libc2_exit(Code: Integer); assembler; nostackframe;
|
||||||
|
asm
|
||||||
|
.globl exit
|
||||||
|
exit:
|
||||||
|
jmp _RunAtExitAndExit
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure abort;
|
||||||
|
begin
|
||||||
|
kill(getpid(), SIGABRT);
|
||||||
|
_exit(134);
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ --- civil calendar (UTC), based on Howard Hinnant's days_from_civil --- }
|
||||||
|
|
||||||
|
{ Days since 1970-01-01 for a y/m/d (proleptic Gregorian). }
|
||||||
|
function DaysFromCivil(Y, M, D: Int64): Int64;
|
||||||
|
var
|
||||||
|
Era, Yoe, Doy, Doe: Int64;
|
||||||
|
YY: Int64;
|
||||||
|
begin
|
||||||
|
if M <= 2 then YY := Y - 1 else YY := Y;
|
||||||
|
if YY >= 0 then Era := YY div 400 else Era := (YY - 399) div 400;
|
||||||
|
Yoe := YY - Era * 400;
|
||||||
|
if M > 2 then Doy := (153 * (M - 3) + 2) div 5 + D - 1
|
||||||
|
else Doy := (153 * (M + 9) + 2) div 5 + D - 1;
|
||||||
|
Doe := Yoe * 365 + Yoe div 4 - Yoe div 100 + Doy;
|
||||||
|
Result := Era * 146097 + Doe - 719468;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ Inverse: y/m/d from days-since-epoch. }
|
||||||
|
procedure CivilFromDays(Z: Int64; var Y, M, D: Int64);
|
||||||
|
var
|
||||||
|
Era, Doe, Yoe, Doy, Mp: Int64;
|
||||||
|
begin
|
||||||
|
Z := Z + 719468;
|
||||||
|
if Z >= 0 then Era := Z div 146097 else Era := (Z - 146096) div 146097;
|
||||||
|
Doe := Z - Era * 146097;
|
||||||
|
Yoe := (Doe - Doe div 1460 + Doe div 36524 - Doe div 146096) div 365;
|
||||||
|
Y := Yoe + Era * 400;
|
||||||
|
Doy := Doe - (365 * Yoe + Yoe div 4 - Yoe div 100);
|
||||||
|
Mp := (5 * Doy + 2) div 153;
|
||||||
|
D := Doy - (153 * Mp + 2) div 5 + 1;
|
||||||
|
if Mp < 10 then M := Mp + 3 else M := Mp - 9;
|
||||||
|
if M <= 2 then Y := Y + 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure FillTm(T: Int64; Tm: Pointer);
|
||||||
|
var
|
||||||
|
Days, Secs, Y, M, D: Int64;
|
||||||
|
P: PInteger;
|
||||||
|
begin
|
||||||
|
Days := T div 86400;
|
||||||
|
Secs := T - Days * 86400;
|
||||||
|
if Secs < 0 then
|
||||||
|
begin
|
||||||
|
Secs := Secs + 86400;
|
||||||
|
Days := Days - 1;
|
||||||
|
end;
|
||||||
|
CivilFromDays(Days, Y, M, D);
|
||||||
|
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_SEC)); P^ := Integer(Secs mod 60);
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MIN)); P^ := Integer((Secs div 60) mod 60);
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_HOUR)); P^ := Integer(Secs div 3600);
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MDAY)); P^ := Integer(D);
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MON)); P^ := Integer(M - 1); { 0-based }
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_YEAR)); P^ := Integer(Y - 1900); { years since 1900 }
|
||||||
|
{ WDay: 1970-01-01 was a Thursday (4). }
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_WDAY));
|
||||||
|
P^ := Integer(((Days mod 7) + 4 + 7) mod 7);
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_YDAY));
|
||||||
|
P^ := Integer(Days - DaysFromCivil(Y, 1, 1));
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_ISDST)); P^ := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function gmtime_r(T: Pointer; Tm: Pointer): Pointer;
|
||||||
|
begin
|
||||||
|
FillTm(PInt64(T)^, Tm);
|
||||||
|
Result := Tm;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function localtime_r(T: Pointer; Tm: Pointer): Pointer;
|
||||||
|
begin
|
||||||
|
{ No timezone database in the freestanding RTL - treat local as UTC. }
|
||||||
|
Result := gmtime_r(T, Tm);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function timegm(Tm: Pointer): Int64;
|
||||||
|
var
|
||||||
|
Y, M, D, Hh, Mm, Ss: Int64;
|
||||||
|
P: PInteger;
|
||||||
|
begin
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_SEC)); Ss := P^;
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MIN)); Mm := P^;
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_HOUR)); Hh := P^;
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MDAY)); D := P^;
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_MON)); M := P^ + 1;
|
||||||
|
P := PInteger(Pointer(PChar(Tm) + TM_YEAR)); Y := P^ + 1900;
|
||||||
|
Result := DaysFromCivil(Y, M, D) * 86400 + Hh * 3600 + Mm * 60 + Ss;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ --- mkstemp --- }
|
||||||
|
|
||||||
|
function mkstemp(Template: PChar): Integer;
|
||||||
|
const
|
||||||
|
ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
var
|
||||||
|
Len, I, Tries, Got: Integer;
|
||||||
|
N: Int64;
|
||||||
|
RandBuf: array[0..5] of Byte;
|
||||||
|
Fd: Integer;
|
||||||
|
begin
|
||||||
|
Len := 0;
|
||||||
|
while (Template[Len] and $FF) <> 0 do Len := Len + 1;
|
||||||
|
if Len < 6 then Exit(-1);
|
||||||
|
Tries := 0;
|
||||||
|
while Tries < 256 do
|
||||||
|
begin
|
||||||
|
{ Fill all 6 bytes; getrandom may short-read, so loop until satisfied rather
|
||||||
|
than leave stale/zero bytes that would weaken the random suffix. }
|
||||||
|
Got := 0;
|
||||||
|
while Got < 6 do
|
||||||
|
begin
|
||||||
|
N := getrandom(@RandBuf[Got], 6 - Got, 0);
|
||||||
|
if N <= 0 then Continue; { EINTR / transient: retry }
|
||||||
|
Got := Got + Integer(N);
|
||||||
|
end;
|
||||||
|
for I := 0 to 5 do
|
||||||
|
Template[Len - 6 + I] := Ord(ALPHABET[(RandBuf[I] mod 62)]);
|
||||||
|
Fd := open(Template, O_RDWR or O_CREAT or O_EXCL, 384); { 0600 }
|
||||||
|
if Fd >= 0 then Exit(Fd);
|
||||||
|
Tries := Tries + 1;
|
||||||
|
end;
|
||||||
|
Result := -1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ --- system --- }
|
||||||
|
|
||||||
|
function system(Cmd: PChar): Integer;
|
||||||
|
var
|
||||||
|
Pid, Status: Integer;
|
||||||
|
Argv: array[0..3] of Pointer;
|
||||||
|
ShPath, DashC: array[0..15] of Byte;
|
||||||
|
begin
|
||||||
|
{ Build argv = /bin/sh, -c, cmd, NULL. The /bin/sh path and -c flag are short
|
||||||
|
literals copied into local byte buffers so they have stable addresses. }
|
||||||
|
ShPath[0]:=Ord('/'); ShPath[1]:=Ord('b'); ShPath[2]:=Ord('i'); ShPath[3]:=Ord('n');
|
||||||
|
ShPath[4]:=Ord('/'); ShPath[5]:=Ord('s'); ShPath[6]:=Ord('h'); ShPath[7]:=0;
|
||||||
|
DashC[0]:=Ord('-'); DashC[1]:=Ord('c'); DashC[2]:=0;
|
||||||
|
Argv[0] := @ShPath[0];
|
||||||
|
Argv[1] := @DashC[0];
|
||||||
|
Argv[2] := Pointer(Cmd);
|
||||||
|
Argv[3] := nil;
|
||||||
|
|
||||||
|
Pid := fork();
|
||||||
|
if Pid = 0 then
|
||||||
|
begin
|
||||||
|
{ Child: replace image; on failure exit 127 like the shell. Forward the
|
||||||
|
process environment so the spawned shell sees PATH/HOME/etc. }
|
||||||
|
execve(PChar(@ShPath[0]), @Argv[0], environ);
|
||||||
|
_exit(127);
|
||||||
|
end;
|
||||||
|
if Pid < 0 then Exit(-1);
|
||||||
|
Status := 0;
|
||||||
|
wait4(Pid, @Status, 0, nil);
|
||||||
|
Result := Status;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
|
|
@ -83,6 +83,17 @@ function mmap(Addr: Pointer; Length: Int64; Prot, Flags, Fd: Integer;
|
||||||
Offset: Int64): Pointer;
|
Offset: Int64): Pointer;
|
||||||
function munmap(Addr: Pointer; Length: Int64): Integer;
|
function munmap(Addr: Pointer; Length: Int64): Integer;
|
||||||
|
|
||||||
|
{ mremap(old, oldsz, newsz, flags): FreeBSD has NO in-place remap syscall, so
|
||||||
|
this is a stub that always reports failure (MAP_FAILED = -1). runtime.mem's
|
||||||
|
large-realloc path already treats an mremap failure as "grow the slow way":
|
||||||
|
it allocates a fresh region, memcpy's the old contents across, and frees the
|
||||||
|
old one (see the MapFailed fall-through in _BlaiseReallocMem). Exporting the
|
||||||
|
`mremap` symbol as a failing stub therefore gives the allocator its no-mremap
|
||||||
|
grow fallback with no allocator changes. Signature matches the Linux leaf so
|
||||||
|
runtime.mem's `external name 'mremap'` binds on both targets. }
|
||||||
|
function mremap(OldAddr: Pointer; OldSize, NewSize: Int64; Flags: Integer;
|
||||||
|
NewAddr: Pointer): Pointer;
|
||||||
|
|
||||||
{ Time. }
|
{ Time. }
|
||||||
function nanosleep(Req, Rem: Pointer): Integer;
|
function nanosleep(Req, Rem: Pointer): Integer;
|
||||||
function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
||||||
|
|
@ -93,6 +104,9 @@ function execve(Path: PChar; Argv, Envp: Pointer): Integer;
|
||||||
function wait4(Pid: Integer; Status: Pointer; Options: Integer;
|
function wait4(Pid: Integer; Status: Pointer; Options: Integer;
|
||||||
Rusage: Pointer): Integer;
|
Rusage: Pointer): Integer;
|
||||||
function kill(Pid, Sig: Integer): Integer;
|
function kill(Pid, Sig: Integer): Integer;
|
||||||
|
{ getrandom(buf, count, flags): fill buf with count random bytes; returns the
|
||||||
|
count written or -errno. Used by mkstemp's random suffix. }
|
||||||
|
function getrandom(Buf: Pointer; Count: Int64; Flags: Integer): Int64;
|
||||||
{ Raw __getcwd(2): fills Buf (up to Size); returns 0 on success or -errno. The
|
{ Raw __getcwd(2): fills Buf (up to Size); returns 0 on success or -errno. The
|
||||||
wrapper in the posix layer adapts that to libc's buffer-pointer contract. }
|
wrapper in the posix layer adapts that to libc's buffer-pointer contract. }
|
||||||
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
||||||
|
|
@ -134,6 +148,7 @@ const
|
||||||
SYS_mkdir = 136;
|
SYS_mkdir = 136;
|
||||||
SYS_rmdir = 137;
|
SYS_rmdir = 137;
|
||||||
SYS_getcwd = 326; { __getcwd }
|
SYS_getcwd = 326; { __getcwd }
|
||||||
|
SYS_getrandom = 563;
|
||||||
SYS_clock_gettime = 232;
|
SYS_clock_gettime = 232;
|
||||||
SYS_nanosleep = 240;
|
SYS_nanosleep = 240;
|
||||||
SYS_mmap = 477; { ino64 (legacy freebsd6 mmap = 197) }
|
SYS_mmap = 477; { ino64 (legacy freebsd6 mmap = 197) }
|
||||||
|
|
@ -360,6 +375,14 @@ asm
|
||||||
ret
|
ret
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ mremap: no FreeBSD in-place-remap syscall. Always return MAP_FAILED (-1) so
|
||||||
|
the allocator takes its alloc-new-copy-free fallback (see the interface note). }
|
||||||
|
function mremap(OldAddr: Pointer; OldSize, NewSize: Int64; Flags: Integer;
|
||||||
|
NewAddr: Pointer): Pointer;
|
||||||
|
begin
|
||||||
|
Result := Pointer(-1);
|
||||||
|
end;
|
||||||
|
|
||||||
function nanosleep(Req, Rem: Pointer): Integer;
|
function nanosleep(Req, Rem: Pointer): Integer;
|
||||||
assembler; nostackframe;
|
assembler; nostackframe;
|
||||||
asm
|
asm
|
||||||
|
|
@ -429,6 +452,17 @@ asm
|
||||||
ret
|
ret
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function getrandom(Buf: Pointer; Count: Int64; Flags: Integer): Int64;
|
||||||
|
assembler; nostackframe;
|
||||||
|
asm
|
||||||
|
movq $563, %rax { SYS_getrandom }
|
||||||
|
syscall
|
||||||
|
jae .Lok_getrandom
|
||||||
|
negq %rax
|
||||||
|
.Lok_getrandom:
|
||||||
|
ret
|
||||||
|
end;
|
||||||
|
|
||||||
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
||||||
assembler; nostackframe;
|
assembler; nostackframe;
|
||||||
asm
|
asm
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ type
|
||||||
CF-error-translation idiom (jae + negq). Standalone — no FreeBSD
|
CF-error-translation idiom (jae + negq). Standalone — no FreeBSD
|
||||||
emulation, header/byte shape only. }
|
emulation, header/byte shape only. }
|
||||||
procedure TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers;
|
procedure TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers;
|
||||||
|
{ Step 4b: the FreeBSD leaf additions — getrandom uses the FreeBSD number
|
||||||
|
(563, not Linux's 318) and the mremap stub is a plain function that returns
|
||||||
|
MAP_FAILED (-1) so runtime.mem takes its alloc-copy-free grow fallback (no
|
||||||
|
in-place remap syscall on FreeBSD). Standalone byte/symbol shape. }
|
||||||
|
procedure TestLink_FreeBSDSyscallLeaf_GetrandomAndMremapStub;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TDynLinkerTests = class(TTestCase)
|
TDynLinkerTests = class(TTestCase)
|
||||||
|
|
@ -1209,6 +1214,62 @@ begin
|
||||||
Pos(Chr($49) + Chr($89) + Chr($CA), Bytes) >= 0);
|
Pos(Chr($49) + Chr($89) + Chr($CA), Bytes) >= 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TLinkerE2ETests.TestLink_FreeBSDSyscallLeaf_GetrandomAndMremapStub;
|
||||||
|
const
|
||||||
|
{ getrandom is a raw syscall with the FreeBSD number 563 (Linux's is 318).
|
||||||
|
mremap has no FreeBSD syscall, so the leaf's stub just returns MAP_FAILED
|
||||||
|
(-1) — a mov of -1 into %rax then ret; runtime.mem's realloc path treats
|
||||||
|
that as "grow the slow way". A leading _start keeps the entry resolvable. }
|
||||||
|
FixtureAsm =
|
||||||
|
'.text' + LineEnding +
|
||||||
|
'.globl _start' + LineEnding +
|
||||||
|
'_start:' + LineEnding +
|
||||||
|
' callq getrandom' + LineEnding +
|
||||||
|
' callq mremap' + LineEnding +
|
||||||
|
' xorl %edi, %edi' + LineEnding +
|
||||||
|
' movq $1, %rax' + LineEnding + { SYS_exit }
|
||||||
|
' syscall' + LineEnding +
|
||||||
|
' hlt' + LineEnding +
|
||||||
|
'.globl getrandom' + LineEnding +
|
||||||
|
'getrandom:' + LineEnding +
|
||||||
|
' movq $563, %rax' + LineEnding + { SYS_getrandom (FreeBSD) }
|
||||||
|
' syscall' + LineEnding +
|
||||||
|
' jae .Lok_gr' + LineEnding +
|
||||||
|
' negq %rax' + LineEnding +
|
||||||
|
'.Lok_gr:' + LineEnding +
|
||||||
|
' ret' + LineEnding +
|
||||||
|
'.globl mremap' + LineEnding +
|
||||||
|
'mremap:' + LineEnding +
|
||||||
|
' movq $-1, %rax' + LineEnding + { MAP_FAILED — no FreeBSD mremap }
|
||||||
|
' ret' + LineEnding;
|
||||||
|
function MovRaxImm4b(AImm: Integer): string;
|
||||||
|
begin
|
||||||
|
Result := Chr($48) + Chr($C7) + Chr($C0) +
|
||||||
|
Chr(AImm and $FF) + Chr((AImm shr 8) and $FF) +
|
||||||
|
Chr((AImm shr 16) and $FF) + Chr((AImm shr 24) and $FF);
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
Lk: TLinker;
|
||||||
|
Obj: TElfObjectFile;
|
||||||
|
Bytes: string;
|
||||||
|
begin
|
||||||
|
Lk := TLinker.Create(FreeBSDX86_64Target());
|
||||||
|
try
|
||||||
|
Obj := ParseElfObject(AssembleToBytes(FixtureAsm), 'fbsys2.o');
|
||||||
|
Lk.AddOwnedObject(Obj);
|
||||||
|
Bytes := Lk.LinkToBytes('_start');
|
||||||
|
AssertTrue('getrandom defined', Lk.AddrOfSymbol('getrandom') > 0);
|
||||||
|
AssertTrue('mremap defined', Lk.AddrOfSymbol('mremap') > 0);
|
||||||
|
finally
|
||||||
|
Lk.Free();
|
||||||
|
end;
|
||||||
|
{ getrandom uses the FreeBSD number 563 — NOT Linux's 318. }
|
||||||
|
AssertTrue('SYS_getrandom=563 movq imm present', Pos(MovRaxImm4b(563), Bytes) >= 0);
|
||||||
|
AssertTrue('Linux SYS_getrandom=318 NOT present', Pos(MovRaxImm4b(318), Bytes) < 0);
|
||||||
|
{ mremap stub loads -1 (movq $-1,%rax = 48 C7 C0 FF FF FF FF). }
|
||||||
|
AssertTrue('mremap stub returns -1 (MAP_FAILED)', Pos(MovRaxImm4b(-1), Bytes) >= 0);
|
||||||
|
end;
|
||||||
|
|
||||||
{ ---- TDynLinkerTests ---- }
|
{ ---- TDynLinkerTests ---- }
|
||||||
|
|
||||||
function TDynLinkerTests.ProjectRoot: string;
|
function TDynLinkerTests.ProjectRoot: string;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue