feat(rtl): FreeBSD syscall leaf — file/process leaves (Step 4a)
Grow runtime.syscall.freebsd.pas from the Step-3 minimum (_exit/write) to
the file/process syscall leaves, mirroring runtime.syscall.linux but with
FreeBSD syscall numbers and FreeBSD's carry-flag error convention.
Added leaves: open close read lseek fstat stat mkdir rmdir unlink rename
chdir chmod getpid dup2 pipe mmap munmap nanosleep clock_gettime fork
execve wait4 kill getcwd. Each emits the raw `syscall` (number in %rax,
args rdi/rsi/rdx/r10/r8/r9, %rcx->%r10 for >=4 args) and translates
FreeBSD's error convention (CF set, positive errno in %rax) into the
-errno the Pascal call sites in rtl.platform.posix expect:
movq $N, %rax ; syscall ; jae .Lok ; negq %rax ; .Lok: ret
Syscall numbers are the FreeBSD 14 ino64 ABI (mmap=477 lseek=478
fstat=551, NOT the freebsd6/11 compat 197/199/189). FreeBSD's ino64 ABI
has no plain `stat` syscall, so stat(path,buf) is a wrapper over
fstatat(AT_FDCWD,path,buf,0)=552; pipe(fds) wraps pipe2(fds,0)=542. All
numbers verified against release/14.0.0 sys/sys/syscall.h.
The unit is standalone (only runtime.start.static.freebsd references it,
itself outside the Linux build graph), so the Linux self-host is
untouched. Test TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers links a
fixture and asserts the bare POSIX symbols are defined and the FreeBSD
syscall-number bytes + negq (CF xlate) + mov %rcx,%r10 are present.
Scope: file/process only; memcpy/date/allocator/threads are Step 4b/4c.
This commit is contained in:
parent
77197739de
commit
2662535579
|
|
@ -17,56 +17,434 @@ unit runtime.syscall.freebsd;
|
|||
// the Linux leaf, so the resulting binary reaches the kernel with raw
|
||||
// `syscall`s and needs no libc, no PT_INTERP.
|
||||
//
|
||||
// Step 3 scope: the minimum the freestanding _start needs — `_exit`, `write`,
|
||||
// and the `environ` global. The remaining file/process/thread leaves grow here
|
||||
// in Step 4.
|
||||
// Step 4a scope: the file/process syscall leaves (open/close/read/write/lseek,
|
||||
// stat/fstat/mkdir/rmdir/unlink/rename/chdir/chmod, getpid/dup2/pipe/fork/
|
||||
// execve/wait4/kill/getcwd, mmap/munmap/nanosleep/clock_gettime), plus the
|
||||
// pre-existing `_exit`, `_sys_write` and `environ`. The harder leaves
|
||||
// (memcpy/memset, date math, the mmap allocator, threads) grow here in Steps
|
||||
// 4b/4c.
|
||||
//
|
||||
// System V AMD64 -> FreeBSD syscall ABI:
|
||||
// * syscall number in %rax; args in %rdi,%rsi,%rdx,%r10,%r8,%r9 (the 4th C
|
||||
// arg %rcx must be moved to %r10 — the `syscall` instruction clobbers %rcx).
|
||||
// * The syscall NUMBERS differ from Linux (exit=1, write=4, read=3, open=5,
|
||||
// close=6 — see sys/syscall.h); they are NOT Linux's.
|
||||
// * Errors are reported via the CARRY FLAG (errno returned in %rax), not the
|
||||
// -errno convention Linux uses. The error-translating wrappers that need it
|
||||
// arrive in Step 4; `_exit` never returns and `write` on success returns the
|
||||
// byte count, so neither needs CF handling here.
|
||||
// close=6 — see sys/syscall.h); they are NOT Linux's. FreeBSD 12 renumbered
|
||||
// stat/fstat/lseek/mmap for the 64-bit-inode ("ino64") ABI; this leaf uses
|
||||
// the ino64 numbers to match the FreeBSD 14 224-byte `struct stat` that the
|
||||
// rtl.platform.layout.freebsd adapter expects.
|
||||
// * ERROR CONVENTION — the key FreeBSD difference from Linux. FreeBSD reports
|
||||
// errors via the CARRY FLAG: on error CF is set and %rax holds a POSITIVE
|
||||
// errno; on success CF is clear and %rax is the result. The Pascal call
|
||||
// sites in rtl.platform.posix expect the Linux -errno convention (they test
|
||||
// `if Fd < 0`, `< 0`, `<> 0`). So every leaf that can fail translates:
|
||||
// after `syscall`, `jae` past a `negq %rax` when CF is clear (success —
|
||||
// leave %rax untouched); when CF is set, `negq %rax` turns the positive
|
||||
// errno into -errno. Each body uses a UNIQUE local label (.Lok_xxx) so the
|
||||
// forward-local jumps do not collide when the unit assembles to one object.
|
||||
//
|
||||
// `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;
|
||||
{ FreeBSD's ino64 ABI has NO plain `stat` syscall — it is expressed as
|
||||
fstatat(AT_FDCWD, path, buf, 0). This wrapper sets up that call so the bare
|
||||
`stat` symbol posix imports still resolves. }
|
||||
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;
|
||||
function chmod(Path: PChar; Mode: Integer): Integer;
|
||||
|
||||
{ Process. }
|
||||
function getpid: Integer;
|
||||
function dup2(OldFd, NewFd: Integer): Integer;
|
||||
{ FreeBSD deprecated the 2-arg pipe(2) (legacy 42); this calls pipe2(fds, 0). }
|
||||
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 control + raw primitives the higher-level wrappers build on. }
|
||||
function fork: Integer;
|
||||
function execve(Path: PChar; Argv, Envp: Pointer): Integer;
|
||||
function wait4(Pid: Integer; Status: Pointer; Options: Integer;
|
||||
Rusage: Pointer): Integer;
|
||||
function kill(Pid, Sig: Integer): Integer;
|
||||
{ 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. }
|
||||
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
||||
|
||||
{ _exit(2) — SYS_exit = 1. Terminates the process; never returns. The
|
||||
unit-level routine name `_exit` already emits the unmangled `_exit` symbol that
|
||||
satisfies posix's `external name '_exit'`. }
|
||||
procedure _exit(Code: Integer);
|
||||
|
||||
{ The current process environment (envp), captured by _start. rtl.platform.posix
|
||||
/ getenv walk this NULL-terminated array. Defined here (as on Linux) so the
|
||||
kernel leaf owns it. }
|
||||
var
|
||||
environ: Pointer;
|
||||
|
||||
{ write(fd, buf, count) — SYS_write = 4. The Pascal name is _sys_write because
|
||||
`write` collides with the Write/WriteLn builtin; the asm body emits a bare
|
||||
`write` label so the linker symbol matches posix's `external name 'write'`. }
|
||||
function _sys_write(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
|
||||
{ _exit(code) — SYS_exit = 1. Terminates the process; never returns. The
|
||||
unit-level routine name `_exit` already emits the unmangled `_exit` symbol that
|
||||
satisfies posix's `external name '_exit'`. }
|
||||
procedure _exit(Code: Integer);
|
||||
|
||||
implementation
|
||||
|
||||
{ ---- FreeBSD x86_64 syscall numbers (FreeBSD 14 sys/sys/syscall.h). ----
|
||||
Verified against
|
||||
https://raw.githubusercontent.com/freebsd/freebsd-src/release/14.0.0/sys/sys/syscall.h
|
||||
ino64 variants used where FreeBSD 12 renumbered (fstat=551, lseek=478,
|
||||
mmap=477; stat has no ino64 syscall — expressed via fstatat=552). }
|
||||
const
|
||||
SYS_exit = 1;
|
||||
SYS_fork = 2;
|
||||
SYS_read = 3;
|
||||
SYS_write = 4;
|
||||
SYS_open = 5;
|
||||
SYS_close = 6;
|
||||
SYS_wait4 = 7;
|
||||
SYS_unlink = 10;
|
||||
SYS_chdir = 12;
|
||||
SYS_chmod = 15;
|
||||
SYS_getpid = 20;
|
||||
SYS_kill = 37;
|
||||
SYS_munmap = 73;
|
||||
SYS_dup2 = 90;
|
||||
SYS_rename = 128;
|
||||
SYS_mkdir = 136;
|
||||
SYS_rmdir = 137;
|
||||
SYS_getcwd = 326; { __getcwd }
|
||||
SYS_clock_gettime = 232;
|
||||
SYS_nanosleep = 240;
|
||||
SYS_mmap = 477; { ino64 (legacy freebsd6 mmap = 197) }
|
||||
SYS_lseek = 478; { ino64 (legacy lseek = 199) }
|
||||
SYS_fstat = 551; { ino64 (legacy freebsd11_fstat = 189) }
|
||||
SYS_fstatat = 552; { ino64 (legacy freebsd11_fstatat = 493) }
|
||||
SYS_pipe2 = 542; { pipe(2) legacy 42 deprecated }
|
||||
SYS_execve = 59;
|
||||
AT_FDCWD = -100; { dirfd for path-relative *at() syscalls at cwd }
|
||||
{ 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 $5, %rax { SYS_open }
|
||||
syscall
|
||||
jae .Lok_open { CF clear = success }
|
||||
negq %rax { CF set = error: errno -> -errno }
|
||||
.Lok_open:
|
||||
ret
|
||||
end;
|
||||
|
||||
function close(Fd: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $6, %rax { SYS_close }
|
||||
syscall
|
||||
jae .Lok_close
|
||||
negq %rax
|
||||
.Lok_close:
|
||||
ret
|
||||
end;
|
||||
|
||||
function read(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $3, %rax { SYS_read }
|
||||
syscall
|
||||
jae .Lok_read
|
||||
negq %rax
|
||||
.Lok_read:
|
||||
ret
|
||||
end;
|
||||
|
||||
{ write CAN fail; posix's write-loop expects a negative return on error, so it
|
||||
gets CF-translation too. }
|
||||
function _sys_write(Fd: Integer; Buf: Pointer; Count: Int64): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
.globl write
|
||||
write:
|
||||
movq $4, %rax { SYS_write }
|
||||
movq $4, %rax { SYS_write }
|
||||
syscall
|
||||
jae .Lok_write
|
||||
negq %rax
|
||||
.Lok_write:
|
||||
ret
|
||||
end;
|
||||
|
||||
function lseek(Fd: Integer; Offset: Int64; Whence: Integer): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $478, %rax { SYS_lseek (ino64) }
|
||||
syscall
|
||||
jae .Lok_lseek
|
||||
negq %rax
|
||||
.Lok_lseek:
|
||||
ret
|
||||
end;
|
||||
|
||||
function fstat(Fd: Integer; Buf: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $551, %rax { SYS_fstat (ino64) }
|
||||
syscall
|
||||
jae .Lok_fstat
|
||||
negq %rax
|
||||
.Lok_fstat:
|
||||
ret
|
||||
end;
|
||||
|
||||
{ stat(path, buf) -> fstatat(AT_FDCWD, path, buf, 0). Incoming: %rdi=Path,
|
||||
%rsi=Buf. Shift to the fstatat argument layout: dirfd(%rdi)=AT_FDCWD,
|
||||
path(%rsi)=Path, buf(%rdx)=Buf, flag(%r10)=0. arg4 goes straight to %r10
|
||||
(the syscall's 4th kernel register); no %rcx move is needed since we build
|
||||
the arguments here rather than receive four C args. }
|
||||
function stat(Path: PChar; Buf: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq %rsi, %rdx { buf -> arg3 }
|
||||
movq %rdi, %rsi { path -> arg2 }
|
||||
movq $-100, %rdi { AT_FDCWD -> arg1 (dirfd) }
|
||||
xorq %r10, %r10 { flag = 0 -> arg4 }
|
||||
movq $552, %rax { SYS_fstatat (ino64) }
|
||||
syscall
|
||||
jae .Lok_stat
|
||||
negq %rax
|
||||
.Lok_stat:
|
||||
ret
|
||||
end;
|
||||
|
||||
function mkdir(Path: PChar; Mode: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $136, %rax { SYS_mkdir }
|
||||
syscall
|
||||
jae .Lok_mkdir
|
||||
negq %rax
|
||||
.Lok_mkdir:
|
||||
ret
|
||||
end;
|
||||
|
||||
function chmod(Path: PChar; Mode: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $15, %rax { SYS_chmod }
|
||||
syscall
|
||||
jae .Lok_chmod
|
||||
negq %rax
|
||||
.Lok_chmod:
|
||||
ret
|
||||
end;
|
||||
|
||||
function rmdir(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $137, %rax { SYS_rmdir }
|
||||
syscall
|
||||
jae .Lok_rmdir
|
||||
negq %rax
|
||||
.Lok_rmdir:
|
||||
ret
|
||||
end;
|
||||
|
||||
function unlink(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $10, %rax { SYS_unlink }
|
||||
syscall
|
||||
jae .Lok_unlink
|
||||
negq %rax
|
||||
.Lok_unlink:
|
||||
ret
|
||||
end;
|
||||
|
||||
function rename(OldPath, NewPath: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $128, %rax { SYS_rename }
|
||||
syscall
|
||||
jae .Lok_rename
|
||||
negq %rax
|
||||
.Lok_rename:
|
||||
ret
|
||||
end;
|
||||
|
||||
function chdir(Path: PChar): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $12, %rax { SYS_chdir }
|
||||
syscall
|
||||
jae .Lok_chdir
|
||||
negq %rax
|
||||
.Lok_chdir:
|
||||
ret
|
||||
end;
|
||||
|
||||
function getpid: Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $20, %rax { SYS_getpid }
|
||||
syscall
|
||||
ret { getpid never fails; no CF translation }
|
||||
end;
|
||||
|
||||
function dup2(OldFd, NewFd: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $90, %rax { SYS_dup2 }
|
||||
syscall
|
||||
jae .Lok_dup2
|
||||
negq %rax
|
||||
.Lok_dup2:
|
||||
ret
|
||||
end;
|
||||
|
||||
{ pipe(fds) -> pipe2(fds, 0). Incoming: %rdi=Fds; add flags=0 in %rsi. }
|
||||
function pipe(Fds: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
xorq %rsi, %rsi { flags = 0 }
|
||||
movq $542, %rax { SYS_pipe2 }
|
||||
syscall
|
||||
jae .Lok_pipe
|
||||
negq %rax
|
||||
.Lok_pipe:
|
||||
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 $477, %rax { SYS_mmap (ino64) }
|
||||
syscall
|
||||
jae .Lok_mmap
|
||||
negq %rax
|
||||
.Lok_mmap:
|
||||
ret
|
||||
end;
|
||||
|
||||
function munmap(Addr: Pointer; Length: Int64): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $73, %rax { SYS_munmap }
|
||||
syscall
|
||||
jae .Lok_munmap
|
||||
negq %rax
|
||||
.Lok_munmap:
|
||||
ret
|
||||
end;
|
||||
|
||||
function nanosleep(Req, Rem: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $240, %rax { SYS_nanosleep }
|
||||
syscall
|
||||
jae .Lok_nanosleep
|
||||
negq %rax
|
||||
.Lok_nanosleep:
|
||||
ret
|
||||
end;
|
||||
|
||||
function clock_gettime(ClockId: Integer; Ts: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $232, %rax { SYS_clock_gettime }
|
||||
syscall
|
||||
jae .Lok_clock_gettime
|
||||
negq %rax
|
||||
.Lok_clock_gettime:
|
||||
ret
|
||||
end;
|
||||
|
||||
function fork: Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $2, %rax { SYS_fork }
|
||||
syscall
|
||||
jae .Lok_fork
|
||||
negq %rax
|
||||
.Lok_fork:
|
||||
ret
|
||||
end;
|
||||
|
||||
function execve(Path: PChar; Argv, Envp: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $59, %rax { SYS_execve }
|
||||
syscall
|
||||
jae .Lok_execve
|
||||
negq %rax
|
||||
.Lok_execve:
|
||||
ret
|
||||
end;
|
||||
|
||||
{ wait4 has 4 args; arg4 (Rusage) arrives in %rcx -> move to %r10. }
|
||||
function wait4(Pid: Integer; Status: Pointer; Options: Integer;
|
||||
Rusage: Pointer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq %rcx, %r10
|
||||
movq $7, %rax { SYS_wait4 }
|
||||
syscall
|
||||
jae .Lok_wait4
|
||||
negq %rax
|
||||
.Lok_wait4:
|
||||
ret
|
||||
end;
|
||||
|
||||
function kill(Pid, Sig: Integer): Integer;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $37, %rax { SYS_kill }
|
||||
syscall
|
||||
jae .Lok_kill
|
||||
negq %rax
|
||||
.Lok_kill:
|
||||
ret
|
||||
end;
|
||||
|
||||
function sys_getcwd(Buf: PChar; Size: Int64): Int64;
|
||||
assembler; nostackframe;
|
||||
asm
|
||||
movq $326, %rax { SYS___getcwd }
|
||||
syscall
|
||||
jae .Lok_getcwd
|
||||
negq %rax
|
||||
.Lok_getcwd:
|
||||
ret
|
||||
end;
|
||||
|
||||
{ _exit(code) — SYS_exit = 1. Terminates the process; never returns, so no
|
||||
CF translation is needed. }
|
||||
procedure _exit(Code: Integer); assembler; nostackframe;
|
||||
asm
|
||||
movq $1, %rax { SYS_exit }
|
||||
movq $1, %rax { SYS_exit }
|
||||
syscall
|
||||
ret
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,13 @@ type
|
|||
PT_INTERP — the Strategy-B shape a cross-compiled FreeBSD binary needs.
|
||||
Asserts header shape only (it cannot run on the Linux host). }
|
||||
procedure TestLink_FreeBSDStart_StaticExecShape;
|
||||
{ Step 4a: a fixture that mirrors the runtime.syscall.freebsd file/process
|
||||
leaves (open/read/write/mmap/stat) links under the FreeBSD target with the
|
||||
bare POSIX symbols DEFINED, and the linked .text carries the FreeBSD
|
||||
ino64 syscall NUMBERS (open=5, write=4, mmap=477, fstatat=552) plus the
|
||||
CF-error-translation idiom (jae + negq). Standalone — no FreeBSD
|
||||
emulation, header/byte shape only. }
|
||||
procedure TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers;
|
||||
end;
|
||||
|
||||
TDynLinkerTests = class(TTestCase)
|
||||
|
|
@ -1090,6 +1097,118 @@ begin
|
|||
AssertTrue('static ET_EXEC must have no PT_INTERP', not FoundInterp);
|
||||
end;
|
||||
|
||||
procedure TLinkerE2ETests.TestLink_FreeBSDSyscallLeaf_SymbolsAndNumbers;
|
||||
const
|
||||
{ Mirrors the runtime.syscall.freebsd leaf bodies byte-for-byte (a handful of
|
||||
representative leaves): each is `movq $N, %rax; syscall; jae ok; negq %rax;
|
||||
ok: ret`, with mmap's `movq %rcx, %r10` and stat lowered to
|
||||
fstatat(AT_FDCWD, path, buf, 0). Names are the bare POSIX symbols posix
|
||||
imports. A leading _start keeps the linker's entry resolvable. }
|
||||
FixtureAsm =
|
||||
'.text' + LineEnding +
|
||||
'.globl _start' + LineEnding +
|
||||
'_start:' + LineEnding +
|
||||
' callq open' + LineEnding +
|
||||
' callq read' + LineEnding +
|
||||
' callq write' + LineEnding +
|
||||
' callq mmap' + LineEnding +
|
||||
' callq stat' + LineEnding +
|
||||
' xorl %edi, %edi' + LineEnding +
|
||||
' movq $1, %rax' + LineEnding + { SYS_exit }
|
||||
' syscall' + LineEnding +
|
||||
' hlt' + LineEnding +
|
||||
'.globl open' + LineEnding +
|
||||
'open:' + LineEnding +
|
||||
' movq $5, %rax' + LineEnding + { SYS_open }
|
||||
' syscall' + LineEnding +
|
||||
' jae .Lok_open' + LineEnding +
|
||||
' negq %rax' + LineEnding +
|
||||
'.Lok_open:' + LineEnding +
|
||||
' ret' + LineEnding +
|
||||
'.globl read' + LineEnding +
|
||||
'read:' + LineEnding +
|
||||
' movq $3, %rax' + LineEnding + { SYS_read }
|
||||
' syscall' + LineEnding +
|
||||
' jae .Lok_read' + LineEnding +
|
||||
' negq %rax' + LineEnding +
|
||||
'.Lok_read:' + LineEnding +
|
||||
' ret' + LineEnding +
|
||||
'.globl write' + LineEnding +
|
||||
'write:' + LineEnding +
|
||||
' movq $4, %rax' + LineEnding + { SYS_write }
|
||||
' syscall' + LineEnding +
|
||||
' jae .Lok_write' + LineEnding +
|
||||
' negq %rax' + LineEnding +
|
||||
'.Lok_write:' + LineEnding +
|
||||
' ret' + LineEnding +
|
||||
'.globl mmap' + LineEnding +
|
||||
'mmap:' + LineEnding +
|
||||
' movq %rcx, %r10' + LineEnding +
|
||||
' movq $477, %rax' + LineEnding + { SYS_mmap (ino64) }
|
||||
' syscall' + LineEnding +
|
||||
' jae .Lok_mmap' + LineEnding +
|
||||
' negq %rax' + LineEnding +
|
||||
'.Lok_mmap:' + LineEnding +
|
||||
' ret' + LineEnding +
|
||||
'.globl stat' + LineEnding +
|
||||
'stat:' + LineEnding +
|
||||
' movq %rsi, %rdx' + LineEnding +
|
||||
' movq %rdi, %rsi' + LineEnding +
|
||||
' movq $-100, %rdi' + LineEnding + { AT_FDCWD }
|
||||
' xorq %r10, %r10' + LineEnding +
|
||||
' movq $552, %rax' + LineEnding + { SYS_fstatat (ino64) }
|
||||
' syscall' + LineEnding +
|
||||
' jae .Lok_stat' + LineEnding +
|
||||
' negq %rax' + LineEnding +
|
||||
'.Lok_stat:' + LineEnding +
|
||||
' ret' + LineEnding;
|
||||
{ Encoded `movq $imm32, %rax` = 48 C7 C0 <imm32-LE>. These byte strings must
|
||||
appear in the linked .text if the correct syscall number was emitted. }
|
||||
function MovRaxImm(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), 'fbsys.o');
|
||||
Lk.AddOwnedObject(Obj);
|
||||
Bytes := Lk.LinkToBytes('_start');
|
||||
|
||||
{ 1. The bare POSIX leaf symbols must be DEFINED (non-zero address). }
|
||||
AssertTrue('open defined', Lk.AddrOfSymbol('open') > 0);
|
||||
AssertTrue('read defined', Lk.AddrOfSymbol('read') > 0);
|
||||
AssertTrue('write defined', Lk.AddrOfSymbol('write') > 0);
|
||||
AssertTrue('mmap defined', Lk.AddrOfSymbol('mmap') > 0);
|
||||
AssertTrue('stat defined', Lk.AddrOfSymbol('stat') > 0);
|
||||
finally
|
||||
Lk.Free();
|
||||
end;
|
||||
|
||||
{ 2. FreeBSD ino64 syscall NUMBERS present in the linked .text as the
|
||||
`movq $N, %rax` encoding — NOT Linux's numbers. }
|
||||
AssertTrue('SYS_open=5 movq imm present', Pos(MovRaxImm(5), Bytes) >= 0);
|
||||
AssertTrue('SYS_read=3 movq imm present', Pos(MovRaxImm(3), Bytes) >= 0);
|
||||
AssertTrue('SYS_write=4 movq imm present', Pos(MovRaxImm(4), Bytes) >= 0);
|
||||
AssertTrue('SYS_mmap=477 movq imm present', Pos(MovRaxImm(477), Bytes) >= 0);
|
||||
AssertTrue('SYS_fstatat=552 movq imm present (stat via fstatat)',
|
||||
Pos(MovRaxImm(552), Bytes) >= 0);
|
||||
|
||||
{ 3. The CF-error-translation idiom: negq %rax (48 F7 D8) must appear. This
|
||||
is the FreeBSD-specific errno translation the Linux leaf does not emit. }
|
||||
AssertTrue('negq %rax (CF translation) present',
|
||||
Pos(Chr($48) + Chr($F7) + Chr($D8), Bytes) >= 0);
|
||||
{ mmap's arg4 shuffle: movq %rcx, %r10 (49 89 CA). }
|
||||
AssertTrue('movq %rcx,%r10 (mmap arg4) present',
|
||||
Pos(Chr($49) + Chr($89) + Chr($CA), Bytes) >= 0);
|
||||
end;
|
||||
|
||||
{ ---- TDynLinkerTests ---- }
|
||||
|
||||
function TDynLinkerTests.ProjectRoot: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue