1/*
2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <IOKit/kext/OSKext.h>
24#include <IOKit/kext/OSKextPrivate.h>
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <unistd.h>
29
30#include <sys/types.h>
31#include <sys/param.h>
32
33#include <mach/mach.h>
34#include <mach/mach_error.h>
35#include <mach/mach_host.h>
36
37#include "kextstat_main.h"
38
39// not a utility.[ch] customer yet
40static const char * progname = "(unknown)";
41
42void printPList_new(FILE * stream, CFTypeRef aPlist);
43/*******************************************************************************
44*
45*******************************************************************************/
46ExitStatus main(int argc, char * const * argv)
47{
48    ExitStatus          result        = EX_OK;
49    KextstatArgs        toolArgs;
50    CFDictionaryRef   * kextInfoList  = NULL;  // must free
51    CFIndex             count, i;
52
53    if (argv[0]) {
54        progname = argv[0];
55    }
56
57   /* Set the OSKext log callback right away.
58    */
59    OSKextSetLogOutputFunction(&tool_log);
60
61    result = readArgs(argc, argv, &toolArgs);
62    if (result != EX_OK) {
63        if (result == kKextstatExitHelp) {
64            result = EX_OK;
65        }
66        goto finish;
67    }
68
69    toolArgs.runningKernelArch = OSKextGetRunningKernelArchitecture();
70    if (!toolArgs.runningKernelArch) {
71        result = EX_OSERR;
72        goto finish;
73    }
74
75    toolArgs.loadedKextInfo = OSKextCopyLoadedKextInfo(toolArgs.bundleIDs,
76        NULL /* all info */);
77
78    if (!toolArgs.loadedKextInfo) {
79        OSKextLog(/* kext */ NULL,
80            kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogIPCFlag,
81            "Couldn't get list of loaded kexts from kernel.");
82        result = EX_OSERR;
83        goto finish;
84    }
85
86    if (!toolArgs.flagListOnly) {
87        printf("Index Refs Address    ");
88        if (toolArgs.runningKernelArch->cputype & CPU_ARCH_ABI64) {
89            printf("        ");
90        }
91        printf("Size       Wired      "
92            "Name (Version) <Linked Against>\n");
93    }
94
95    count = CFDictionaryGetCount(toolArgs.loadedKextInfo);
96    if (!count) {
97        goto finish;
98    }
99
100    kextInfoList = (CFDictionaryRef *)malloc(count * sizeof(CFDictionaryRef));
101    if (!kextInfoList) {
102        OSKextLogMemError();
103        result = EX_OSERR;
104        goto finish;
105    }
106
107    CFDictionaryGetKeysAndValues(toolArgs.loadedKextInfo, /* keys */ NULL,
108        (const void **)kextInfoList);
109    qsort(kextInfoList, count, sizeof(CFDictionaryRef), &compareKextInfo);
110    for (i = 0; i < count; i++) {
111        printKextInfo(kextInfoList[i], &toolArgs);
112    }
113
114finish:
115    exit(result);
116
117    SAFE_FREE(kextInfoList);
118
119    return result;
120}
121
122/*******************************************************************************
123*******************************************************************************/
124ExitStatus readArgs(int argc, char * const * argv, KextstatArgs * toolArgs)
125{
126    ExitStatus   result        = EX_USAGE;
127    CFStringRef  scratchString = NULL;  // must release
128    int          optChar       = 0;
129
130    bzero(toolArgs, sizeof(*toolArgs));
131
132   /*****
133    * Allocate collection objects needed for command line argument processing.
134    */
135    if (!createCFMutableArray(&toolArgs->bundleIDs, &kCFTypeArrayCallBacks)) {
136        goto finish;
137    }
138
139   /*****
140    * Process command-line arguments.
141    */
142    result = EX_USAGE;
143
144    while ((optChar = getopt_long_only(argc, argv, kOptChars,
145        sOptInfo, NULL)) != -1) {
146
147        SAFE_RELEASE_NULL(scratchString);
148
149        switch (optChar) {
150
151            case kOptHelp:
152                usage(kUsageLevelFull);
153                result = kKextstatExitHelp;
154                goto finish;
155                break;
156
157            case kOptNoKernelComponents:
158                toolArgs->flagNoKernelComponents = true;
159                break;
160
161            case kOptListOnly:
162                toolArgs->flagListOnly = true;
163                break;
164
165            case kOptBundleIdentifier:
166                scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
167                    optarg, kCFStringEncodingUTF8);
168                if (!scratchString) {
169                    OSKextLogMemError();
170                    result = EX_OSERR;
171                    goto finish;
172                }
173                CFArrayAppendValue(toolArgs->bundleIDs, scratchString);
174                break;
175
176            }
177    }
178
179    argc -= optind;
180    argv += optind;
181
182    if (argc) {
183        OSKextLog(/* kext */ NULL,
184            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
185            "Extra arguments starting at %s....", argv[0]);
186        usage(kUsageLevelBrief);
187        goto finish;
188    }
189
190    result = EX_OK;
191
192finish:
193    SAFE_RELEASE_NULL(scratchString);
194
195    if (result == EX_USAGE) {
196        usage(kUsageLevelBrief);
197    }
198    return result;
199}
200
201/*******************************************************************************
202*******************************************************************************/
203#define kStringInvalidShort    "??"
204#define kStringInvalidLong   "????"
205
206void printKextInfo(CFDictionaryRef kextInfo, KextstatArgs * toolArgs)
207{
208    CFBooleanRef      isKernelComponent      = NULL;  // do not release
209    CFNumberRef       loadTag                = NULL;  // do not release
210    CFNumberRef       retainCount            = NULL;  // do not release
211    CFNumberRef       loadAddress            = NULL;  // do not release
212    CFNumberRef       loadSize               = NULL;  // do not release
213    CFNumberRef       wiredSize              = NULL;  // do not release
214    CFStringRef       bundleID               = NULL;  // do not release
215    CFStringRef       bundleVersion          = NULL;  // do not release
216    CFArrayRef        dependencyLoadTags     = NULL;  // do not release
217    CFMutableArrayRef sortedLoadTags         = NULL;  // must release
218
219    uint32_t          loadTagValue           = kOSKextInvalidLoadTag;
220    uint32_t          retainCountValue       = (uint32_t)-1;
221    uint64_t          loadAddressValue       = (uint64_t)-1;
222    uint32_t          loadSizeValue          = (uint32_t)-1;
223    uint32_t          wiredSizeValue         = (uint32_t)-1;
224    char            * bundleIDCString        = NULL;  // must free
225    char            * bundleVersionCString   = NULL;  // must free
226
227    CFIndex           count, i;
228
229    loadTag = (CFNumberRef)CFDictionaryGetValue(kextInfo,
230        CFSTR(kOSBundleLoadTagKey));
231    retainCount = (CFNumberRef)CFDictionaryGetValue(kextInfo,
232        CFSTR(kOSBundleRetainCountKey));
233    loadAddress = (CFNumberRef)CFDictionaryGetValue(kextInfo,
234        CFSTR(kOSBundleLoadAddressKey));
235    loadSize = (CFNumberRef)CFDictionaryGetValue(kextInfo,
236        CFSTR(kOSBundleLoadSizeKey));
237    wiredSize = (CFNumberRef)CFDictionaryGetValue(kextInfo,
238        CFSTR(kOSBundleWiredSizeKey));
239    bundleID = (CFStringRef)CFDictionaryGetValue(kextInfo,
240        kCFBundleIdentifierKey);
241    bundleVersion = (CFStringRef)CFDictionaryGetValue(kextInfo,
242        kCFBundleVersionKey);
243    dependencyLoadTags = (CFArrayRef)CFDictionaryGetValue(kextInfo,
244        CFSTR(kOSBundleDependenciesKey));
245
246   /* If the -k flag was given, skip any kernel components unless
247    * they are explicitly requested.
248    */
249    if (toolArgs->flagNoKernelComponents) {
250        isKernelComponent = (CFBooleanRef)CFDictionaryGetValue(kextInfo,
251            CFSTR(kOSKernelResourceKey));
252        if (isKernelComponent && CFBooleanGetValue(isKernelComponent)) {
253            if (bundleID &&
254                kCFNotFound == CFArrayGetFirstIndexOfValue(toolArgs->bundleIDs,
255                    RANGE_ALL(toolArgs->bundleIDs), bundleID)) {
256
257                goto finish;
258            }
259        }
260    }
261
262    if (!getNumValue(loadTag, kCFNumberSInt32Type, &loadTagValue)) {
263        loadTagValue = kOSKextInvalidLoadTag;
264    }
265
266   /* Never print the info for the kernel (loadTag 0, id __kernel__).
267    */
268    if (loadTagValue == 0) {
269        goto finish;
270    }
271
272    if (!getNumValue(retainCount, kCFNumberSInt32Type, &retainCountValue)) {
273        retainCountValue = (uint32_t)-1;
274    }
275    if (!getNumValue(loadAddress, kCFNumberSInt64Type, &loadAddressValue)) {
276        loadAddressValue = (uint64_t)-1;
277    }
278    if (!getNumValue(loadSize, kCFNumberSInt32Type, &loadSizeValue)) {
279        loadSizeValue = (uint32_t)-1;
280    }
281    if (!getNumValue(wiredSize, kCFNumberSInt32Type, &wiredSizeValue)) {
282        wiredSizeValue = (uint32_t)-1;
283    }
284
285    bundleIDCString = createUTF8CStringForCFString(bundleID);
286    bundleVersionCString = createUTF8CStringForCFString(bundleVersion);
287
288   /* First column has no leading space.
289    *
290    * These field widths are from the old kextstat, may want to change them.
291    */
292    if (loadTagValue == kOSKextInvalidLoadTag) {
293        fprintf(stdout, "%5s", kStringInvalidShort);
294    } else {
295        fprintf(stdout, "%5d", loadTagValue);
296    }
297
298    if (retainCountValue == (uint32_t)-1) {
299        fprintf(stdout, " %4s", kStringInvalidShort);
300    } else {
301        fprintf(stdout, " %4d", retainCountValue);
302    }
303
304    if (toolArgs->runningKernelArch->cputype & CPU_ARCH_ABI64) {
305        if (loadAddressValue == (uint64_t)-1) {
306            fprintf(stdout, " %-18s", kStringInvalidLong);
307        } else {
308            fprintf(stdout, " %#-18llx", (uint64_t)loadAddressValue);
309        }
310    } else {
311        if (loadAddressValue == (uint64_t)-1) {
312            fprintf(stdout, " %-10s", kStringInvalidLong);
313        } else {
314            fprintf(stdout, " %#-10x", (uint32_t)loadAddressValue);
315        }
316    }
317
318    if (loadSizeValue == (uint32_t)-1) {
319        fprintf(stdout, " %-10s", kStringInvalidLong);
320    } else {
321        fprintf(stdout, " %#-10x", loadSizeValue);
322    }
323
324    if (wiredSizeValue == (uint32_t)-1) {
325        fprintf(stdout, " %-10s", kStringInvalidLong);
326    } else {
327        fprintf(stdout, " %#-10x", wiredSizeValue);
328    }
329
330    fprintf(stdout, " %s",
331        bundleIDCString ? bundleIDCString : kStringInvalidLong);
332
333    fprintf(stdout, " (%s)",
334        bundleVersionCString ? bundleVersionCString : kStringInvalidLong);
335
336    if (dependencyLoadTags && CFArrayGetCount(dependencyLoadTags)) {
337        sortedLoadTags = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0,
338            dependencyLoadTags);
339        if (!sortedLoadTags) {
340            OSKextLogMemError();
341            goto finish;
342        }
343
344        CFArraySortValues(sortedLoadTags, RANGE_ALL(sortedLoadTags),
345            &compareNumbers, /* context */ NULL);
346
347        fprintf(stdout, " <");
348        count = CFArrayGetCount(sortedLoadTags);
349        for (i = 0; i < count; i++) {
350            loadTag = (CFNumberRef)CFArrayGetValueAtIndex(sortedLoadTags, i);
351            if (!getNumValue(loadTag, kCFNumberSInt32Type, &loadTagValue)) {
352                loadTagValue = kOSKextInvalidLoadTag;
353            }
354
355            if (loadTagValue == kOSKextInvalidLoadTag) {
356                fprintf(stdout, "%s%s", i == 0 ? "" : " ", kStringInvalidShort);
357            } else {
358                fprintf(stdout, "%s%d", i == 0 ? "" : " ", loadTagValue);
359            }
360
361        }
362
363        fprintf(stdout, ">");
364
365    }
366
367    fprintf(stdout, "\n");
368
369finish:
370
371    SAFE_RELEASE(sortedLoadTags);
372    SAFE_FREE(bundleIDCString);
373    SAFE_FREE(bundleVersionCString);
374    return;
375}
376
377/*******************************************************************************
378*******************************************************************************/
379Boolean getNumValue(CFNumberRef aNumber, CFNumberType type, void * valueOut)
380{
381    if (aNumber && (CFNumberGetTypeID() == CFGetTypeID(aNumber))) {
382        return CFNumberGetValue(aNumber, type, valueOut);
383    }
384    return false;
385}
386
387/*******************************************************************************
388*******************************************************************************/
389int compareKextInfo(const void * vKextInfo1, const void * vKextInfo2)
390{
391    int             result = 0;
392    CFDictionaryRef kextInfo1 = *(CFDictionaryRef *)vKextInfo1;
393    CFDictionaryRef kextInfo2 = *(CFDictionaryRef *)vKextInfo2;
394    CFNumberRef     loadTag1 = CFDictionaryGetValue(kextInfo1, CFSTR(kOSBundleLoadTagKey));
395    CFNumberRef     loadTag2 = CFDictionaryGetValue(kextInfo2, CFSTR(kOSBundleLoadTagKey));
396    OSKextLoadTag   tag1Value = kOSKextInvalidLoadTag;
397    OSKextLoadTag   tag2Value = kOSKextInvalidLoadTag;
398
399    getNumValue(loadTag1, kCFNumberSInt32Type, &tag1Value);
400    getNumValue(loadTag2, kCFNumberSInt32Type, &tag2Value);
401
402    if (tag1Value == tag2Value) {
403       /* Whether invalid or valid, same is same. */
404        result = 0;
405    } else if (tag1Value == kOSKextInvalidLoadTag) {
406        result = -1;
407    } else if (tag2Value == kOSKextInvalidLoadTag) {
408        result = 1;
409    } else if (tag1Value < tag2Value) {
410        result = -1;
411    } else if (tag2Value < tag1Value) {
412        result = 1;
413    }
414
415    return result;
416}
417
418/*******************************************************************************
419*******************************************************************************/
420CFComparisonResult compareNumbers(
421    const void * val1,
422    const void * val2,
423          void * context)
424{
425    CFComparisonResult result = CFNumberCompare((CFNumberRef)val1,
426         (CFNumberRef)val2, context);
427     if (result == kCFCompareLessThan) {
428         result = kCFCompareGreaterThan;
429     } else if (result == kCFCompareGreaterThan) {
430         result = kCFCompareLessThan;
431     }
432     return result;
433}
434
435/*******************************************************************************
436*******************************************************************************/
437static void usage(UsageLevel usageLevel)
438{
439    fprintf(stderr, "usage: %s [-k] [-l] [-b bundle_id] ...\n", progname);
440
441    if (usageLevel == kUsageLevelBrief) {
442        fprintf(stderr, "\nUse %s -%s (-%c) for a list of options.\n",
443            progname, kOptNameHelp, kOptHelp);
444        return;
445    }
446
447    fprintf(stderr, "-%s (-%c): show only loadable kexts (omit kernel components).\n",
448        kOptNameNoKernelComponents, kOptNoKernelComponents);
449    fprintf(stderr, "-%s (-%c): print the list only, omitting the header.\n",
450        kOptNameListOnly, kOptListOnly);
451    fprintf(stderr, "-%s (-%c) <bundle_id>: print info for kexts named by identifier.\n",
452        kOptNameBundleIdentifier, kOptBundleIdentifier);
453
454    return;
455}
456
457