Skip to content

feat(freestanding): x86_64-none-elf,以及驱动拒绝执行的那次链接 - #472

Merged
Sunrisepeak merged 5 commits into
mainfrom
feat/x86_64-none-elf
Aug 20, 2026
Merged

feat(freestanding): x86_64-none-elf,以及驱动拒绝执行的那次链接#472
Sunrisepeak merged 5 commits into
mainfrom
feat/x86_64-none-elf

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

一个目标行不止是四个字符串

目标行通常就是两张表里的两条记录。这一行需要引擎代码,而原因是 clang 的属性,不是指令集的属性

clang 由 triple 选工具链。它为 arm / aarch64 / riscv 备有 BareMetal 工具链,直接以 ld.lld 链接;它没有 x86_64 的,于是裸 x86_64 triple 的每一种写法都落到通用 GCC 工具链上 —— 而后者的链接器是宿主的 g++:

g++: error: unrecognized command-line option '-fuse-ld=/…/llvm/22.1.8/bin/ld.lld'

对六种 triple 写法逐一实测,结果一致;-fuse-ld=lld / --ld-path= / --gcc-toolchain= / -B 逐一实测,均不改变结果。唯一能改变它的是把 linux 放进 OS 位 —— 那会给一次裸机链接带来八条宿主 -L

两种结果都不可接受:经宿主 g++ 会让这一行只在 Linux 宿主成立;宿主搜索路径出现在 freestanding 链接上,正是本引擎要守住的封闭性。

解法:lldEmulation

ISA 档表新增一列;置位时引擎直接用 ld.lld 驱动链接。标志词汇随工具一起变 —— -Map= 而非 -Wl,-Map=,-m elf_x86_64 而非 --target=

⚠️ 仅属于驱动的标志是丢弃而非翻译,因为第一次尝试漏了两个:

ld.lld: error: unknown argument '-nostdlib++'
ld.lld: error: unknown argument '-Wl,--disable-new-dtags'

⚠️ riscv 与 aarch64 两行该列留空。它们的驱动本就到得了 lld,为了让三行看起来一致而改动一条可用的链接,正是引入回归的方式。两者的链接行与改动前逐字节相同。

第二列:extra 承载 -mno-red-zone

也不是偏好。System V 的 128 字节红区在有 OS 的机器上安全,是因为内核为中断切了栈;裸机上处理器把中断帧压进红区,被中断的叶函数恢复后局部变量已被覆盖 —— 不触发异常、没有诊断,而且只在中断恰好落在叶函数内部时发生。不存在红区安全的裸机 x86_64 程序。

验证

  • tests/e2e/137_x86_64_zero_libc_target.sh:镜像是 x86-64、装载在脚本指定处、零未定义符号、不指名加载器、链接图经链接器原生标志产出、确实走了 ld.lld、带 -march=x86-64-mno-red-zone
  • 92 个单元测试全过,未改动。
  • 仓库之外:openarch 的探针 —— 一份源码 —— 在 riscv64 / aarch64 / x86_64 上构建并运行,输出逐字节相同。这是它的接口确实做了抽象、而非只是恰好适配两台相似 RISC 机器的第一份证据。

…uses

A target row is normally two table entries. This one needed engine code, and
the reason is a property of clang rather than of the instruction set.

clang selects a toolchain from the triple. It has a BareMetal toolchain for
arm, aarch64 and riscv, which reaches `ld.lld` directly; it has none for
x86_64, so every spelling of a bare x86_64 triple falls through to the generic
GCC toolchain, whose linker is the HOST'S `g++`:

    g++: error: unrecognized command-line option
         '-fuse-ld=/…/llvm/22.1.8/bin/ld.lld'

Measured for `x86_64-none-elf`, `x86_64-unknown-none-elf`,
`x86_64-unknown-none`, `x86_64-elf`, `x86_64-none-none` and
`x86_64-unknown-unknown`, and unchanged by `-fuse-ld=lld`, `--ld-path=`,
`--gcc-toolchain=` or `-B`. Only putting `linux` in the OS position changes it,
and that adds eight host `-L` paths to a bare-metal link.

Neither outcome is acceptable: a host `g++` makes the row work on one host and
nowhere else, and host search paths on a freestanding link are the hermeticity
this engine exists to keep. So the ISA-profile row carries an `lldEmulation`
column, and when it is set the engine drives the link with `ld.lld` itself.
The flag vocabulary changes with the tool — `-Map=` rather than `-Wl,-Map=`,
`-m elf_x86_64` rather than `--target=` — and the driver-only flags are dropped
rather than translated, because the first attempt left two behind:

    ld.lld: error: unknown argument '-nostdlib++'
    ld.lld: error: unknown argument '-Wl,--disable-new-dtags'

The column is empty for the riscv and aarch64 rows: their driver already
reaches lld, and changing a working link to make three rows look alike is how a
regression is introduced. Their link lines are byte-identical to before.

