3737#include < libkern/c++/OSCollectionIterator.h>
3838
3939/*
40- * OSKext::copyLoadedKextInfo() is declared here rather than by including
41- * <libkern/c++/OSKext.h>, which is unusable from a kext build: it pulls in
42- * <libkern/OSKextLibPrivate.h>, a private header the SDK does not ship. A
43- * one-method declaration is enough - a static member call mangles from the
44- * class name and parameter types alone, so this emits exactly the same symbol
45- * reference the real header would, without vendoring private headers.
40+ * The release kernel no longer exports OSKext::copyLoadedKextInfo() - the
41+ * kpi.libkern symbol set was pruned (verified against kernel.release.t8142 for
42+ * macOS 26.5.2, build 25F84). The loaded-kext snapshot is instead read from
43+ * the kernel's own loaded-kext summary table, published as the exported global
44+ * gLoadedKextSummaries: a write-protected header followed by one entry per
45+ * loaded kext, rebuilt on every kext load/unload
46+ * (libkern/c++/OSKext.cpp: OSKext::updateLoadedKextSummaries()). It is the same
47+ * table the panic log and lldb consume.
4648 *
47- * The return type is spelled OSDictionary* because OSPtr<T> is a plain T* in
48- * kext builds, and the return type takes no part in C++ mangling regardless.
49- *
50- * Caller owns a reference to the returned dictionary .
49+ * The structures mirror OSKextLoadedKextSummaryHeader/OSKextLoadedKextSummary
50+ * from <libkern/OSKextLibPrivate.h>. entry_size is validated at read time so a
51+ * layout drift fails loudly instead of walking garbage. name[] is KMOD_MAX_NAME
52+ * (64) bytes as fixed in <mach/kmod.h> .
5153 */
52- class OSKext {
53- public:
54- static OSDictionary *copyLoadedKextInfo (OSArray *kextIdentifiers,
55- OSArray *keys);
54+ #define SYSFS_KEKXT_SUMMARY_VERSION 2
55+
56+ struct sysfs_oskext_summary {
57+ char name[64 ];
58+ uint8_t uuid[16 ];
59+ uint64_t address;
60+ uint64_t size;
61+ uint64_t version; /* packed OSKextVersion, decoded below */
62+ uint32_t loadTag;
63+ uint32_t flags;
64+ uint64_t reference_list;
65+ uint64_t text_exec_address;
66+ size_t text_exec_size;
67+ };
68+
69+ struct sysfs_oskext_summary_header {
70+ uint32_t version;
71+ uint32_t entry_size;
72+ uint32_t numSummaries;
73+ uint32_t reserved;
74+ struct sysfs_oskext_summary summaries[0 ];
5675};
76+
77+ extern struct sysfs_oskext_summary_header *gLoadedKextSummaries ;
78+
5779#include < libkern/libkern.h>
5880
5981#include < string.h>
@@ -361,46 +383,34 @@ sysfs_count_entries(void)
361383/*
362384 * Capture the loaded kext list into the snapshot, for /sys/module.
363385 *
364- * copyLoadedKextInfo() is asked for only the four keys we publish rather than
365- * the full per-kext dictionary, which keeps the work (and the time the kext
366- * lock is held) proportional to what we actually use. A kext missing any of
367- * them is still listed - the value simply reads as zero or empty - because a
368- * present-but-incomplete module is more useful than a hidden one.
386+ * The list is read from gLoadedKextSummaries, the kernel's exported loaded-kext
387+ * summary table (mirror structs above). It is write-protected and rebuilt on
388+ * every kext load/unload, and it is built for exactly this kind of external
389+ * read (panic log, lldb). Entries with a zero address are empty slots and are
390+ * skipped. The table is not reference-counted, so refcnt is published as 0;
391+ * name, load_tag, load_size and version are populated from the summary.
392+ *
393+ * version is a packed OSKextVersion: major*1e8 + minor*1e6 + revision*1e4 +
394+ * stage*1e3 + stage_level, with stages dev=1, alpha=3, beta=5, candidate=7,
395+ * release=9 (libkern/OSKextVersion.c). It is rendered the way the kernel's own
396+ * OSKextVersionGetString() would: "major.minor[.revision][a|b|f|d<n>]".
369397 *
370398 * Failure here is never fatal: the snapshot is published regardless and
371399 * /sys/module reads as empty, exactly as it does before the first refresh.
372400 */
373401static void
374402sysfs_snap_build_modules (struct sysfs_snapshot *snap)
375403{
376- static const char * const wanted[] = {
377- " OSBundleLoadTag" ,
378- " OSBundleLoadSize" ,
379- " OSBundleRetainCount" ,
380- " CFBundleVersion" ,
381- };
382-
383- OSArray *keys = OSArray::withCapacity (4 );
384- if (keys == nullptr ) {
385- return ;
386- }
387- for (unsigned i = 0 ; i < 4 ; i++) {
388- const OSSymbol *sym = OSSymbol::withCString (wanted[i]);
389- if (sym != nullptr ) {
390- keys->setObject (const_cast <OSSymbol *>(sym));
391- sym->release ();
392- }
393- }
404+ struct sysfs_oskext_summary_header *hdr = gLoadedKextSummaries ;
394405
395- OSDictionary *info = OSKext::copyLoadedKextInfo ( nullptr , keys);
396- keys-> release ();
397- if (info == nullptr ) {
406+ if (hdr == nullptr ||
407+ hdr-> version != SYSFS_KEKXT_SUMMARY_VERSION ||
408+ hdr-> entry_size != sizeof ( struct sysfs_oskext_summary ) ) {
398409 return ;
399410 }
400411
401- uint32_t cap = info-> getCount () ;
412+ uint32_t cap = hdr-> numSummaries ;
402413 if (cap == 0 ) {
403- info->release ();
404414 return ;
405415 }
406416 if (cap > SYSFS_IOKIT_MAXMODULES ) {
@@ -411,47 +421,46 @@ sysfs_snap_build_modules(struct sysfs_snapshot *snap)
411421 struct sysfs_snap_module *arr =
412422 (struct sysfs_snap_module *)sysfs_alloc (bytes);
413423 if (arr == nullptr ) {
414- info->release ();
415- return ;
416- }
417-
418- OSCollectionIterator *it = OSCollectionIterator::withCollection (info);
419- if (it == nullptr ) {
420- sysfs_free (arr, bytes);
421- info->release ();
422424 return ;
423425 }
424426
425427 uint32_t n = 0 ;
426- OSObject *k;
427- while ((k = it->getNextObject ()) != nullptr && n < cap) {
428- OSString *ident = OSDynamicCast (OSString, k);
429- if (ident == nullptr ) {
430- continue ;
431- }
432- OSDictionary *kd = OSDynamicCast (OSDictionary, info->getObject (ident));
433- if (kd == nullptr ) {
428+ for (uint32_t i = 0 ; i < cap; i++) {
429+ const struct sysfs_oskext_summary *s = &hdr->summaries [i];
430+
431+ if (s->address == 0 && s->text_exec_address == 0 ) {
434432 continue ;
435433 }
436434
437435 struct sysfs_snap_module *m = &arr[n];
438- strlcpy (m->name , ident->getCStringNoCopy (), sizeof (m->name ));
439-
440- OSNumber *num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleLoadTag" ));
441- m->load_tag = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
442- num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleLoadSize" ));
443- m->load_size = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
444- num = OSDynamicCast (OSNumber, kd->getObject (" OSBundleRetainCount" ));
445- m->refcnt = (num != nullptr ) ? num->unsigned64BitValue () : 0 ;
446-
447- OSString *ver = OSDynamicCast (OSString, kd->getObject (" CFBundleVersion" ));
448- if (ver != nullptr ) {
449- strlcpy (m->version , ver->getCStringNoCopy (), sizeof (m->version ));
436+ strlcpy (m->name , s->name , sizeof (m->name ));
437+ m->load_tag = s->loadTag ;
438+ m->load_size = s->size ;
439+ m->refcnt = 0 ; /* not present in the summary */
440+
441+ uint64_t v = s->version ;
442+ if (v != 0 ) {
443+ unsigned major = (unsigned )(v / 100000000u );
444+ unsigned minor = (unsigned )((v / 1000000u ) % 100u );
445+ unsigned rev = (unsigned )((v / 10000u ) % 100u );
446+ unsigned stage = (unsigned )((v / 1000u ) % 10u );
447+ if (rev != 0 ) {
448+ snprintf (m->version , sizeof (m->version ),
449+ " %u.%u.%u" , major, minor, rev);
450+ } else {
451+ snprintf (m->version , sizeof (m->version ),
452+ " %u.%u" , major, minor);
453+ }
454+ if (stage != 9 ) { /* kOSKextVersionStageRelease */
455+ size_t o = strlen (m->version );
456+ snprintf (m->version + o, sizeof (m->version ) - o, " %c%u" ,
457+ stage == 1 ? ' d' : stage == 3 ? ' a' :
458+ stage == 5 ? ' b' : stage == 7 ? ' f' : ' ?' ,
459+ (unsigned )(v % 1000u ));
460+ }
450461 }
451462 n++;
452463 }
453- it->release ();
454- info->release ();
455464
456465 snap->modules = arr;
457466 snap->module_cap = cap;
0 commit comments