#include <CoreFoundation/CoreFoundation.h>
#include <System/libkern/OSKextLibPrivate.h>
#include <System/libkern/prelink.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/IOCFUnserialize.h>
#include <IOKit/kext/macho_util.h>
#include <architecture/byte_order.h>
#include <errno.h>
#include <libc.h>
#include <mach-o/fat.h>
#include <sys/mman.h>
#include "kclist_main.h"
#include "compression.h"
extern void printPList_new(FILE * stream, CFPropertyListRef plist, int style);
const char * progname = "(unknown)";
int main(int argc, char * const argv[])
{
ExitStatus result = EX_SOFTWARE;
KclistArgs toolArgs;
int kernelcache_fd = -1; void * fat_header = NULL; struct fat_arch * fat_arch = NULL;
CFDataRef rawKernelcache = NULL; CFDataRef kernelcacheImage = NULL; const UInt8 * kernelcacheStart = NULL;
void * prelinkInfoSect = NULL;
const char * prelinkInfoBytes = NULL;
CFPropertyListRef prelinkInfoPlist = NULL;
bzero(&toolArgs, sizeof(toolArgs));
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
result = readArgs(&argc, &argv, &toolArgs);
if (result != EX_OK) {
if (result == kKclistExitHelp) {
result = EX_OK;
}
goto finish;
}
result = checkArgs(&toolArgs);
if (result != EX_OK) {
if (result == kKclistExitHelp) {
result = EX_OK;
}
goto finish;
}
kernelcache_fd = open(toolArgs.kernelcachePath, O_RDONLY);
if (kernelcache_fd == -1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't open %s: %s.", toolArgs.kernelcachePath, strerror(errno));
result = EX_OSERR;
goto finish;
}
fat_header = mapAndSwapFatHeaderPage(kernelcache_fd);
if (!fat_header) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't map %s: %s.", toolArgs.kernelcachePath, strerror(errno));
result = EX_OSERR;
goto finish;
}
fat_arch = getFirstFatArch(fat_header);
if (fat_arch && !toolArgs.archInfo) {
toolArgs.archInfo = NXGetArchInfoFromCpuType(fat_arch->cputype, fat_arch->cpusubtype);
}
rawKernelcache = readMachOSliceForArch(toolArgs.kernelcachePath, toolArgs.archInfo,
FALSE);
if (!rawKernelcache) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't read arch %s from %s.", toolArgs.archInfo->name, toolArgs.kernelcachePath);
goto finish;
}
if (MAGIC32(CFDataGetBytePtr(rawKernelcache)) == OSSwapHostToBigInt32('comp')) {
kernelcacheImage = uncompressPrelinkedSlice(rawKernelcache);
if (!kernelcacheImage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't uncompress kernelcache slice.");
goto finish;
}
} else {
kernelcacheImage = CFRetain(rawKernelcache);
}
kernelcacheStart = CFDataGetBytePtr(kernelcacheImage);
if (ISMACHO64(MAGIC32(kernelcacheStart))) {
prelinkInfoSect = (void *)macho_get_section_by_name_64(
(struct mach_header_64 *)kernelcacheStart,
kPrelinkInfoSegment, kPrelinkInfoSection);
} else {
prelinkInfoSect = (void *)macho_get_section_by_name(
(struct mach_header *)kernelcacheStart,
kPrelinkInfoSegment, kPrelinkInfoSection);
}
if (!prelinkInfoSect) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't find prelink info section.");
goto finish;
}
if (ISMACHO64(MAGIC32(kernelcacheStart))) {
prelinkInfoBytes = ((char *)kernelcacheStart) +
((struct section_64 *)prelinkInfoSect)->offset;
} else {
prelinkInfoBytes = ((char *)kernelcacheStart) +
((struct section *)prelinkInfoSect)->offset;
}
prelinkInfoPlist = (CFPropertyListRef)IOCFUnserialize(prelinkInfoBytes,
kCFAllocatorDefault, 0, NULL);
if (!prelinkInfoPlist) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't unserialize prelink info.");
goto finish;
}
listPrelinkedKexts(&toolArgs, prelinkInfoPlist);
result = EX_OK;
finish:
SAFE_RELEASE(prelinkInfoPlist);
SAFE_RELEASE(kernelcacheImage);
SAFE_RELEASE(rawKernelcache);
if (fat_header) {
unmapFatHeaderPage(fat_header);
}
if (kernelcache_fd != -1) {
close(kernelcache_fd);
}
return result;
}
ExitStatus readArgs(
int * argc,
char * const ** argv,
KclistArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
ExitStatus scratchResult = EX_USAGE;
int optchar = 0;
int longindex = -1;
bzero(toolArgs, sizeof(*toolArgs));
if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks)) {
OSKextLogMemError();
result = EX_OSERR;
exit(result);
}
while ((optchar = getopt_long_only(*argc, *argv,
kOptChars, sOptInfo, &longindex)) != -1) {
switch (optchar) {
case kOptArch:
toolArgs->archInfo = NXGetArchInfoFromName(optarg);
if (!toolArgs->archInfo) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown architecture %s.", optarg);
goto finish;
}
break;
case kOptHelp:
usage(kUsageLevelFull);
result = kKclistExitHelp;
goto finish;
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"unrecognized option %s", (*argv)[optind-1]);
goto finish;
break;
}
longindex = -1;
}
for ( ; optind < *argc; optind++) {
if (!toolArgs->kernelcachePath) {
toolArgs->kernelcachePath = (*argv)[optind];
} else {
CFStringRef scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
(*argv)[optind], kCFStringEncodingUTF8);
if (!scratchString) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
CFSetAddValue(toolArgs->kextIDs, scratchString);
CFRelease(scratchString);
}
}
*argc -= optind;
*argv += optind;
result = EX_OK;
finish:
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
ExitStatus checkArgs(KclistArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
if (!toolArgs->kernelcachePath) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernelcache file specified.");
goto finish;
}
result = EX_OK;
finish:
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
void listPrelinkedKexts(KclistArgs * toolArgs, CFPropertyListRef kcInfoPlist)
{
CFIndex i, count;
Boolean haveIDs = CFSetGetCount(toolArgs->kextIDs) > 0 ? TRUE : FALSE;
CFArrayRef kextPlistArray = NULL;
if (CFArrayGetTypeID() == CFGetTypeID(kcInfoPlist)) {
kextPlistArray = (CFArrayRef)kcInfoPlist;
} else if (CFDictionaryGetTypeID() == CFGetTypeID(kcInfoPlist)){
kextPlistArray = (CFArrayRef)CFDictionaryGetValue(kcInfoPlist,
CFSTR("_PrelinkInfoDictionary"));
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unrecognized kernelcache plist data.");
goto finish;
}
count = CFArrayGetCount(kextPlistArray);
for (i = 0; i < count; i++) {
CFDictionaryRef kextPlist = (CFDictionaryRef)CFArrayGetValueAtIndex(kextPlistArray, i);
CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
if (haveIDs && !CFSetContainsValue(toolArgs->kextIDs, kextIdentifier)) {
continue;
}
printKextInfo(kextPlist);
}
finish:
return;
}
void printKextInfo(CFDictionaryRef kextPlist)
{
CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
CFStringRef kextVersion = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleVersionKey);
CFStringRef kextPath = (CFStringRef)CFDictionaryGetValue(kextPlist, CFSTR("_PrelinkBundlePath"));
char idBuffer[KMOD_MAX_NAME];
char versionBuffer[KMOD_MAX_NAME];
char pathBuffer[PATH_MAX];
if (!kextIdentifier || !kextVersion || !kextPath) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Missing identifier, version, or path.");
goto finish;
}
CFStringGetCString(kextIdentifier, idBuffer, sizeof(idBuffer), kCFStringEncodingUTF8);
CFStringGetCString(kextVersion, versionBuffer, sizeof(versionBuffer), kCFStringEncodingUTF8);
CFStringGetCString(kextPath, pathBuffer, sizeof(pathBuffer), kCFStringEncodingUTF8);
printf("%s\t%s\t%s\n", idBuffer, versionBuffer, pathBuffer);
finish:
return;
}
CFComparisonResult compareIdentifiers(const void * val1, const void * val2, void * context __unused)
{
CFDictionaryRef dict1 = (CFDictionaryRef)val1;
CFDictionaryRef dict2 = (CFDictionaryRef)val2;
CFStringRef id1 = CFDictionaryGetValue(dict1, kCFBundleIdentifierKey);
CFStringRef id2 = CFDictionaryGetValue(dict2, kCFBundleIdentifierKey);
return CFStringCompare(id1, id2, 0);
}
void usage(UsageLevel usageLevel)
{
fprintf(stderr,
"usage: %1$s [-arch archname] [--] kernelcache [bundle-id ...]\n"
"usage: %1$s -help\n"
"\n",
progname);
if (usageLevel == kUsageLevelBrief) {
fprintf(stderr, "use %s -%s for an explanation of each option\n",
progname, kOptNameHelp);
}
if (usageLevel == kUsageLevelBrief) {
return;
}
fprintf(stderr, "-%s <archname>:\n"
" list info for architecture <archname>\n",
kOptNameArch);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): print this message and exit\n",
kOptNameHelp, kOptHelp);
return;
}