3737#include < libkern/c++/OSCollectionIterator.h>
3838
3939/*
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.
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.
4846 *
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> .
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 .
5351 */
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 ];
52+ class OSKext {
53+ public:
54+ static OSDictionary *copyLoadedKextInfo (OSArray *kextIdentifiers,
55+ OSArray *keys);
7556};
76-
77- extern struct sysfs_oskext_summary_header *gLoadedKextSummaries ;
78-
7957#include < libkern/libkern.h>
8058
8159#include < string.h>
@@ -383,34 +361,46 @@ sysfs_count_entries(void)
383361/*
384362 * Capture the loaded kext list into the snapshot, for /sys/module.
385363 *
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>]".
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.
397369 *
398370 * Failure here is never fatal: the snapshot is published regardless and
399371 * /sys/module reads as empty, exactly as it does before the first refresh.
400372 */
401373static void
402374sysfs_snap_build_modules (struct sysfs_snapshot *snap)
403375{
404- struct sysfs_oskext_summary_header *hdr = gLoadedKextSummaries ;
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+ }
405394
406- if (hdr == nullptr ||
407- hdr-> version != SYSFS_KEKXT_SUMMARY_VERSION ||
408- hdr-> entry_size != sizeof ( struct sysfs_oskext_summary ) ) {
395+ OSDictionary *info = OSKext::copyLoadedKextInfo ( nullptr , keys);
396+ keys-> release ();
397+ if (info == nullptr ) {
409398 return ;
410399 }
411400
412- uint32_t cap = hdr-> numSummaries ;
401+ uint32_t cap = info-> getCount () ;
413402 if (cap == 0 ) {
403+ info->release ();
414404 return ;
415405 }
416406 if (cap > SYSFS_IOKIT_MAXMODULES ) {
@@ -421,46 +411,47 @@ sysfs_snap_build_modules(struct sysfs_snapshot *snap)
421411 struct sysfs_snap_module *arr =
422412 (struct sysfs_snap_module *)sysfs_alloc (bytes);
423413 if (arr == nullptr ) {
414+ info->release ();
424415 return ;
425416 }
426417
427- uint32_t n = 0 ;
428- for (uint32_t i = 0 ; i < cap; i++) {
429- const struct sysfs_oskext_summary *s = &hdr->summaries [i];
418+ OSCollectionIterator *it = OSCollectionIterator::withCollection (info);
419+ if (it == nullptr ) {
420+ sysfs_free (arr, bytes);
421+ info->release ();
422+ return ;
423+ }
430424
431- if (s->address == 0 && s->text_exec_address == 0 ) {
425+ 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 ) {
432434 continue ;
433435 }
434436
435437 struct sysfs_snap_module *m = &arr[n];
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- }
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 ));
461450 }
462451 n++;
463452 }
453+ it->release ();
454+ info->release ();
464455
465456 snap->modules = arr;
466457 snap->module_cap = cap;
0 commit comments