1/*
2 *  kclist_main.c
3 *  kext_tools
4 *
5 *  Created by Nik Gervae on 2010 10 04.
6 *  Copyright 2010 Apple Computer, Inc. All rights reserved.
7 *
8 */
9
10#include <CoreFoundation/CoreFoundation.h>
11#include <System/libkern/OSKextLibPrivate.h>
12#include <System/libkern/prelink.h>
13#include <IOKit/kext/OSKext.h>
14#include <IOKit/kext/OSKextPrivate.h>
15#include <IOKit/IOCFUnserialize.h>
16#include <IOKit/kext/macho_util.h>
17
18#include <architecture/byte_order.h>
19#include <errno.h>
20#include <libc.h>
21#include <mach-o/fat.h>
22#include <sys/mman.h>
23
24#include "kclist_main.h"
25#include "compression.h"
26
27/*******************************************************************************
28*******************************************************************************/
29extern void printPList_new(FILE * stream, CFPropertyListRef plist, int style);
30
31/*******************************************************************************
32* Program Globals
33*******************************************************************************/
34const char * progname = "(unknown)";
35
36/*******************************************************************************
37*******************************************************************************/
38int main(int argc, char * const argv[])
39{
40    ExitStatus           result             = EX_SOFTWARE;
41    KclistArgs           toolArgs;
42    int                  kernelcache_fd     = -1;  // must close()
43    void               * fat_header         = NULL;  // must unmapFatHeaderPage()
44    struct fat_arch    * fat_arch           = NULL;
45    CFDataRef            rawKernelcache     = NULL;  // must release
46    CFDataRef            kernelcacheImage   = NULL;  // must release
47    const UInt8        * kernelcacheStart   = NULL;
48
49    void               * prelinkInfoSect = NULL;
50
51    const char         * prelinkInfoBytes = NULL;
52    CFPropertyListRef    prelinkInfoPlist = NULL;  // must release
53
54    bzero(&toolArgs, sizeof(toolArgs));
55
56   /*****
57    * Find out what the program was invoked as.
58    */
59    progname = rindex(argv[0], '/');
60    if (progname) {
61        progname++;   // go past the '/'
62    } else {
63        progname = (char *)argv[0];
64    }
65
66   /* Set the OSKext log callback right away.
67    */
68    OSKextSetLogOutputFunction(&tool_log);
69
70   /*****
71    * Process args & check for permission to load.
72    */
73    result = readArgs(&argc, &argv, &toolArgs);
74    if (result != EX_OK) {
75        if (result == kKclistExitHelp) {
76            result = EX_OK;
77        }
78        goto finish;
79    }
80
81    result = checkArgs(&toolArgs);
82    if (result != EX_OK) {
83        if (result == kKclistExitHelp) {
84            result = EX_OK;
85        }
86        goto finish;
87    }
88
89    kernelcache_fd = open(toolArgs.kernelcachePath, O_RDONLY);
90    if (kernelcache_fd == -1) {
91        OSKextLog(/* kext */ NULL,
92            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
93            "Can't open %s: %s.", toolArgs.kernelcachePath, strerror(errno));
94        result = EX_OSERR;
95        goto finish;
96    }
97    fat_header = mapAndSwapFatHeaderPage(kernelcache_fd);
98    if (!fat_header) {
99        OSKextLog(/* kext */ NULL,
100            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
101            "Can't map %s: %s.", toolArgs.kernelcachePath, strerror(errno));
102        result = EX_OSERR;
103        goto finish;
104    }
105
106    fat_arch = getFirstFatArch(fat_header);
107    if (fat_arch && !toolArgs.archInfo) {
108        toolArgs.archInfo = NXGetArchInfoFromCpuType(fat_arch->cputype, fat_arch->cpusubtype);
109    }
110
111    rawKernelcache = readMachOSliceForArch(toolArgs.kernelcachePath, toolArgs.archInfo,
112        /* checkArch */ FALSE);
113    if (!rawKernelcache) {
114        OSKextLog(/* kext */ NULL,
115            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
116            "Can't read arch %s from %s.", toolArgs.archInfo->name, toolArgs.kernelcachePath);
117        goto finish;
118    }
119
120    if (MAGIC32(CFDataGetBytePtr(rawKernelcache)) == OSSwapHostToBigInt32('comp')) {
121        kernelcacheImage = uncompressPrelinkedSlice(rawKernelcache);
122        if (!kernelcacheImage) {
123            OSKextLog(/* kext */ NULL,
124                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
125                "Can't uncompress kernelcache slice.");
126            goto finish;
127        }
128    } else {
129        kernelcacheImage = CFRetain(rawKernelcache);
130    }
131
132    kernelcacheStart = CFDataGetBytePtr(kernelcacheImage);
133
134    if (ISMACHO64(MAGIC32(kernelcacheStart))) {
135        prelinkInfoSect = (void *)macho_get_section_by_name_64(
136            (struct mach_header_64 *)kernelcacheStart,
137            kPrelinkInfoSegment, kPrelinkInfoSection);
138
139    } else {
140        prelinkInfoSect = (void *)macho_get_section_by_name(
141            (struct mach_header *)kernelcacheStart,
142            kPrelinkInfoSegment, kPrelinkInfoSection);
143    }
144
145    if (!prelinkInfoSect) {
146        OSKextLog(/* kext */ NULL,
147            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
148            "Can't find prelink info section.");
149        goto finish;
150    }
151
152    if (ISMACHO64(MAGIC32(kernelcacheStart))) {
153        prelinkInfoBytes = ((char *)kernelcacheStart) +
154            ((struct section_64 *)prelinkInfoSect)->offset;
155    } else {
156        prelinkInfoBytes = ((char *)kernelcacheStart) +
157            ((struct section *)prelinkInfoSect)->offset;
158    }
159
160    prelinkInfoPlist = (CFPropertyListRef)IOCFUnserialize(prelinkInfoBytes,
161        kCFAllocatorDefault, /* options */ 0, /* errorString */ NULL);
162    if (!prelinkInfoPlist) {
163        OSKextLog(/* kext */ NULL,
164            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
165            "Can't unserialize prelink info.");
166        goto finish;
167    }
168
169    listPrelinkedKexts(&toolArgs, prelinkInfoPlist);
170
171    result = EX_OK;
172
173finish:
174
175    SAFE_RELEASE(prelinkInfoPlist);
176    SAFE_RELEASE(kernelcacheImage);
177    SAFE_RELEASE(rawKernelcache);
178
179    if (fat_header) {
180        unmapFatHeaderPage(fat_header);
181    }
182
183    if (kernelcache_fd != -1) {
184        close(kernelcache_fd);
185    }
186    return result;
187}
188
189/*******************************************************************************
190*******************************************************************************/
191ExitStatus readArgs(
192    int            * argc,
193    char * const  ** argv,
194    KclistArgs     * toolArgs)
195{
196    ExitStatus   result         = EX_USAGE;
197    ExitStatus   scratchResult  = EX_USAGE;
198    int          optchar        = 0;
199    int          longindex      = -1;
200
201    bzero(toolArgs, sizeof(*toolArgs));
202
203   /*****
204    * Allocate collection objects.
205    */
206    if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks)) {
207
208        OSKextLogMemError();
209        result = EX_OSERR;
210        exit(result);
211    }
212
213    /*****
214    * Process command line arguments.
215    */
216    while ((optchar = getopt_long_only(*argc, *argv,
217        kOptChars, sOptInfo, &longindex)) != -1) {
218
219        switch (optchar) {
220
221            case kOptArch:
222                toolArgs->archInfo = NXGetArchInfoFromName(optarg);
223                if (!toolArgs->archInfo) {
224                    OSKextLog(/* kext */ NULL,
225                        kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
226                        "Unknown architecture %s.", optarg);
227                    goto finish;
228                }
229                break;
230
231            case kOptHelp:
232                usage(kUsageLevelFull);
233                result = kKclistExitHelp;
234                goto finish;
235
236            case kOptVerbose:
237                toolArgs->verbose = true;
238                break;
239
240            default:
241                OSKextLog(/* kext */ NULL,
242                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
243                    "unrecognized option %s", (*argv)[optind-1]);
244                goto finish;
245                break;
246
247        }
248
249       /* Reset longindex, because getopt_long_only() is stupid and doesn't.
250        */
251        longindex = -1;
252    }
253
254   /*****
255    * Record remaining args from the command line.
256    */
257    for ( /* optind already set */ ; optind < *argc; optind++) {
258        if (!toolArgs->kernelcachePath) {
259            toolArgs->kernelcachePath = (*argv)[optind];
260        } else {
261            CFStringRef scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
262                (*argv)[optind], kCFStringEncodingUTF8);
263            if (!scratchString) {
264                result = EX_OSERR;
265                OSKextLogMemError();
266                goto finish;
267            }
268            CFSetAddValue(toolArgs->kextIDs, scratchString);
269            CFRelease(scratchString);
270        }
271    }
272
273   /* Update the argc & argv seen by main() so that boot<>root calls
274    * handle remaining args.
275    */
276    *argc -= optind;
277    *argv += optind;
278
279    result = EX_OK;
280
281finish:
282    if (result == EX_USAGE) {
283        usage(kUsageLevelBrief);
284    }
285    return result;
286}
287
288/*******************************************************************************
289*******************************************************************************/
290ExitStatus checkArgs(KclistArgs * toolArgs)
291{
292    ExitStatus result = EX_USAGE;
293
294    if (!toolArgs->kernelcachePath) {
295
296        OSKextLog(/* kext */ NULL,
297            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
298            "No kernelcache file specified.");
299        goto finish;
300    }
301
302    result = EX_OK;
303
304finish:
305    if (result == EX_USAGE) {
306        usage(kUsageLevelBrief);
307    }
308    return result;
309}
310
311/*******************************************************************************
312*******************************************************************************/
313void listPrelinkedKexts(KclistArgs * toolArgs, CFPropertyListRef kcInfoPlist)
314{
315    CFIndex i, count;
316    Boolean haveIDs = CFSetGetCount(toolArgs->kextIDs) > 0 ? TRUE : FALSE;
317    CFArrayRef kextPlistArray = NULL;
318
319    if (CFArrayGetTypeID() == CFGetTypeID(kcInfoPlist)) {
320        kextPlistArray = (CFArrayRef)kcInfoPlist;
321    } else if (CFDictionaryGetTypeID() == CFGetTypeID(kcInfoPlist)){
322        kextPlistArray = (CFArrayRef)CFDictionaryGetValue(kcInfoPlist,
323            CFSTR("_PrelinkInfoDictionary"));
324    } else {
325        OSKextLog(/* kext */ NULL,
326            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
327            "Unrecognized kernelcache plist data.");
328        goto finish;
329    }
330
331    count = CFArrayGetCount(kextPlistArray);
332    for (i = 0; i < count; i++) {
333        CFDictionaryRef kextPlist = (CFDictionaryRef)CFArrayGetValueAtIndex(kextPlistArray, i);
334        CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
335
336        if (haveIDs && !CFSetContainsValue(toolArgs->kextIDs, kextIdentifier)) {
337            continue;
338        }
339
340        printKextInfo(kextPlist, toolArgs->verbose);
341    }
342
343finish:
344    return;
345}
346
347/*******************************************************************************
348*******************************************************************************/
349void printKextInfo(CFDictionaryRef kextPlist, Boolean beVerbose)
350{
351    CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
352    CFStringRef kextVersion = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleVersionKey);
353    CFStringRef kextPath = (CFStringRef)CFDictionaryGetValue(kextPlist, CFSTR("_PrelinkBundlePath"));
354    char idBuffer[KMOD_MAX_NAME];
355    char versionBuffer[KMOD_MAX_NAME];
356    char pathBuffer[PATH_MAX];
357
358    if (!kextIdentifier || !kextVersion || !kextPath) {
359        OSKextLog(/* kext */ NULL,
360            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
361            "Missing identifier, version, or path.");
362        goto finish;
363    }
364    CFStringGetCString(kextIdentifier, idBuffer, sizeof(idBuffer), kCFStringEncodingUTF8);
365    CFStringGetCString(kextVersion, versionBuffer, sizeof(versionBuffer), kCFStringEncodingUTF8);
366    CFStringGetCString(kextPath, pathBuffer, sizeof(pathBuffer), kCFStringEncodingUTF8);
367
368    printf("%s\t%s\t%s\n", idBuffer, versionBuffer, pathBuffer);
369
370    if (beVerbose) {
371        CFNumberRef cfNum;
372        u_int64_t  kextLoadAddress = 0xDEADBEEF;
373        u_int64_t  kextSourceAddress = 0xDEADBEEF;
374        u_int64_t  kextExecutableSize = 0;
375        u_int64_t  kextKmodInfoAddress = 0xDEADBEEF;
376
377        if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableLoadKey))))
378            CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextLoadAddress);
379        if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSourceKey))))
380            CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextSourceAddress);
381        if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSizeKey))))
382            CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextExecutableSize);
383        if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkKmodInfoKey))))
384            CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextKmodInfoAddress);
385        printf("\t-> load address:   0x%0.8llx, "
386               "size              = 0x%0.8llx,\n"
387               "\t-> source address: 0x%0.8llx, "
388               "kmod_info address = 0x%0.8llx\n",
389               kextLoadAddress, kextExecutableSize, kextSourceAddress, kextKmodInfoAddress);
390    }
391
392finish:
393    return;
394}
395
396/*******************************************************************************
397*******************************************************************************/
398CFComparisonResult compareIdentifiers(const void * val1, const void * val2, void * context __unused)
399{
400    CFDictionaryRef dict1 = (CFDictionaryRef)val1;
401    CFDictionaryRef dict2 = (CFDictionaryRef)val2;
402
403    CFStringRef id1 = CFDictionaryGetValue(dict1, kCFBundleIdentifierKey);
404    CFStringRef id2 = CFDictionaryGetValue(dict2, kCFBundleIdentifierKey);
405
406    return CFStringCompare(id1, id2, 0);
407}
408
409/*******************************************************************************
410* usage()
411*******************************************************************************/
412void usage(UsageLevel usageLevel)
413{
414    fprintf(stderr,
415      "usage: %1$s [-arch archname] [-v] [--] kernelcache [bundle-id ...]\n"
416      "usage: %1$s -help\n"
417      "\n",
418      progname);
419
420    if (usageLevel == kUsageLevelBrief) {
421        fprintf(stderr, "use %s -%s for an explanation of each option\n",
422            progname, kOptNameHelp);
423    }
424
425    if (usageLevel == kUsageLevelBrief) {
426        return;
427    }
428
429    fprintf(stderr, "-%s <archname>:\n"
430        "        list info for architecture <archname>\n",
431        kOptNameArch);
432    fprintf(stderr, "-%s (-%c):\n"
433        "        emit additional information about kext load addresses and sizes\n",
434            kOptNameVerbose, kOptVerbose);
435    fprintf(stderr, "\n");
436
437    fprintf(stderr, "-%s (-%c): print this message and exit\n",
438        kOptNameHelp, kOptHelp);
439
440    return;
441}
442