fix(runtime): --debug leak tracker crashed with "undefined symbol: atexit" on the native backend

The leak tracker registered its end-of-run report via glibc's bare `atexit`.
That symbol lives only in the static libc_nonshared.a; it is NOT a dynamic
export of libc.so.6.  The QBE/cc path linked fine because gcc auto-pulls
libc_nonshared.a, but the native backend's linkers (internal ELF linker and
the native external-assembler cc invocation) do not, so a --debug build aborted
at load time with `symbol lookup error: undefined symbol: atexit`.

Register via __cxa_atexit instead — a genuine dynamic libc export that resolves
on every link path.  Its third argument is the DSO handle; nil registers the
handler against the main program.  The handler takes one (ignored) argument.

Pre-existing bug (reproduces on v0.12.0); affects only --debug builds compiled
with --backend native.  Verified on all three link paths (QBE/cc, native
internal linker, native external assembler) plus the full suite and all four
fixpoints.
This commit is contained in:
Graeme Geldenhuys 2026-06-25 20:27:04 +01:00
parent 6fa4c62e80
commit 7d8a4c7ad9

View file

@ -60,7 +60,15 @@ procedure _AbstractMethodError;
procedure _LeakTrackerEnable;
procedure _LeakTrackerRegister(UserPtr: Pointer; ClassName: Pointer;
UnitName: Pointer; Line: Int64);
procedure _libc_atexit(Fn: Pointer); external name 'atexit';
{ Register an at-exit handler. Uses __cxa_atexit, NOT atexit: glibc's bare
`atexit` lives only in the static libc_nonshared.a (it is not a dynamic export
of libc.so.6), so a link that does not pull that archive as the native
backend's internal and external linkers do not leaves `atexit` unresolved at
load time. __cxa_atexit is a genuine dynamic libc export and resolves on
every link path. Its third argument is the DSO handle; nil registers the
handler against the main program. The handler is passed one (ignored) arg. }
function _libc_cxa_atexit(Fn, Arg, DsoHandle: Pointer): Integer;
external name '__cxa_atexit';
implementation
@ -366,7 +374,7 @@ begin
GLTTable := PChar(_BlaiseGetMem(TableSize));
if GLTTable = nil then begin GLTEnabled := False; Exit end;
_libc_memset(GLTTable, 0, Int64(TableSize));
_libc_atexit(Pointer(@_LeakTrackerReport));
_libc_cxa_atexit(Pointer(@_LeakTrackerReport), nil, nil);
end;
{ ------------------------------------------------------------------ }