From 5d1c7621f858af014b70cbde5530cc32502a5ea0 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sat, 22 Aug 2026 20:42:10 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=E8=BF=99=E4=B8=AA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=BE=9B=E7=BB=99=E4=B8=80=E4=B8=AA=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E7=9A=84=E5=85=A8=E9=83=A8,=E6=98=AF=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把那两个名字写成一份 port/libSystem.tbd,程序据此链接即可,不需要这个系统 提供任何文件。 名字不是读源码得来的。把一次完整构建的全部对象交给链接器、不给 -undefined dynamic_lookup,让它说还缺什么 —— 这个方法顺带排掉了一个读源码 会留下的假阳性(__DYNAMIC,来自一个本系统构建本就排除的源文件)。 实测 2026-08-22,在 Linux 上用 ld64.lld 链 1356 个对象,产出 Mach-O arm64 可执行文件,依赖只有 /usr/lib/libSystem.B.dylib。 ⚠️ 链接不是运行。这个系统的 64 位 ARM 变体还要求签名,那一步归产出制品的 一方,并且要在那个系统的机器上验。 --- port/libSystem.tbd | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 port/libSystem.tbd diff --git a/port/libSystem.tbd b/port/libSystem.tbd new file mode 100644 index 0000000..89e3d0a --- /dev/null +++ b/port/libSystem.tbd @@ -0,0 +1,33 @@ +--- !tapi-tbd +# The whole of what this system supplies to a program built above openkal. +# +# This implementation issues the kernel's own calls, so a program above it needs +# nothing from this system's C library except the two names below. Neither could +# be replaced by a system call: one arranges the state a new execution context +# needs, which on this system is not what a kernel call does, and the other +# reads a clock the kernel does not otherwise expose. +# +# The list was not read out of the sources. Every object of a complete build was +# given to the linker without `-undefined dynamic_lookup`, and this is what +# remained. That method also found a false positive a reading would have kept +# --- `__DYNAMIC`, from a source this system's build excludes --- which is the +# reason to prefer it. openkal-musl/musl/PATCHES.md records the measurement. +# +# A stub is a list of names, not code, so this file is a complete substitute for +# the vendor's own stub for the purpose of linking: a program built with it +# names /usr/lib/libSystem.B.dylib and binds to the system's real library when +# it runs. Verified 2026-08-22 by linking 1356 objects with ld64.lld ON LINUX, +# producing a Mach-O arm64 executable whose only dependency is that library. +# +# ⚠️ Linking is not running. A program for the 64-bit ARM variant of this system +# must also be signed, if only ad hoc, before the kernel will start it. That +# step belongs to whoever produces the artifact, and it is verified on a machine +# of that system rather than here. +tbd-version: 4 +targets: [ arm64-macos, x86_64-macos ] +install-name: '/usr/lib/libSystem.B.dylib' +current-version: 1351.0.0 +exports: + - targets: [ arm64-macos, x86_64-macos ] + symbols: [ _clock_gettime_nsec_np, _pthread_create_from_mach_thread ] +... From 2fe5d6277ede7cbec491b0d8d26cb3d40942d967 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sat, 22 Aug 2026 20:56:08 +0800 Subject: [PATCH 02/11] =?UTF-8?q?deps:=20=E6=94=B9=E7=94=A8=20git=20+=20?= =?UTF-8?q?=E5=88=86=E6=94=AF=E8=B7=9F=E9=9A=8F=20openkal=20=E7=9A=84?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mcpp.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcpp.toml b/mcpp.toml index 7b5a55d..43dd782 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -8,7 +8,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-macos" [dependencies] -openkal = "0.5.2" +openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-closure" } [build] # The flags are attached to this package's own sources rather than to the whole From 314f358849f92f0d25e9cc205b10f3cdb5557319 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sat, 22 Aug 2026 22:08:00 +0800 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20=E7=AC=AC=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=90=8D=E5=AD=97,=E8=80=8C=E4=B8=94=E5=AE=83=E6=98=AF?= =?UTF-8?q?=E5=8F=A6=E4=B8=80=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前两个是这个实现「调用」的名字,枚举找到了它们。dyld_stub_binder 不被这里或 任何源文件调用:它是链接器为自己发出的惰性绑定引用的,因此是目标格式的要求, 不是这个包的要求。 漏掉它是因为它取决于链接器的版本。实测 2026-08-22:ld64.lld 22.1.8 链这批 对象不需要它,ld64.lld 18 停在 undefined symbol: dyld_stub_binder, referenced by lazy binding 第一次测量用的是一个版本,而一个版本不是关于一种格式的证据。 --- port/libSystem.tbd | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/port/libSystem.tbd b/port/libSystem.tbd index 89e3d0a..2637c4c 100644 --- a/port/libSystem.tbd +++ b/port/libSystem.tbd @@ -27,7 +27,21 @@ tbd-version: 4 targets: [ arm64-macos, x86_64-macos ] install-name: '/usr/lib/libSystem.B.dylib' current-version: 1351.0.0 +# +# ⚠️ THREE NAMES, AND THE THIRD IS OF A DIFFERENT KIND. +# +# The first two are names this implementation CALLS, and the enumeration above +# found them. `dyld_stub_binder' is not called by anything here or in any source: +# it is referenced by the LINKER, for the lazy binding it emits, and it is +# therefore a requirement of the object format rather than of this package. +# +# It was missed because it depends on the linker's version. Measured +# 2026-08-22: ld64.lld 22.1.8 links these objects without it, and ld64.lld 18 +# stops with `undefined symbol: dyld_stub_binder, referenced by lazy binding'. +# The first measurement was made with one version, and one version is not +# evidence about a format. exports: - targets: [ arm64-macos, x86_64-macos ] - symbols: [ _clock_gettime_nsec_np, _pthread_create_from_mach_thread ] + symbols: [ _clock_gettime_nsec_np, _pthread_create_from_mach_thread, + dyld_stub_binder ] ... From 855d196b78c2973ccd5618c871839f195e9b6b83 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 04:30:47 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=E8=BF=99=E4=B8=AA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=9A=84=E5=B1=95=E5=BC=80=E8=A1=A8=E5=9C=A8=E5=93=AA?= =?UTF-8?q?,=E7=94=B1=E9=95=9C=E5=83=8F=E8=87=AA=E5=B7=B1=E5=9B=9E?= =?UTF-8?q?=E7=AD=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libunwind 在这个目标格式上问 _dyld_find_unwind_sections,而 Apple 的 libSystem 是靠查动态加载器的镜像表来实现它的。openkal 之上的程序不由那个加载器装载 —— 它是一个镜像,而「这个地址在哪个镜像里、它的表在哪」有一个常量答案。 ⚠️ 它不在 port/libSystem.tbd 里:stub 列的是这个系统**提供**的名字,而这是一个 这个系统提供、而 openkal **另行回答**的名字 —— 那使它成为实现,不是借用。 实测 2026-08-23:从 Linux 交叉链接、上面压着完整 C++ 运行时时, undefined symbol: _dyld_find_unwind_sections 是编译器运行时和模拟 TLS 都解决之后 最后站着的那个名字。 段边界取自链接器合成的 section$start$/section$end$ 符号,并且是**弱的** —— 没有异常的程序没有 __eh_frame,而对一个不存在的段的强引用,恰好会在那些「本来 就不该有它」的程序上变成链接错误。 ⚠️ 返回 false 而不是「true 加零长度」:libunwind 读返回值决定要不要继续找, 把零长度的段报成在场会让它停下来。 ⚠️ 一个 include 都不要:实测 #include 会够到工具链自己的 libc++ shim 并停在 '__config_site' file not found —— 那是为宿主生成的、每次安装都不同的文件。 两个宽度取自编译器,与 openkal 的 types.h 同一个理由。 build.mcpp:把 port/ 放上消费者的链接搜索路径。⚠️ 不能条件于宿主 —— 实测从 Linux 交叉链接失败于 library not found for -lSystem,因为在此之前唯一把 stub 放上链接线 的是一个直接点名文件的 shell 脚本。解析了这个包的构建工具不该还需要那个脚本。 --- build.mcpp | 26 +++++++++++++++ src/unwind.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 build.mcpp create mode 100644 src/unwind.cpp diff --git a/build.mcpp b/build.mcpp new file mode 100644 index 0000000..fa61052 --- /dev/null +++ b/build.mcpp @@ -0,0 +1,26 @@ +import mcpp; + +// ⭐ WHERE THIS SYSTEM'S NAMES COME FROM, PUT ON THE CONSUMER'S LINK LINE. +// +// `port/libSystem.tbd` lists the whole of what a program above openkal borrows +// from this system — two names, and a third that belongs to the object format. +// A stub is a list of names rather than code, so linking against it is what +// makes the program name `/usr/lib/libSystem.B.dylib` and bind to the real +// library when it runs. +// +// ⚠️ IT HAS TO BE A SEARCH PATH RATHER THAN A FILE ON THE LINE, and it has to +// come from HERE rather than from whoever is building. `link_search` reaches +// the CONSUMER's link line — the package that knows this system is not the +// package being built — and a relative path in `ldflags` would resolve against +// the build directory instead of against this package. +// +// ⚠️ NOT CONDITIONAL ON THE HOST. Measured 2026-08-23: cross-linking for this +// system from Linux failed with `library not found for -lSystem`, because the +// only thing that had ever put the stub on a link line was a shell script +// (`openkal-musl/tools/cross-build-macos.sh`) that names the file directly. +// A build tool that resolves this package should not need that script. +int main() { + mcpp::link_search("port"); + mcpp::rerun_if_changed("port/libSystem.tbd"); + return 0; +} diff --git a/src/unwind.cpp b/src/unwind.cpp new file mode 100644 index 0000000..d53ef10 --- /dev/null +++ b/src/unwind.cpp @@ -0,0 +1,90 @@ +// Where this image's unwind tables are — answered by the image, because there +// is no loader to ask. +// +// ⭐ WHY THIS IS THIS PACKAGE'S JOB. libunwind, on this object format, asks +// `_dyld_find_unwind_sections`, which Apple's libSystem implements by consulting +// the dynamic loader's table of loaded images. A program above openkal is not +// loaded by that loader — it is one image, and the question "which image is this +// address in, and where are its tables" has a constant answer. +// +// So the function is supplied here, beside the two names `port/libSystem.tbd` +// records as genuinely borrowed. ⚠️ It is NOT in the stub: the stub lists names +// this system provides, and this is a name this system provides that openkal +// answers differently — which makes it an implementation, not a borrowing. +// +// ⚠️ Measured 2026-08-23, cross-linking for this system from Linux with the +// whole C++ runtime above openkal: `undefined symbol: _dyld_find_unwind_sections` +// was the last name standing after the compiler runtime and emulated +// thread-local storage were resolved. +// ⚠️ NO INCLUDES. This package's other sources take none either, and the reason +// is the same one src/sys.h records: a header found here would be the C library +// of whichever machine is building, and this package describes the machine +// being built FOR. Measured while adding this file: `#include ` +// reached the toolchain's own libc++ shim and stopped on `'__config_site' file +// not found` — a per-installation file generated for the host. +// +// The two widths used below are the compiler's own, which is where openkal's +// `types.h` takes them from for the same reason. +using okm_uptr = __UINTPTR_TYPE__; + +namespace { + +// The linker synthesises a symbol for the bounds of any section that exists. +// ⚠️ WEAK, because a program with no exceptions has no `__eh_frame`, and a +// strong reference to an absent section is a link error in exactly the programs +// that were right not to have one. +extern "C" { +extern const char __eh_frame_start[] + __asm("section$start$__TEXT$__eh_frame") __attribute__((weak)); +extern const char __eh_frame_end[] + __asm("section$end$__TEXT$__eh_frame") __attribute__((weak)); +extern const char __unwind_info_start[] + __asm("section$start$__TEXT$__unwind_info") __attribute__((weak)); +extern const char __unwind_info_end[] + __asm("section$end$__TEXT$__unwind_info") __attribute__((weak)); + +// The image's own header. Every Mach-O program has one, and the linker names it. +extern const char __dso_handle[] __attribute__((weak)); +} + +// libunwind's shape, reproduced rather than included: the declaration lives in +// its own header behind `__APPLE__`, and this package does not depend on +// libunwind. ⚠️ The order and widths are the contract; a disagreement here +// writes through a wrong member rather than failing to compile. +struct dyld_unwind_sections { + const void* mh; + const void* dwarf_section; + okm_uptr dwarf_section_length; + const void* compact_unwind_section; + okm_uptr compact_unwind_section_length; +}; + +} // namespace + +extern "C" bool _dyld_find_unwind_sections(void*, dyld_unwind_sections* info) { + if (info == nullptr) return false; + + // ⚠️ The address is ignored, and that is correct rather than lazy: there is + // one image. A caller asking about an address outside it would be asking + // about memory no loader here ever mapped. + info->mh = static_cast(__dso_handle); + + const bool haveDwarf = __eh_frame_start != nullptr + && __eh_frame_end > __eh_frame_start; + info->dwarf_section = haveDwarf ? __eh_frame_start : nullptr; + info->dwarf_section_length = + haveDwarf ? static_cast(__eh_frame_end - __eh_frame_start) : 0; + + const bool haveCompact = __unwind_info_start != nullptr + && __unwind_info_end > __unwind_info_start; + info->compact_unwind_section = haveCompact ? __unwind_info_start : nullptr; + info->compact_unwind_section_length = + haveCompact + ? static_cast(__unwind_info_end - __unwind_info_start) + : 0; + + // ⚠️ False when there is nothing to find, not "true with zero length". + // libunwind reads the return value to decide whether to look further, and + // reporting a section of length zero as present makes it stop looking. + return haveDwarf || haveCompact; +} From d331722d3002264a57c56d4e69277bf04163fd14 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 07:02:00 +0800 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20=E4=B8=89=E4=B8=AA=E5=90=8D?= =?UTF-8?q?=E5=AD=97=E7=9A=84=20stub=20=E9=81=AE=E8=94=BD=E4=BA=86?= =?UTF-8?q?=E8=BF=99=E4=B8=AA=E7=B3=BB=E7=BB=9F=E8=87=AA=E5=B7=B1=E9=82=A3?= =?UTF-8?q?=E4=BB=BD=E5=AE=8C=E6=95=B4=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一提交给 build.mcpp 写了 `link_search("port")` 并注明「不条件于宿主」。那条 原则瞄准的东西是对的 —— 包不该需要一个 shell 脚本,交叉构建不该取决于它发生在 哪里 —— 但据此得出的结论是错的:**这个文件不是唯一能把 libSystem 放上搜索路径 的东西**。在这个系统上,SDK 里本来就有一份,是厂商的,而且列的是全部而不是三个 名字。 ⇒ 把我们这份放到它前面不是补充。`-L` 先于 SDK 被搜索,`-lSystem` 停在第一个命中 上,于是一个需要 strlen 的程序被告知没有这个名字。 ⚠️ 实测 2026-08-22,本包自己的 conformance 在 macOS runner 上,就在那行加进去 的下一个提交: ld64.lld: error: undefined symbol: strlen >>> referenced by …/xim-x-llvm/20.1.7/lib/libc++.a(stdexcept.cpp.o) ld64.lld: error: undefined symbol: dlopen ld64.lld: error: undefined symbol: __error 三个名字,就是这个系统供给一个正在对着它整体链接的构建的全部。 ⇒ 判据不是「这是哪台机器」这种偏好,而是这个 stub 存在的那个事实:**替代品恰好 在被替代的东西缺席时才需要**,而在这个系统上它从不缺席。真的那份在场时它是我们 这份的超集 —— openkal 之上的程序对着它链接一样成立,并且命名同一个库。 --- build.mcpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/build.mcpp b/build.mcpp index fa61052..d8fc682 100644 --- a/build.mcpp +++ b/build.mcpp @@ -14,13 +14,57 @@ import mcpp; // package being built — and a relative path in `ldflags` would resolve against // the build directory instead of against this package. // -// ⚠️ NOT CONDITIONAL ON THE HOST. Measured 2026-08-23: cross-linking for this -// system from Linux failed with `library not found for -lSystem`, because the -// only thing that had ever put the stub on a link line was a shell script -// (`openkal-musl/tools/cross-build-macos.sh`) that names the file directly. -// A build tool that resolves this package should not need that script. +// Measured 2026-08-23: cross-linking for this system from Linux failed with +// `library not found for -lSystem`, because the only thing that had ever put +// the stub on a link line was a shell script +// (`openkal-musl/tools/cross-build-macos.sh`) that names the file directly. A +// build tool that resolves this package should not need that script. +// +// ⚠️⚠️ AND ONLY WHEN THIS SYSTEM'S OWN STUB IS NOT PRESENT, WHICH THE FIRST +// VERSION OF THIS FILE GOT WRONG. +// +// It said "not conditional on the host" and meant it as a principle. The +// principle was aimed at the right thing — a package should not need a shell +// script, and a cross build should not depend on where it happens — but the +// conclusion drawn from it was false, because this file is not the only thing +// that can put a `libSystem` on the search path. On this system, the SDK +// already has one, it is the vendor's, and it lists everything rather than +// three names. +// +// ⇒ Adding ours ahead of it does not augment it. `-L` is searched before the +// SDK, so `-lSystem` stops at the first match, and a program that needs +// `strlen` is told there is no such name. +// +// ⚠️ Measured 2026-08-22, this package's own conformance suite on a macOS +// runner, one commit after the line was added: +// +// ld64.lld: error: undefined symbol: strlen +// >>> referenced by …/xim-x-llvm/20.1.7/lib/libc++.a(stdexcept.cpp.o) +// ld64.lld: error: undefined symbol: dlopen +// ld64.lld: error: undefined symbol: __error +// +// The three names were all this system supplied to a build that was linking +// against the whole of it. +// +// ⇒ The condition is not "which machine is this" as a preference. It is the +// fact the stub exists for: a substitute is needed exactly where the thing it +// substitutes for is absent, and on this system it is never absent. Where the +// real one is present it is a SUPERSET of ours, so a program above openkal +// links against it just as well and names the same library. +static bool contains(const char* haystack, const char* needle) { + for (; *haystack; ++haystack) { + const char* a = haystack; + const char* b = needle; + while (*b && *a == *b) { ++a; ++b; } + if (!*b) return true; + } + return false; +} + int main() { - mcpp::link_search("port"); + // `MCPP_HOST` is a triple; this system's spelling of it carries `macos`. + if (!contains(mcpp::host(), "macos")) + mcpp::link_search("port"); mcpp::rerun_if_changed("port/libSystem.tbd"); return 0; } From c4dd4428a759c32dc2cfa7f58c4fbf6eb44946a2 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 07:08:19 +0800 Subject: [PATCH 06/11] =?UTF-8?q?ci:=20=E6=AE=B5=E8=BE=B9=E7=95=8C?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7=E6=98=AF=E9=93=BE=E6=8E=A5=E5=99=A8=E5=90=88?= =?UTF-8?q?=E6=88=90=E7=9A=84,=E4=B8=8D=E6=98=AF=E6=9F=90=E4=B8=AA?= =?UTF-8?q?=E5=BA=93=E9=87=8C=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 独立性检查报了 unwind.o 的五个符号: ___dso_handle section${start,end}$__TEXT$__{eh_frame,unwind_info} src/unwind.cpp 用自己镜像的这两个段的边界回答 _dyld_find_unwind_sections,而这个 目标格式把边界表达成**链接器合成的符号**。没有任何库定义它们,所以也没有任何库 能经它们被到达 —— 而那正是这道检查要确立的性质。 ⭐ 与 port/libSystem.tbd 里 dyld_stub_binder 同一类,那里的注记已经说过理由: 它是目标格式的要求,不是本包的。 ⚠️ 正则只放行这五个,_strlen / _printf 仍被拒(已逐个验证),检查没有被削弱。 --- .github/workflows/ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 094c5a8..3bfbae1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,20 @@ jobs: # __stack_chk_* emitted by the toolchain around # a frame it protects; supplied by the program, not called by this # implementation. - permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume)$' + # section$start$… / section$end$… ⭐ THE LINKER'S, NOT A LIBRARY'S. + # `src/unwind.cpp` answers `_dyld_find_unwind_sections` out of the + # boundaries of its own image's `__TEXT,__eh_frame` and + # `__TEXT,__unwind_info`, and this object format states those as + # symbols the LINKER synthesises. Nothing defines them in any + # library, so no library can be reached through them — which is + # the property this check exists to establish. Same kind of entry + # as `dyld_stub_binder` in `port/libSystem.tbd`, and the note + # there says why: a requirement of the format rather than of this + # package. + # ___dso_handle likewise the format's: emitted + # beside the weak references above, defined by the image's own + # start files. + permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume|__dso_handle|section\$(start|end)\$__TEXT\$__(eh_frame|unwind_info))$' # Reported with the object that references it. A symbol without the # object it came from names a fault and not a place, and the first # time this check fired the answer was in the object rather than in From 12447993e8987920a65698fa548592497c4ef12b Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 08:22:37 +0800 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20=E6=9C=80=E5=90=8E=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=8E=82=E5=95=86=20SDK=20=E7=9A=84=20include=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=20=E5=9C=A8=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=B8=8A=E4=BC=9A=E8=B5=A2=E8=BF=87=E5=9B=BE?= =?UTF-8?q?=E9=87=8C=E7=9A=84=E9=82=A3=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …/MacOSX.sdk/usr/include/mach/mach_time.h:61:1: error: a type specifier is required for all declarations (× 19) 在**这个系统上**构建本包、而目标侧来自依赖图时,SDK 的 赢过了图里 C 库的那份,并拉进一个自身前提没有到位的 Mach 头。从 Linux 宿主出发的同一次构建取到 的是图里那份,一声不吭 —— 所以这是宿主差异,只在这里才现形。 ⭐ 而本包的其余部分早就不这样做:pthread_create_from_mach_thread 一直是自己声明的 (因为没有头声明它)。openkal-linux 写系统调用号,openkal-opensbi 写 SBI 标识, openkal-windows 写 Win32 声明 —— 这三个普通名字是最后一处例外。 ⭐ pthread_t 在两个 C 库上都是指针(musl 的 struct __pthread*、这个系统的 struct _opaque_pthread_t*),所以 void* 在 ABI 这一层是同一个参数,而 extern "C" 匹配的正是这一层。值在两种情况下都以整数存放。 本机复验:从 Linux 交叉到 aarch64-macos 仍然产出 Mach-O。 --- src/task.cpp | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/task.cpp b/src/task.cpp index 15d39a1..6696e38 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -22,14 +22,39 @@ // and the default --- there is no such constraint and the ordinary names are // used, which also avoids the cost the other arrangement pays. -#ifndef OKM_STANDALONE -#include -#endif - +// ⚠️ DECLARED, NOT INCLUDED — AND THAT IS THE SAME RULE THE BRANCH BELOW +// ALREADY FOLLOWS. +// +// `pthread_create_from_mach_thread` has always been declared here rather than +// taken from a header, because no header declares it. The three ordinary names +// were taken from ``, and that header is this system's SDK — the one +// thing every other implementation in this ecosystem avoids: openkal-linux +// writes the system-call numbers, openkal-opensbi the SBI identifiers, +// openkal-windows the Win32 declarations. +// +// ⚠️ Measured 2026-08-23, building this package ON this system with the target +// side coming from the graph: +// +// …/MacOSX.sdk/usr/include/mach/mach_time.h:61:1: +// error: a type specifier is required for all declarations (× 19) +// +// The SDK's `` won the search over the C library's in the graph, and +// pulled in a Mach header that its own prerequisites were not there for. On a +// Linux host the same build had taken the graph's copy and said nothing, which +// is why the difference is a HOST difference and shows up only here. +// +// ⭐ `pthread_t` IS A POINTER ON BOTH C LIBRARIES — `struct __pthread*` in musl +// and `struct _opaque_pthread_t*` on this system — so `void*` is the same +// argument at the ABI, which is the level `extern "C"` matches at. The value is +// stored as an integer below in either case. extern "C" { #ifdef OKM_STANDALONE int pthread_create_from_mach_thread(void** thread, const void* attr, void* (*start)(void*), void* arg); +#else +int pthread_create(void** thread, const void* attr, + void* (*start)(void*), void* arg); +int pthread_join(void* thread, void** value); #endif } @@ -83,7 +108,7 @@ int kal_task_start(void (*entry)(void*), void* arg, kal_task* out) { void* thread = nullptr; const int rc = pthread_create_from_mach_thread(&thread, nullptr, run, c); #else - pthread_t id{}; + void* id = nullptr; const int rc = ::pthread_create(&id, nullptr, run, c); if (rc == 0) c->thread = static_cast(reinterpret_cast(id)); #endif @@ -105,7 +130,8 @@ int kal_task_join(kal_task h) { reinterpret_cast(const_cast(&c->finished)), 0, 0); } #else - const int rc = ::pthread_join(reinterpret_cast(c->thread), nullptr); + const int rc = ::pthread_join(reinterpret_cast( + static_cast(c->thread)), nullptr); if (rc != 0) return translate_posix(rc); #endif kal_free(c, sizeof(context), alignof(context)); From 2468170a313e105fc627fc9afa70bd5a04f0c461 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 09:50:35 +0800 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20=E5=88=A4=E6=8D=AE=E4=B8=8D?= =?UTF-8?q?=E6=98=AF=E3=80=8C=E5=93=AA=E5=8F=B0=E6=9C=BA=E5=99=A8=E3=80=8D?= =?UTF-8?q?,=E8=80=8C=E6=98=AF=E3=80=8C=E8=BF=99=E6=AC=A1=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=9C=89=E6=B2=A1=E6=9C=89=E8=A2=AB=E6=8C=87=E5=90=91?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E7=9B=AE=E6=A0=87=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前两版都写成了「哪台机器」,而且失败方向相反: · 第一版无条件供给 stub —— 在这个系统上它**遮蔽**了厂商那份(那份列的是全部名字, 不是三个); · 第二版问「宿主是不是这个系统」—— 反过来错了:在 Mac 上跑 mcpp build --target aarch64-macos 是一次目标侧来自图的构建,而构建工具**刻意** 把这台机器的 SDK 挡在这种链接之外。于是厂商那份不在,我们这份也不在: ld64.lld: error: library not found for -lSystem ld64.lld: error: undefined symbol: clock_gettime_nsec_np ld64.lld: error: undefined symbol: pthread_create_from_mach_thread ⭐ 真正决定它的事实是:这次构建**有没有被指向一个目标**。被指向了,工具就不供给 任何自己的系统,而本包是唯一能点名一个的东西;没被指向 —— 也就是本包自己的 conformance 做的那种本机构建 —— SDK 就在链接线上,而且是完整的答案。 --- build.mcpp | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/build.mcpp b/build.mcpp index d8fc682..c1fed2d 100644 --- a/build.mcpp +++ b/build.mcpp @@ -1,4 +1,5 @@ import mcpp; +import std; // ⭐ WHERE THIS SYSTEM'S NAMES COME FROM, PUT ON THE CONSUMER'S LINK LINE. // @@ -51,19 +52,30 @@ import mcpp; // substitutes for is absent, and on this system it is never absent. Where the // real one is present it is a SUPERSET of ours, so a program above openkal // links against it just as well and names the same library. -static bool contains(const char* haystack, const char* needle) { - for (; *haystack; ++haystack) { - const char* a = haystack; - const char* b = needle; - while (*b && *a == *b) { ++a; ++b; } - if (!*b) return true; - } - return false; -} int main() { - // `MCPP_HOST` is a triple; this system's spelling of it carries `macos`. - if (!contains(mcpp::host(), "macos")) + // ⚠️⚠️ THE CONDITION IS NOT "WHICH MACHINE", AND THE FIRST TWO ATTEMPTS AT + // IT BOTH WERE. + // + // Attempt one supplied the stub always, and on this system it SHADOWED the + // vendor's — which lists every name rather than three (see above). + // Attempt two asked whether the host was this system, and that failed the + // other way: `mcpp build --target aarch64-macos` ON a Mac is a build whose + // target side comes from the graph, and the build tool deliberately keeps + // this machine's SDK off such a link. The vendor's stub was then not there + // either, and neither was ours: + // + // ld64.lld: error: library not found for -lSystem + // ld64.lld: error: undefined symbol: clock_gettime_nsec_np + // ld64.lld: error: undefined symbol: pthread_create_from_mach_thread + // + // ⭐ The fact that decides it is whether the build was POINTED AT A TARGET. + // When it was, the tool supplies no system of its own and this package is + // the only thing that can name one. When it was not — a native build, which + // is what this package's own conformance suite does — the SDK is on the + // link and is the complete answer. + const char* target = std::getenv("MCPP_TARGET"); + if (target && *target) mcpp::link_search("port"); mcpp::rerun_if_changed("port/libSystem.tbd"); return 0; From 2cb595d153d7da5e50e470f4631a8c161c000178 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Sun, 23 Aug 2026 10:06:51 +0800 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20=E5=88=A4=E6=8D=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20MCPP=5FTARGET=5FREQUESTED=20=E2=80=94=E2=80=94=20MC?= =?UTF-8?q?PP=5FTARGET=20=E4=BB=8E=E4=B8=8D=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ld64.lld: error: undefined symbol: wcslen / strtoul / __error (本包自己的 conformance,本机 macOS) MCPP_TARGET 在没给 --target 时用宿主填上,所以「非空」恒真,于是这个 stub 上了一条 **本机**链接线,而那里 SDK 完整的那份本来就在,我们这份把它遮蔽了。 MCPP_TARGET_REQUESTED 是同一个值未被填充的形态:恰好在这是一次普通本机构建时为空。 ⭐ 太旧而不设这个变量的 mcpp 会读到空,而那对它是正确答案:它没有「目标侧来自图」 这回事,系统永远在链接线上。 本机双向复验:交叉到 aarch64-macos 供给 stub 并产出;用已发布的 2026.8.19.1 走本机 路径构建本包,不供给,通过。 --- build.mcpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/build.mcpp b/build.mcpp index c1fed2d..6e713c3 100644 --- a/build.mcpp +++ b/build.mcpp @@ -74,8 +74,22 @@ int main() { // the only thing that can name one. When it was not — a native build, which // is what this package's own conformance suite does — the SDK is on the // link and is the complete answer. - const char* target = std::getenv("MCPP_TARGET"); - if (target && *target) + // ⚠️ `MCPP_TARGET_REQUESTED` AND NOT `MCPP_TARGET`. The second is filled in + // with the host when nobody named a target, so it is never empty and the + // test was always true — which put this stub on a NATIVE link, where the + // SDK's complete one was already there and ours shadowed it: + // + // ld64.lld: error: undefined symbol: wcslen / strtoul / __error + // + // The first is the value unfilled: empty exactly when this is an ordinary + // native build. See mcpp's `build_program.cppm` for why the two are + // different questions even when the triples are equal. + // + // ⭐ An mcpp too old to set it leaves it empty, and that is the right answer + // for such a build tool: it has no graph-supplied target side, so the + // system is always on the link. + const char* requested = std::getenv("MCPP_TARGET_REQUESTED"); + if (requested && *requested) mcpp::link_search("port"); mcpp::rerun_if_changed("port/libSystem.tbd"); return 0; From 6d9813db1832400e6ecc728671a1257a11089bc9 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 24 Aug 2026 05:03:14 +0800 Subject: [PATCH 10/11] feat: declare the kernel-abi layer this package supplies mcpp resolves the target side per layer after the dependency graph is known, and reads which layer a package fills from its capabilities. Declaring `mcpp:kernel-abi=openkal` states that this package implements the platform interface a C library sits on, and that it answers to the name `openkal`. The engine knows the three layer names and none of the implementations that fill them, so nothing about openkal appears in mcpp for this to work. Claude-Session: https://claude.ai/code/session_01Q4ucduLuSsETHYJ1RkGVVD --- mcpp.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mcpp.toml b/mcpp.toml index 43dd782..27f2ddb 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -4,6 +4,16 @@ name = "openkal-macos" version = "0.3.1" description = "An implementation of openkal for macOS, written on the kernel's own calls. Its purpose is as much to test the specification as to be used." license = "Apache-2.0" + +# The layer this package supplies, in the vocabulary the engine resolves. +# +# `mcpp:kernel-abi` names the platform interface a C library sits on. On a +# traditional stack that seam is unnamed — a C library issues system calls or +# calls the platform's own entry points directly — and naming it is what lets +# one C library sit above several platforms. `=openkal` is the interface this +# package answers to; several packages answer to it and the engine knows none +# of them by name. +provides = ["mcpp:kernel-abi=openkal"] authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-macos" From 3b3c9b019dc43487633592623953818b6cbf9ee2 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 24 Aug 2026 05:32:59 +0800 Subject: [PATCH 11/11] chore: 0.3.2 The manifest states a capability the previous release did not, and the index serves published tarballs rather than branches. A consumer resolving through the index would otherwise get content in which this package declares no layer, and the engine would resolve its target side to the compiler payload. Claude-Session: https://claude.ai/code/session_01Q4ucduLuSsETHYJ1RkGVVD --- mcpp.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcpp.toml b/mcpp.toml index 27f2ddb..16aaf08 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-macos" -version = "0.3.1" +version = "0.3.2" description = "An implementation of openkal for macOS, written on the kernel's own calls. Its purpose is as much to test the specification as to be used." license = "Apache-2.0"