← Back · de-risks .ko→.kext conversion (#179) & kextd (#177)
kextload + converter + PR CIBefore committing to the full .ko→.kext conversion, the kext_tools/OSKext port, or the mach-into-kernel work, prove the riskiest assumption end-to-end with the cheapest slice: convert one .ko to a .kext, load it with a ported kextload, and confirm the kernel loaded it — in PR CI. Walking skeleton, not the cathedral.
Why it’s cheap The expensive prerequisites already exist in nextbsd: libCoreFoundation ships full CFBundle (CFBundle_InfoPlist.c, CFBundle_Executable.c, CFBundle_Resources.c) — the exact machinery that opens a .kext, parses Info.plist, and locates Contents/MacOS/<binary> — and libIOKit is present. So the proof needs no OSKext: CFBundle + the kld syscalls are enough to load a kext.
The whole Apple-shaped direction (#179 conversion, #177 kextd, #180 loader.conf) rests on one unproven assumption: a FreeBSD .ko wrapped as a .kext bundle can be loaded through an Apple-shaped tool and the kernel accepts it. Nobody has done this on a FreeBSD kernel. Rather than build the cathedral and discover a problem late, prove the load path now with the smallest real artifact, gated by CI so it stays proven.
Success = a PR pipeline that converts a real module, kextloads the bundle in a booted VM, and asserts the kernel shows it loaded (then kextunloads it).
| Component | In nextbsd | Role in the proof |
|---|---|---|
libCoreFoundation / CFBundle | yes (full bundle + Info.plist) | open Foo.kext, read CFBundleExecutable, find Contents/MacOS/Foo |
libIOKit | yes (IOKitMatching.c, IOKitLib.c) | personalities/matching — later, not needed for the load proof |
kld syscalls | base FreeBSD | kldload(2)/kldstat(2)/kldunload(2) — the actual load engine |
| Apple-suite build pattern | build.sh (configd, bootstrap_cmds, libxpc…) | template for building/installing a new src/kext_tools |
| kext tooling | none yet | what we add in Phase 1 |
nextbsdThree thin commands in src/kext_tools/, each a front-end over kld, parsing the bundle with CFBundle. No OSKext, no codesign, no personalities in this phase.
| Tool | Does |
|---|---|
kextload Foo.kext | CFBundleCreate(Foo.kext) → CFBundleCopyExecutableURL → kldload(2) that path. Print the assigned file id. |
kextstat | kldstat(2) / kldnext walk — list loaded files (Apple-shape columns). |
kextunload Foo.kext | resolve the bundle’s module name → kldunload(2). |
Sourcing: vendor a pre-SIP kext_tools (APSL 2.0) and reduce kextload_main.c/kextstat_main.c/kextunload_main.c to the kld-backed path (the OSKext calls become kld calls). Build in the chroot against libCoreFoundation (already built earlier in build.sh); install to the image. Per the §9 of the conversion plan, the older tag avoids the SIP/collection machinery we don’t want anyway.
Scope discipline Phase 1 only proves load. It does not register IOKitPersonalities, resolve dependencies, or do matching — those ride the full port (deferred, ticketed). Wrapping a leaf module (no deps) keeps the proof honest.
nextbsd-kernel-modules.ko (no MODULE_DEPEND), wrap it as Foo.kext/Contents/{Info.plist,MacOS/Foo} with a generated Info.plist (CFBundleIdentifier, CFBundleExecutable; IOKitPersonalities from MODULE_PNP_INFO optional/decorative here).nextbsd-kernel boot smoke test #6): boot the nextbsd continuous image (now shipping kextload) in a VM, mount in Foo.kext, run kextload /System/Library/Extensions/Foo.kext, assert kextstat/kldstat lists the module, then kextunload. Green = kexts work end-to-end.nextbsd-kernel-modules (PR)
└─ build leaf .ko → converter wraps → Foo.kext
└─ boot nextbsd `continuous` image (ships kextload)
└─ kextload Foo.kext → kldload inner .ko
└─ kextstat | grep Foo → ASSERT loaded
└─ kextunload Foo.kext → kldunload
GREEN ⇒ "a FreeBSD .ko, wrapped as .kext, loads via an Apple-shaped tool." ✔
kext_tools + OSKext: port OSKext into libIOKit, plus kextutil/kextfind/kextlibs/kextsymboltool/kextcache — the faithful family the conversion (#179 §9) and kextd (#177) need. ← gets its own ticket.IOKitPersonalities to the libIOKit matcher (autoload on device match) — part of #177.OSBundleLibraries via kextlibs (decorative; MODULE_DEPEND authoritative) — #179.kextd autoloader — #177.kextload/kextstat/kextunload in nextbsd + converter + PR CI in nextbsd-kernel-modules — this plan.OSKext into libIOKit + the rest of the kext_tools family. Blocks faithful #179/#177; must not be lost.nextbsd; ships in continuous.nextbsd-kernel-modules; first green = proof.kext_tools/OSKext port (tracked ticket) → bulk conversion (#179) → kextd (#177).From a 2-agent code-grounded pass over nextbsd, nextbsd-kernel-modules, and a pre-SIP kext_tools.
Base: kext_tools-65.76 (pre-SIP, APSL 2.0). Each tool keeps its CFBundle parsing and swaps the OSKext/Mach call for a kld syscall:
| Tool | Apple call | NextBSD |
|---|---|---|
kextload | _KXKextManagerLoadKextUsingOptions() | CFBundleCreate → CFBundleCopyExecutableURL → CFURLGetFileSystemRepresentation → kldload(path) (full paths bypass kern.module_path) |
kextstat | kmod_get_info() (Mach RPC) | kldnext(0) loop + kldstat(id,&st) over struct kld_file_stat |
kextunload | _KXKextManagerUnloadKext() | name-match loop (kldnext/kldstat) → kldunload(fileid) |
Build: new src/kext_tools/; build.sh step after 3p (libCoreFoundation); link -lCoreFoundation -ldispatch -lBlocksRuntime -lsystem_kernel -lpthread (libs in /usr/lib/system); install to /usr/sbin/{kextload,kextstat,kextunload}. Drop codesign + personalities for this phase.
if_dummy.ko (zero MODULE_DEPEND, ~8–12 KB, clean load/unload — creates/removes dummy0). Alt: misc/zero.ko.tools/ko2kext.sh Foo.ko Name org.nextbsd.kext.id → Name.kext/Contents/{Info.plist,MacOS/Name}. Minimal plist keys: CFBundleExecutable, CFBundleIdentifier, CFBundlePackageType=KEXT, CFBundleVersion, OSBundleRequired=Root.nextbsd-kernel-modules already ingests kernel-obj from the kernel’s continuous; the new PR-only job builds if_dummy.ko, converts it, downloads the nextbsd continuous .img.zip, mdconfig+mount-injects Dummy.kext into /System/Library/Extensions, then boots via the reused tests/boot-test.sh + appended expect blocks asserting kextload→kextstat shows it→kextunload. PR-only, no publish.Hard ordering Phase 2 CI boots the nextbsd continuous image and calls kextload — so Phase 1’s trio must already be shipping in that image before the Phase 2 job can pass. Phase 1 lands and republishes continuous first; then Phase 2.
Kext proof-of-concept plan, 2026-06-03. De-risks #179 / #177. Grounded in the nextbsd tree (libCoreFoundation/CFBundle, libIOKit, build.sh suite pattern) and Apple kext_tools (APSL 2.0).