fix(linker): ship own _start; drop system CRT startup objects (#142)
The native internal linker linked every program against system CRT startup objects — Scrt1.o for _start plus crtbeginS.o/crtendS.o/crti.o/crtn.o — located by scanning a hard-coded list of multiarch and versioned gcc directories (/usr/lib/gcc/<triple>/<ver>/). That layout varies by distro, so the scan failed with "internal linker: CRT startup objects not found" on distributions whose triple/gcc version was not listed (issue #142, Mageia 9). Asking gcc (cc -print-file-name) would locate them portably but reintroduces a link-time gcc dependency, defeating the internal linker's purpose (toolchain independence). Instead, follow FPC's approach: the runtime now supplies its OWN _start (runtime/src/main/asm/blaise_start_x86_64.s), which marshals argc/argv and tail-calls __libc_start_main(main, ...) — the modern glibc convention (init/fini = NULL; glibc runs the init array itself). Blaise consumes nothing else from the CRT set: main is a plain C-style entry, unit init runs explicitly from main, and the emitted code references no crtbegin/crtend symbol. So the internal linker now needs no gcc-provided object and no versioned gcc directory; FindCrtObjects and its directory lists are removed. The new _start ships inside blaise_rtl.a; AddArchive includes all members unconditionally, so the entry symbol is always present. Tests: two TInternalAsmE2ETests cases drive the REAL compiler binary through the internal assembler + internal linker (the only harness that exercises the runtime _start; the e2e suite links via cc) and assert the two things _start owns — exit-code propagation (Halt(7)) and argc/argv forwarding (ParamCount/ ParamStr). The native fixpoints already link the compiler itself via this path. Design note: docs/self-contained-start-design.adoc.
This commit is contained in:
parent
47d18eb749
commit
626ee4c9f8
|
|
@ -188,13 +188,9 @@ var
|
|||
Lk: TLinker;
|
||||
Obj: TElfObjectFile;
|
||||
TC: TToolchain;
|
||||
Crt1, Crti, Crtn, CrtBeginS, CrtEndS: string;
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
if not FindCrtObjects(Crt1, Crti, Crtn, CrtBeginS, CrtEndS) then
|
||||
Exit('internal linker: CRT startup objects not found');
|
||||
|
||||
TC := ResolveToolchain(AOpts.Target);
|
||||
|
||||
{ Every Blaise program links blaise_rtl.a — the program entry emits
|
||||
|
|
@ -213,9 +209,6 @@ begin
|
|||
try
|
||||
try
|
||||
Lk.SetDynamic(True);
|
||||
Lk.AddCrtObject(Crt1);
|
||||
Lk.AddCrtObject(Crti);
|
||||
Lk.AddCrtObject(CrtBeginS);
|
||||
|
||||
Obj := ReadElfObjectFile(AObjFile);
|
||||
Lk.AddOwnedObject(Obj);
|
||||
|
|
@ -227,10 +220,11 @@ begin
|
|||
Lk.AddOwnedObject(Obj);
|
||||
end;
|
||||
|
||||
{ _start now lives in blaise_rtl.a (blaise_start_x86_64.s) — no system
|
||||
Scrt1.o / crtbegin / crtend / crti / crtn needed. AddArchive adds all
|
||||
members unconditionally, so the entry symbol is always present. }
|
||||
Lk.AddArchive(TC.RTLPath);
|
||||
|
||||
Lk.AddCrtObject(CrtEndS);
|
||||
Lk.AddCrtObject(Crtn);
|
||||
Lk.Link('_start', AOutputFile);
|
||||
except
|
||||
on E: Exception do
|
||||
|
|
|
|||
|
|
@ -37,13 +37,14 @@ unit blaise.linker.elf;
|
|||
with Phase B alone.
|
||||
|
||||
Phase C — TLinker in dynamic mode (SetDynamic(True)) produces a
|
||||
PIE (ET_DYN) executable linked against libc. It reads CRT startup
|
||||
objects, generates GOT/PLT for undefined (libc) symbols, emits
|
||||
.dynamic/.dynsym/.dynstr/.hash sections, resolves R_X86_64_64 as
|
||||
R_X86_64_RELATIVE in .rela.dyn, handles R_X86_64_TPOFF32 for TLS,
|
||||
and merges .init_array/.fini_array from CRT objects. The entry
|
||||
point is _start (from Scrt1.o), which calls __libc_start_main with
|
||||
main as the program's entry. }
|
||||
PIE (ET_DYN) executable linked against libc. It generates GOT/PLT
|
||||
for undefined (libc) symbols, emits .dynamic/.dynsym/.dynstr/.hash
|
||||
sections, resolves R_X86_64_64 as R_X86_64_RELATIVE in .rela.dyn,
|
||||
handles R_X86_64_TPOFF32 for TLS, and merges any .init_array/
|
||||
.fini_array a linked object contributes. The entry point is _start,
|
||||
which the runtime archive supplies (blaise_start_x86_64.s) and which
|
||||
calls __libc_start_main with main as the program's entry — no system
|
||||
Scrt1.o / crtbegin / crtend / crti / crtn is needed (issue #142). }
|
||||
|
||||
interface
|
||||
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@ function ResolveAssembler: TTool;
|
|||
function ResolveLinker(const ATarget: TTargetDesc): TTool;
|
||||
function FindRTLArchive(const ATarget: TTargetDesc): string;
|
||||
|
||||
{ Locate the system CRT startup objects needed for dynamic (PIE) linking.
|
||||
Returns True when all five objects are found; on False the paths are empty
|
||||
and the caller should fall back to the external linker. }
|
||||
function FindCrtObjects(out ACrt1, ACrti, ACrtn,
|
||||
ACrtBeginS, ACrtEndS: string): Boolean;
|
||||
|
||||
{ Walks $PATH for the first file named ABaseName that exists. Returns the
|
||||
absolute path on hit, '' on miss. On Windows hosts also tries '.exe'. }
|
||||
function WhichInPath(const ABaseName: string): string;
|
||||
|
|
@ -337,62 +331,6 @@ begin
|
|||
Result := '';
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ CRT object discovery }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
function FindCrtObjects(out ACrt1, ACrti, ACrtn,
|
||||
ACrtBeginS, ACrtEndS: string): Boolean;
|
||||
const
|
||||
CrtDirs: array[0..2] of string = (
|
||||
'/usr/lib/x86_64-linux-gnu',
|
||||
'/usr/lib64',
|
||||
'/usr/lib'
|
||||
);
|
||||
GccVers: array[0..3] of string = ('14', '13', '12', '11');
|
||||
GccBases: array[0..1] of string = (
|
||||
'/usr/lib/gcc/x86_64-linux-gnu',
|
||||
'/usr/lib/gcc/x86_64-redhat-linux'
|
||||
);
|
||||
var
|
||||
CrtDir, GccDir: string;
|
||||
I, J: Integer;
|
||||
begin
|
||||
ACrt1 := '';
|
||||
ACrti := '';
|
||||
ACrtn := '';
|
||||
ACrtBeginS := '';
|
||||
ACrtEndS := '';
|
||||
Result := False;
|
||||
|
||||
for I := 0 to High(CrtDirs) do
|
||||
begin
|
||||
CrtDir := CrtDirs[I];
|
||||
if FileExists(CrtDir + '/Scrt1.o') then
|
||||
begin
|
||||
ACrt1 := CrtDir + '/Scrt1.o';
|
||||
ACrti := CrtDir + '/crti.o';
|
||||
ACrtn := CrtDir + '/crtn.o';
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
if ACrt1 = '' then Exit;
|
||||
|
||||
for I := 0 to High(GccBases) do
|
||||
for J := 0 to High(GccVers) do
|
||||
begin
|
||||
GccDir := GccBases[I] + '/' + GccVers[J];
|
||||
if FileExists(GccDir + '/crtbeginS.o') then
|
||||
begin
|
||||
ACrtBeginS := GccDir + '/crtbeginS.o';
|
||||
ACrtEndS := GccDir + '/crtendS.o';
|
||||
Result := FileExists(ACrti) and FileExists(ACrtn)
|
||||
and FileExists(ACrtEndS);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Top-level resolver }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ type
|
|||
function RunProcNoArgs(const AExe: string; out AStdout: string): Integer;
|
||||
function CompileAndRun(const ASrc: string;
|
||||
out AStdout: string; out AExitCode: Integer): Boolean;
|
||||
function CompileAndRunArgs(const ASrc: string;
|
||||
const AArgs: array of string;
|
||||
out AStdout: string; out AExitCode: Integer): Boolean;
|
||||
protected
|
||||
procedure SetUp; override;
|
||||
published
|
||||
|
|
@ -97,6 +100,12 @@ type
|
|||
procedure TestClassVirtualCall;
|
||||
procedure TestFloatArithmetic;
|
||||
procedure TestFormatFloatArg;
|
||||
{ Self-contained program entry (issue #142): the runtime supplies its own
|
||||
_start (blaise_start_x86_64.s) so the internal linker needs no system
|
||||
Scrt1.o / crtbegin / crtend. These assert the two things _start owns —
|
||||
argc/argv forwarding and exit-code propagation through __libc_start_main. }
|
||||
procedure TestEntry_ExitCodePropagates;
|
||||
procedure TestEntry_ArgsForwarded;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -675,6 +684,39 @@ begin
|
|||
Result := True;
|
||||
end;
|
||||
|
||||
{ Like CompileAndRun but runs the produced binary WITH command-line arguments,
|
||||
so argc/argv forwarding through the runtime's own _start is exercised. }
|
||||
function TInternalAsmE2ETests.CompileAndRunArgs(const ASrc: string;
|
||||
const AArgs: array of string;
|
||||
out AStdout: string; out AExitCode: Integer): Boolean;
|
||||
var
|
||||
SrcFile, OutFile, CompOut: string;
|
||||
Rc: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
if not Self.CompilerAvailable() then Exit;
|
||||
|
||||
FCounter := FCounter + 1;
|
||||
SrcFile := FScratch + 'test_asm_' + IntToStr(FCounter) + '.pas';
|
||||
OutFile := FScratch + 'test_asm_' + IntToStr(FCounter);
|
||||
|
||||
WriteFile(SrcFile, ASrc);
|
||||
|
||||
Rc := Self.RunProc(FCompiler, [
|
||||
'--source', SrcFile,
|
||||
'--unit-path', FRTLPath,
|
||||
'--unit-path', FStdlibPath,
|
||||
'--output', OutFile,
|
||||
'--backend', 'native',
|
||||
'--assembler', 'internal'
|
||||
], CompOut);
|
||||
if Rc <> 0 then
|
||||
Fail('compile failed (rc=' + IntToStr(Rc) + '): ' + CompOut);
|
||||
|
||||
AExitCode := Self.RunProc(OutFile, AArgs, AStdout);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
{ ---- Test methods ---- }
|
||||
|
||||
procedure TInternalAsmE2ETests.TestSimpleAdd;
|
||||
|
|
@ -989,6 +1031,52 @@ begin
|
|||
AssertEquals('1.2500' + LineEnding, Out_);
|
||||
end;
|
||||
|
||||
procedure TInternalAsmE2ETests.TestEntry_ExitCodePropagates;
|
||||
var
|
||||
Out_: string;
|
||||
EC: Integer;
|
||||
begin
|
||||
{ Halt(N): the runtime's own _start calls __libc_start_main(main, ...) and
|
||||
glibc exits with main's return value. A broken entry sequence would crash
|
||||
at startup or return the wrong code. No system Scrt1.o is involved. }
|
||||
if not CompileAndRun(
|
||||
'program test_exit;' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' WriteLn(''before'');' + LineEnding +
|
||||
' Halt(7)' + LineEnding +
|
||||
'end.',
|
||||
Out_, EC) then
|
||||
begin
|
||||
Ignore('<toolchain-missing>');
|
||||
Exit;
|
||||
end;
|
||||
AssertEquals(7, EC);
|
||||
AssertEquals('before' + LineEnding, Out_);
|
||||
end;
|
||||
|
||||
procedure TInternalAsmE2ETests.TestEntry_ArgsForwarded;
|
||||
var
|
||||
Out_: string;
|
||||
EC: Integer;
|
||||
begin
|
||||
{ argc/argv reach the program through _start -> __libc_start_main -> main ->
|
||||
_SetArgs. ParamStr(0) is the program path; the passed args follow. }
|
||||
if not CompileAndRunArgs(
|
||||
'program test_args;' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' WriteLn(ParamCount());' + LineEnding +
|
||||
' WriteLn(ParamStr(1));' + LineEnding +
|
||||
' WriteLn(ParamStr(2))' + LineEnding +
|
||||
'end.',
|
||||
['alpha', 'beta'], Out_, EC) then
|
||||
begin
|
||||
Ignore('<toolchain-missing>');
|
||||
Exit;
|
||||
end;
|
||||
AssertEquals(0, EC);
|
||||
AssertEquals('2' + LineEnding + 'alpha' + LineEnding + 'beta' + LineEnding, Out_);
|
||||
end;
|
||||
|
||||
{ ---- Registration ---- }
|
||||
|
||||
initialization
|
||||
|
|
|
|||
203
docs/self-contained-start-design.adoc
Normal file
203
docs/self-contained-start-design.adoc
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
= Self-Contained Program Entry: Replacing System CRT Startup Objects
|
||||
:toc: macro
|
||||
:toclevels: 3
|
||||
|
||||
toc::[]
|
||||
|
||||
== Summary
|
||||
|
||||
The native backend's internal linker currently links every Blaise program
|
||||
against three categories of system CRT startup object:
|
||||
|
||||
* `Scrt1.o` — provides the `_start` entry symbol, which calls
|
||||
`__libc_start_main(main, argc, argv, ...)`.
|
||||
* `crtbeginS.o` / `crtendS.o` — gcc-provided, supply the `.init_array` /
|
||||
`.fini_array` frame and registration glue.
|
||||
* `crti.o` / `crtn.o` — supply the `_init` / `_fini` C-runtime function
|
||||
prologue/epilogue.
|
||||
|
||||
These objects are located by `uToolchain.FindCrtObjects`, which scans a
|
||||
hard-coded list of multiarch and *versioned gcc* directories
|
||||
(`/usr/lib/gcc/<triple>/<ver>/`). That directory layout varies by
|
||||
distribution, so the scan fails on distributions whose triple or gcc version
|
||||
is not in the list — issue #142 reports the failure on Mageia 9
|
||||
(`internal linker: CRT startup objects not found`). The QBE backend is
|
||||
unaffected because it links through the system `cc`, which knows its own
|
||||
startup-object paths.
|
||||
|
||||
This design removes the dependency on `Scrt1.o`, `crtbeginS.o`, and
|
||||
`crtendS.o` entirely by shipping Blaise's *own* `_start` in the runtime
|
||||
archive — the same approach FPC takes (`rtl/linux/x86_64/si_c.inc`). After
|
||||
the change the internal linker needs no gcc-provided object and no versioned
|
||||
gcc directory, so it links correctly on any glibc-based distribution without
|
||||
consulting gcc.
|
||||
|
||||
== Motivation
|
||||
|
||||
The internal linker exists to make Blaise *toolchain-independent* — to produce
|
||||
a self-contained executable without shelling out to `gcc` / `ld`
|
||||
(`docs/toolchain-independence.adoc`). Resolving the #142 failure by asking
|
||||
`gcc -print-file-name=<obj>` would work on every distribution, but it
|
||||
reintroduces a link-time gcc dependency and so defeats the internal linker's
|
||||
purpose. The correct fix removes the need for the gcc-specific objects rather
|
||||
than locating them more cleverly.
|
||||
|
||||
== Background: what each CRT object contributes
|
||||
|
||||
On modern glibc (>= 2.34) the program entry sequence is:
|
||||
|
||||
. The kernel transfers control to `_start` (the ELF entry point) with the
|
||||
initial stack holding `argc`, `argv`, `envp`, and the auxiliary vector.
|
||||
. `_start` (from `Scrt1.o`) marshals these into the System V argument
|
||||
registers and calls
|
||||
`__libc_start_main(main, argc, argv, init, fini, rtld_fini, stack_end)`.
|
||||
+
|
||||
On glibc >= 2.34 the legacy `__libc_csu_init` / `__libc_csu_fini` were
|
||||
removed; modern `Scrt1.o` passes `init = NULL` and `fini = NULL` (`%rcx` and
|
||||
`%r8` zeroed) and glibc runs `.init_array` / `.fini_array` itself.
|
||||
. `__libc_start_main` performs libc setup, runs the init array, calls
|
||||
`main(argc, argv, envp)`, and on return calls `exit(main_result)`.
|
||||
|
||||
The relevant disassembly of the host's `Scrt1.o` (the exact sequence to
|
||||
reproduce):
|
||||
|
||||
[source,asm]
|
||||
----
|
||||
_start:
|
||||
endbr64
|
||||
xor %ebp, %ebp ; outermost frame marker
|
||||
mov %rdx, %r9 ; rtld_fini (passed in %rdx at entry)
|
||||
pop %rsi ; argc
|
||||
mov %rsp, %rdx ; argv
|
||||
and $0xfffffffffffffff0, %rsp ; 16-byte align
|
||||
push %rax ; pad
|
||||
push %rsp ; stack_end
|
||||
xor %r8d, %r8d ; fini = NULL
|
||||
xor %ecx, %ecx ; init = NULL
|
||||
mov main@GOTPCREL(%rip), %rdi ; main
|
||||
call *__libc_start_main@GOTPCREL(%rip)
|
||||
hlt
|
||||
----
|
||||
|
||||
`crtbeginS.o` / `crtendS.o` provide the `.init_array` / `.fini_array`
|
||||
terminator words and the `__dso_handle` / `__cxa_atexit` registration glue
|
||||
that C and C++ static constructors rely on. `crti.o` / `crtn.o` provide the
|
||||
`_init` / `_fini` function frames.
|
||||
|
||||
=== What Blaise actually uses
|
||||
|
||||
Verification on the current native backend (empty and hello-world programs):
|
||||
|
||||
* Blaise's emitted assembly defines a standard `main(argc, argv)` returning
|
||||
`int`. `main` itself calls `_SetArgs`, `_BlaiseInit`, each imported unit's
|
||||
`<Unit>_init`, then the program body, then returns 0. Unit initialisation is
|
||||
therefore driven explicitly from `main`, *not* through `.init_array`.
|
||||
* Neither the emitted program assembly nor `blaise_rtl.a` references any
|
||||
crtbegin/crtend-provided symbol (`__dso_handle`, `__cxa_atexit`,
|
||||
`__gmon_start__`, `_ITM_*`, `__TMC_END__`).
|
||||
* The compiler emits nothing into `.init_array` / `.fini_array`.
|
||||
|
||||
Consequently the *only* contribution Blaise consumes from the whole CRT set is
|
||||
the `_start` entry symbol. `crtbeginS.o` / `crtendS.o` / `crti.o` / `crtn.o`
|
||||
supply machinery Blaise does not exercise.
|
||||
|
||||
== Design
|
||||
|
||||
=== Ship Blaise's own `_start`
|
||||
|
||||
Add a new platform assembly source `runtime/src/main/asm/blaise_start_x86_64.s`
|
||||
that defines the `_start` symbol, reproducing the modern-glibc sequence above:
|
||||
clear `%rbp`, marshal `argc` / `argv` / `rtld_fini` / `stack_end`, zero the
|
||||
`init` / `fini` arguments, load the address of `main`, and tail-call
|
||||
`__libc_start_main`.
|
||||
|
||||
`main` and `__libc_start_main` are referenced through the GOT
|
||||
(`@GOTPCREL`), matching the relocation style the internal linker already
|
||||
handles for PIE output (`R_X86_64_REX_GOTPCRELX` / `R_X86_64_GOTPCRELX`).
|
||||
`main` is defined by the program object; `__libc_start_main` is an undefined
|
||||
libc import resolved through the PLT/GOT exactly as the existing linked libc
|
||||
symbols are.
|
||||
|
||||
The object is assembled into `blaise_rtl.a` alongside the existing
|
||||
`blaise_setjmp_x86_64.o` / `blaise_atomic_x86_64.o` / `blaise_utf8_x86_64.o`.
|
||||
`TLinker.AddArchive` adds *all* archive members unconditionally (it does not
|
||||
pull members on demand by undefined symbol), so the `_start` member is always
|
||||
present in the link; the entry-name argument to `TLinker.Link` (`'_start'`)
|
||||
resolves against it.
|
||||
|
||||
=== Stop linking the system CRT objects
|
||||
|
||||
In `blaise.codegen.native.driver.pas`, `LinkViaInternalLinker` no longer calls
|
||||
`FindCrtObjects` and no longer issues `AddCrtObject` for `Scrt1.o`,
|
||||
`crtbeginS.o`, `crtendS.o`, `crti.o`, or `crtn.o`. The link line becomes:
|
||||
|
||||
* program object,
|
||||
* extra unit objects,
|
||||
* `blaise_rtl.a` (now containing `_start`),
|
||||
* entry `_start`.
|
||||
|
||||
The `.init_array` / `.fini_array` merge paths in `blaise.linker.elf.pas`
|
||||
remain — they tolerate an absent array (size 0, no `DT_INIT_ARRAY`) and cost
|
||||
nothing when no object contributes one. They are retained so that a future
|
||||
object legitimately emitting an init-array (e.g. an `external 'C'` static
|
||||
library a user links) still works.
|
||||
|
||||
=== Removed surface
|
||||
|
||||
* `uToolchain.FindCrtObjects` and its hard-coded `CrtDirs` / `GccVers` /
|
||||
`GccBases` lists are deleted; nothing else calls them.
|
||||
* The `Crt1 … CrtEndS` locals and the five `AddCrtObject` calls in
|
||||
`LinkViaInternalLinker` are removed.
|
||||
|
||||
`TLinker.AddCrtObject` itself is retained: it is a generic "add this object,
|
||||
keeping its `.init_array` contribution" entry point that the linker core still
|
||||
uses internally and that the external-library path may need later.
|
||||
|
||||
== Platform scope
|
||||
|
||||
The change is x86-64 Linux (glibc) only — the single platform the native
|
||||
internal linker currently targets. The `_start` sequence is architecture- and
|
||||
libc-specific:
|
||||
|
||||
* The startup object name carries the architecture suffix
|
||||
(`blaise_start_x86_64.s`), matching the existing `*_x86_64.s` convention, so
|
||||
an i386 / arm64 port adds a sibling file behind the same archive slot.
|
||||
* musl's `_start` calls `__libc_start_main` with the same prototype, so the
|
||||
glibc sequence is musl-compatible; this is consistent with the
|
||||
toolchain-independence design's musl note.
|
||||
|
||||
== Risks and verification
|
||||
|
||||
The risk is a malformed entry sequence: a mis-aligned stack at the
|
||||
`__libc_start_main` call, wrong argument marshalling, or a bad relocation on
|
||||
`main` / `__libc_start_main` would crash at process start rather than produce a
|
||||
diagnostic. The IR-only test harness cannot see this (it never links), so the
|
||||
guard must be executable end-to-end tests.
|
||||
|
||||
Verification:
|
||||
|
||||
. *E2E (both backends):* existing `AssertRunsOnAll` programs already compile →
|
||||
native link → run. Every native arm now exercises the Blaise `_start`. A
|
||||
passing suite proves the entry sequence, argument passing (`argc` / `argv`
|
||||
reach `_SetArgs`), and exit-code propagation.
|
||||
. *A dedicated entry test:* a program that reads `ParamCount` / `ParamStr`
|
||||
and returns a non-zero exit code, asserting both the forwarded arguments and
|
||||
the propagated exit status — the two things `_start` is responsible for.
|
||||
. *All four fixpoints:* the compiler links itself with the internal linker on
|
||||
the native path, so a broken `_start` breaks `fixpoint-native.sh` and
|
||||
`fixpoint-native-internal.sh` immediately.
|
||||
. *Issue #142 regression:* with the system gcc CRT directory made
|
||||
unavailable (or on a distribution without it), the internal-linker link must
|
||||
still succeed — the objects are no longer consulted.
|
||||
|
||||
== Alternatives considered
|
||||
|
||||
* *Probe `gcc -print-file-name=<obj>` (rejected).* Portable across
|
||||
distributions but reintroduces a link-time gcc dependency, contradicting the
|
||||
internal linker's reason to exist.
|
||||
* *Broaden the hard-coded directory scan (rejected).* Adding Mageia's triple
|
||||
and more gcc versions fixes the reported case but keeps the fundamentally
|
||||
brittle "guess the versioned gcc directory" strategy; the next unusual
|
||||
distribution re-opens the issue.
|
||||
* *Ship our own `_start` (chosen).* Removes the dependency on the
|
||||
distribution-variable objects outright and matches FPC's proven approach.
|
||||
|
|
@ -36,7 +36,8 @@ BLAISE = $(COMPILER_BIN)/blaise
|
|||
BLAISE_FLAGS = --no-incremental
|
||||
|
||||
# Assembly sources — platform-specific, no C compiler needed.
|
||||
ASM_OBJS = $(OBJ_DIR)/blaise_setjmp_x86_64.o \
|
||||
ASM_OBJS = $(OBJ_DIR)/blaise_start_x86_64.o \
|
||||
$(OBJ_DIR)/blaise_setjmp_x86_64.o \
|
||||
$(OBJ_DIR)/blaise_atomic_x86_64.o \
|
||||
$(OBJ_DIR)/blaise_utf8_x86_64.o
|
||||
|
||||
|
|
|
|||
53
runtime/src/main/asm/blaise_start_x86_64.s
Normal file
53
runtime/src/main/asm/blaise_start_x86_64.s
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
# Program entry point (x86_64, System V ABI, glibc-compatible).
|
||||
#
|
||||
# This replaces the system Scrt1.o so the internal linker needs no
|
||||
# gcc-provided startup object and no versioned gcc directory (issue #142).
|
||||
# The runtime ships its own _start; the linker (TLinker.Link '_start') uses
|
||||
# this symbol as the ELF entry point.
|
||||
#
|
||||
# On entry the kernel hands us the initial process stack:
|
||||
# (%rsp) argc
|
||||
# 8(%rsp) argv[0]
|
||||
# ... argv[argc] = NULL, then envp, then the auxiliary vector
|
||||
# and %rdx holds the dynamic linker's finaliser (rtld_fini) for PIE images.
|
||||
#
|
||||
# We marshal these into the arguments __libc_start_main expects:
|
||||
# __libc_start_main(main, argc, argv, init, fini, rtld_fini, stack_end)
|
||||
# %rdi = main (the program's C-style entry)
|
||||
# %rsi = argc
|
||||
# %rdx = argv
|
||||
# %rcx = init = NULL (unit init runs from main, not .init_array)
|
||||
# %r8 = fini = NULL
|
||||
# %r9 = rtld_fini
|
||||
# stack_end pushed on the stack
|
||||
# glibc runs the init array (if any), calls main(argc, argv, envp), then
|
||||
# exit(main's return value). This mirrors modern glibc's own Scrt1.o.
|
||||
|
||||
.text
|
||||
|
||||
.globl _start
|
||||
.type _start, @function
|
||||
_start:
|
||||
endbr64
|
||||
xor %ebp, %ebp # outermost frame marker (ABI)
|
||||
mov %rdx, %r9 # rtld_fini -> arg 7
|
||||
pop %rsi # argc -> arg 2
|
||||
mov %rsp, %rdx # argv -> arg 3 (now at stack top)
|
||||
and $0xfffffffffffffff0, %rsp # re-align stack to 16 bytes
|
||||
push %rax # padding (8 bytes) ...
|
||||
push %rsp # ... and stack_end, keeping alignment
|
||||
xor %r8d, %r8d # fini = NULL -> arg 5
|
||||
xor %ecx, %ecx # init = NULL -> arg 4
|
||||
lea main(%rip), %rdi # main -> arg 1
|
||||
call __libc_start_main@PLT # does not return
|
||||
hlt # trap if it ever does
|
||||
.size _start, .-_start
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
Loading…
Reference in a new issue