A second new column, `extra`, carries `-mno-red-zone`. That is not a preference
either. The System V ABI's 128-byte red zone is safe on a hosted system because
the kernel switches stacks for interrupts; on bare metal the processor pushes
the interrupt frame into it, and the interrupted leaf function resumes with its
locals overwritten — no fault, no diagnostic, and only when an interrupt
happens to land inside a leaf. There is no bare-metal x86_64 program for which
the red zone is safe.

Verified: `tests/e2e/137_x86_64_zero_libc_target.sh` asserts the image is
x86-64, loads where the script says, has zero undefined symbols, names no
dynamic loader, produced a link map through a linker-native flag, went through
`ld.lld`, and carries `-march=x86-64` and `-mno-red-zone`. The 92 unit tests
pass unchanged.

Also verified outside this repository: openarch's probe — one source file —
builds and runs on riscv64, aarch64 and x86_64 with byte-identical output.
That is the first evidence its interface abstracts rather than merely fits two
similar RISC machines.
⚠️ 两处本方案原先判断错了,记在文档里而不是悄悄改掉:

1. §4 断言 x86_64 目标行「不是代码,是模拟器载荷」。实测下来它需要引擎代码 ——
   clang 没有 x86_64 的 BareMetal 工具链,于是每一种裸 x86_64 triple 的链接都落到
   宿主的 g++ 上。

2. §3 给的目录树是「根为虚拟 workspace、接口在 spec/ 成员里」。那个形状会让根上的
   跨编译对所有成员扇出,把 aarch64 汇编喂给 riscv 汇编器。混合式的根修好了它。

以及一处排序错误:第 5 项(x86_64)原先排在最后,理由是它被外部依赖卡住。它却是
唯一一项改变了对已完成工作之信心的 —— 第三台机器挖出三条两台 RISC 机器合起来也
看不见的东西。「被卡住」和「价值低」在列表上看起来一样,而它们不是一回事。
Same package, same manifest, two commands, different source sets:

    mcpp build --target riscv64-none-elf   skips src/kal/**   (correct)
    mcpp test  --target riscv64-none-elf   compiles it, and dies on
                                           'openkal/abort.h' file not found

The header arrives through that feature's `[feature-deps]`; with the feature
inactive it is not there, and neither are the files that include it.

⭐ TWO FAMILIES REACH THIS CODE AND THEY WANT OPPOSITE THINGS.

  gtest         lists `*/googletest/src/gtest_main.cc` in base `sources` AND
                under `features.main`. The package provides the file
                unconditionally; the feature is a gate over it, and the
                dev-dependency track's per-test main detection must SEE it in
                order to prune it per test.

  riscv-virt-rt names `src/kal/**` under `features.openkal` and nowhere else.
                Those files are not part of the package without the feature.

The engine gated the whole exclusion on "is this a test build", which is right
for the first family and wrong for the second.

⚠️ THIS IS THE FOURTH ATTEMPT, AND THE THIRD WAS ABANDONED ON A MISTAKEN
READING. It was recorded as failing because "gtest's base entry is a glob that
MATCHES the file rather than the same string". Measured against the descriptor
the index actually carries — `compat.gtest.lua` lines 71-73 and 90 — the two
entries are byte-identical. The reason had been written from memory without
opening the file.

What actually defeated that attempt was WHEN the criterion was applied: it
tested membership against `bc.sources` after `drop()` had already removed the
entry, so the test could only ever be false.

The fix snapshots the base globs BEFORE the drop and uses them only in test
mode: a glob in both places is a gate and stays visible; a glob in one place is
a provider and gets the `!` exclusion. The exclusion is the whole mechanism —
`src/kal/**` is never in `bc.sources` at all, because that package declares no
`sources` and its files are matched by the inferred `src/**`, so erasing the
string erases nothing.

Verified, all three of the plan's criteria:

  1. riscv-virt-rt: `mcpp test` no longer compiles src/kal/**; both commands
     exit 0.
  2. mcpp's own 92 unit tests still link and pass (the gtest family).
  3. tests/e2e/138_feature_sources_gate_vs_provider.sh covers both families
     with two minimal packages and no ecosystem dependency.

⭐ (3) was checked the way a regression test has to be: run against the engine
before this commit it fails with exactly the symptom above; against the engine
after it, it passes. The defect survived this long because no test covered both
families at once, and each of the three earlier attempts broke the one it did
not cover.
直连链接路径定义 cxx_link / c_link / cxx_archive,不定义共享库规则 —— 没有加载器
的目标没有东西去加载它。缺这条检查时,失败长这样:

    ninja: error: build.ninja:88: unknown build rule 'cxx_shared'

对一个在清单里写下 kind = "shared" 的人,这句话指的是一条内部规则。

⚠️ 补上那条规则比省掉它更糟:ld.lld -shared 会成功,产出一个那台机器上没有任何
东西能加载的对象 —— 静默的无意义,而不是响亮的拒绝。

e2e 137 加了第 7 条断言,同时检查两件事:拒绝时提到了原因,且**没有**提到内部
规则名。
@Sunrisepeak
Sunrisepeak merged commit a5db8a6 into main Aug 20, 2026
20 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/x86_64-none-elf branch August 20, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants