fix(rtl,asm): address pre-landing review findings on the static leaf

Pre-landing review fixes, all in the direct-syscall / threads-leaf series:

- runtime.thread.static.linux + runtime.start.static.linux: reclaim the
  per-thread TLS block when clone(2) fails after BuildThreadTLS (previously only
  the stack mapping was unmapped, leaking one TLS mapping per failed
  pthread_create).  Add a symmetric FreeThreadTLS that recovers the mmap base
  from the thread pointer using the same alignment math BuildThreadTLS used.
- runtime.libc2.linux: system() now forwards `environ` to execve instead of
  nil, so the spawned /bin/sh sees PATH/HOME/etc. (was running with an empty
  environment under --static).
- runtime.libc2.linux: mkstemp loops getrandom until all 6 bytes are filled
  rather than ignoring a short read that would weaken the random suffix.
- runtime.libc.linux: drop execvp's dead HasSlash branch (both arms called the
  identical execve); keep the single call + the no-PATH-search note.
- runtime.thread.static.linux: correct the stale _clone_thread comment that
  claimed args pass through callee-saved regs (they are seeded on the child
  stack and popped).
- blaise.assembler.x86_64: in the asm brace-comment strip, splice without
  trimming mid-scan (a left-trim shifted the string under the scan index) and
  trim once at the end, so a second brace comment on the same line is still
  found.
- blaise.codegen.native.driver: replace a non-ASCII ellipsis in an added
  comment with '...' (ASCII-only source rule).

Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK; self-hosted
compiler builds clean (0 spurious lock prefixes); static threads binary runs
counter=80000 PASS (deterministic across runs); no unit/codegen/assembler/linker
test regressions.
This commit is contained in:
Graeme Geldenhuys 2026-06-26 18:02:51 +01:00
parent b03f84c4c3
commit e218b6978c
6 changed files with 47 additions and 31 deletions

View file

@ -679,14 +679,17 @@ begin
Q := P + 1;
while (Q < Length(S)) and (S[Q] <> 125) do
Q := Q + 1;
{ Splice out [P..Q] WITHOUT trimming mid-scan (a left-trim would shift the
string under P); resume from P so a following brace is still found. }
if Q < Length(S) then
S := TrimStr(Copy(S, 0, P) + ' ' + Copy(S, Q + 1, Length(S)))
S := Copy(S, 0, P) + ' ' + Copy(S, Q + 1, Length(S))
else
S := TrimStr(Copy(S, 0, P));
S := Copy(S, 0, P);
end
else
P := P + 1;
end;
S := TrimStr(S);
if Length(S) = 0 then Exit;
{ Strip trailing comment, but skip over quoted strings so that '#'

View file

@ -229,7 +229,7 @@ begin
try
try
{ --static: freestanding non-PIE ET_EXEC, no libc/PT_INTERP (the kernel
leaf supplies open/read/write/ + _start). Default: dynamic PIE
leaf supplies open/read/write/... + _start). Default: dynamic PIE
linked against libc. }
Lk.SetDynamic(not AOpts.Static);

View file

@ -123,27 +123,13 @@ begin
end;
function execvp(File_: PChar; Argv: Pointer): Integer;
var
HasSlash: Boolean;
I: Int64;
begin
{ If File contains a '/', execve it directly; otherwise a PATH search would be
needed. The RTL only execs absolute/relative program paths (system() builds
"/bin/sh"), so direct execve covers the present call sites; a PATH search can
be added when a bare-name exec appears. }
HasSlash := False;
I := 0;
while (File_[I] and $FF) <> 0 do
begin
if (File_[I] and $FF) = Ord('/') then HasSlash := True;
I := I + 1;
end;
if HasSlash then
Result := execve(File_, Argv, environ)
else
{ 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);
{ No PATH search yet: execve the name directly. The RTL only execs
absolute/relative program paths (system() builds "/bin/sh"), so this covers
the present call sites. A bare program name resolves only relative to the
CWD and otherwise fails with the kernel's ENOENT (never a silent wrong
result); add a $PATH scan here if a bare-name exec call site appears. }
Result := execve(File_, Argv, environ);
end;
{ Population count of a 1024-bit affinity mask (128 bytes), counting set CPUs. }

View file

@ -223,7 +223,8 @@ function mkstemp(Template: PChar): Integer;
const
ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var
Len, I, Tries: Integer;
Len, I, Tries, Got: Integer;
N: Int64;
RandBuf: array[0..5] of Byte;
Fd: Integer;
begin
@ -233,7 +234,15 @@ begin
Tries := 0;
while Tries < 256 do
begin
getrandom(@RandBuf[0], 6, 0);
{ 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 }
@ -264,8 +273,9 @@ begin
Pid := fork();
if Pid = 0 then
begin
{ Child: replace image; on failure exit 127 like the shell. }
execve(PChar(@ShPath[0]), @Argv[0], nil);
{ 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);

View file

@ -49,6 +49,12 @@ var
followed by the TCB self-pointer. Returns nil when the program has no TLS. }
function BuildThreadTLS: Pointer;
{ Unmap a TLS block created by BuildThreadTLS, given the thread pointer it
returned. Used to reclaim the block when thread creation fails after the TLS
was built (the offset from TP back to the mmap base is the same alignment math
BuildThreadTLS used, kept here so both sides agree). }
procedure FreeThreadTLS(ATp: Pointer);
implementation
type
@ -190,6 +196,17 @@ begin
Result := Tp;
end;
procedure FreeThreadTLS(ATp: Pointer);
var
Block: Pointer;
BlockSize: Int64;
begin
if (ATp = nil) or (GTlsMemSz = 0) then Exit;
Block := Pointer(PChar(ATp) - AlignUp(GTlsMemSz, GTlsAlign));
BlockSize := AlignUp(GTlsMemSz, GTlsAlign) + 16;
munmap(Block, BlockSize);
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. }

View file

@ -178,10 +178,9 @@ end;
Args (SysV): %rdi=Flags, %rsi=ChildStack(top), %rdx=ParentTid,
%rcx=ChildTid, %r8=Tls, %r9=Entry, [stack]=EntryArg.
The new stack is pre-seeded by the caller with Entry and EntryArg so the child
can reach them after clone (the syscall does not propagate registers reliably
to a freshly cloned stack across all kernels - we read them from the stack).
Here we instead pass them through callee-saved regs that survive the syscall. }
The child stack is pre-seeded (below) with Entry and EntryArg so the child can
reach them after clone without relying on register state surviving across the
syscall: the child pops them off its own stack. }
function _clone_thread(Flags: Int64; ChildStack, ParentTid, ChildTid,
Tls, Entry, EntryArg: Pointer): Int64;
assembler; nostackframe;
@ -252,6 +251,7 @@ begin
if Tid < 0 then
begin
munmap(Region, STACK_SIZE);
FreeThreadTLS(Tls); { reclaim the per-thread TLS block too }
Result := 11; { EAGAIN }
Exit;
end;