diff options
Diffstat (limited to 'system_cmds/zlog.tproj')
-rw-r--r-- | system_cmds/zlog.tproj/SymbolicationHelper.c | 181 | ||||
-rw-r--r-- | system_cmds/zlog.tproj/SymbolicationHelper.h | 35 | ||||
-rw-r--r-- | system_cmds/zlog.tproj/entitlements.plist | 8 | ||||
-rw-r--r-- | system_cmds/zlog.tproj/zlog.1 | 56 | ||||
-rw-r--r-- | system_cmds/zlog.tproj/zlog.c | 257 |
5 files changed, 537 insertions, 0 deletions
diff --git a/system_cmds/zlog.tproj/SymbolicationHelper.c b/system_cmds/zlog.tproj/SymbolicationHelper.c new file mode 100644 index 0000000..68a1285 --- /dev/null +++ b/system_cmds/zlog.tproj/SymbolicationHelper.c @@ -0,0 +1,181 @@ +// +// SymbolicationHelper.c +// zlog +// +// Created by Rasha Eqbal on 2/26/18. +// + +#include "SymbolicationHelper.h" + +/* + * Most of the CoreSymbolication code here has been copied from ioclasscount in the IOKitTools project. + */ + +#define kAddressKey CFSTR("Address") +#define kNameKey CFSTR("Name") +#define kPathKey CFSTR("Path") +#define kSegmentsKey CFSTR("Segments") +#define kSizeKey CFSTR("Size") +#define kUuidKey CFSTR("UUID") + +static void AddSymbolOwnerSummary(CSSymbolOwnerRef owner, CFMutableDictionaryRef binaryImages); +static void ShowBinaryImage(const void *key, const void *value, void *context); + +/* + * Symbolicates 'addr' using the 'symbolicator' passed in. + * Adds owner info to 'binaryImages' for offline symbolication. + * + * Top-level function that needs to be called on each frame address in the backtrace. + */ +void PrintSymbolicatedAddress(CSSymbolicatorRef symbolicator, mach_vm_address_t addr, CFMutableDictionaryRef binaryImages) +{ + printf("0x%llx", addr); + + CSSymbolOwnerRef ownerInfo = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator, addr, kCSNow); + if (!CSIsNull(ownerInfo)) { + const char *moduleName = CSSymbolOwnerGetName(ownerInfo); + if (moduleName) { + printf(" <%s>", moduleName); + } + } + + CSSymbolRef symbolInfo = CSSymbolicatorGetSymbolWithAddressAtTime(symbolicator, addr, kCSNow); + if (!CSIsNull(symbolInfo)) { + printf(" %s", CSSymbolGetName(symbolInfo)); + } + + CSSourceInfoRef sourceInfo = CSSymbolicatorGetSourceInfoWithAddressAtTime(symbolicator, addr, kCSNow); + if (!CSIsNull(sourceInfo)) { + const char *fileName = CSSourceInfoGetPath(sourceInfo); + if (fileName) { + printf(" at %s:%d", fileName, CSSourceInfoGetLineNumber(sourceInfo)); + } + } + printf("\n"); + + AddSymbolOwnerSummary(ownerInfo, binaryImages); +} + +/* + * Adds symbolication information for 'owner' to 'binaryImages' to help with offline symbolication. + * + * This is called from PrintSymbolicatedAddress() on the symbol owner for each address it symbolicates. + */ +static void AddSymbolOwnerSummary(CSSymbolOwnerRef owner, CFMutableDictionaryRef binaryImages) +{ + const CFUUIDBytes *uuidBytes = NULL; + CFUUIDRef uuid = NULL; + CFStringRef uuidString = NULL, path = NULL, name = NULL; + CFMutableDictionaryRef summaryDict = NULL; + __block CSSegmentRef textSegment = kCSNull, textExecSegment = kCSNull; + CSSegmentRef segment = kCSNull; + CSRange range; + CFNumberRef address = NULL, size = NULL; + +#define RETURN_IF_NULL(ptr) \ +if (!(ptr)) { \ +goto cleanup; \ +} + + uuidBytes = CSSymbolOwnerGetCFUUIDBytes(owner); + if (uuidBytes) { + uuid = CFUUIDCreateFromUUIDBytes(NULL, *uuidBytes); + if (uuid) { + uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid); + } + } + RETURN_IF_NULL(uuidString); + + if (!CFDictionaryContainsKey(binaryImages, uuidString)) { + summaryDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + RETURN_IF_NULL(summaryDict); + + CFDictionarySetValue(summaryDict, kUuidKey, uuidString); + + path = CFStringCreateWithCString(kCFAllocatorDefault, CSSymbolOwnerGetPath(owner), kCFStringEncodingUTF8); + RETURN_IF_NULL(path); + CFDictionarySetValue(summaryDict, kPathKey, path); + + name = CFStringCreateWithCString(kCFAllocatorDefault, CSSymbolOwnerGetName(owner), kCFStringEncodingUTF8); + RETURN_IF_NULL(name); + CFDictionarySetValue(summaryDict, kNameKey, name); + + CSSymbolOwnerForeachSegment(owner, ^(CSSegmentRef segment) { + if (strcmp(CSRegionGetName(segment), "__TEXT SEGMENT") == 0) { + textSegment = segment; + CSRetain(textSegment); + } else if (strcmp(CSRegionGetName(segment), "__TEXT_EXEC SEGMENT") == 0) { + textExecSegment = segment; + CSRetain(textExecSegment); + } + }); + + segment = !CSIsNull(textExecSegment) ? textExecSegment : textSegment; + if (CSIsNull(segment)) { + goto cleanup; + } + range = CSRegionGetRange(segment); + + address = CFNumberCreate(NULL, kCFNumberLongLongType, &range.location); + RETURN_IF_NULL(address); + CFDictionarySetValue(summaryDict, kAddressKey, address); + + size = CFNumberCreate(NULL, kCFNumberLongLongType, &range.length); + RETURN_IF_NULL(size); + CFDictionarySetValue(summaryDict, kSizeKey, size); + + CFDictionarySetValue(binaryImages, uuidString, summaryDict); + } + +cleanup: + if (size) CFRelease(size); + if (address) CFRelease(address); + if (!CSIsNull(textExecSegment)) CSRelease(textExecSegment); + if (!CSIsNull(textSegment)) CSRelease(textSegment); + if (name) CFRelease(name); + if (path) CFRelease(path); + if (summaryDict) CFRelease(summaryDict); + if (uuidString) CFRelease(uuidString); + if (uuid) CFRelease(uuid); +} + +/* + * Prints offline symbolication information for the images passed in 'binaryImages'. + * + * Top-level function that needs to be called if the tool wants to include support + * for offline symbolication. + */ +void PrintBinaryImagesInfo(CFMutableDictionaryRef binaryImages) +{ + if (CFDictionaryGetCount(binaryImages) > 0) { + printf("\nBinary Images:\n"); + CFDictionaryApplyFunction(binaryImages, ShowBinaryImage, NULL); + } else { + printf("No binary images\n"); + } +} + +/* + * Prints information about a binary image necessary for offline symbolication. + * + * This is called from PrintBinaryImagesInfo() on each element in 'binaryImages'. + */ +static void ShowBinaryImage(const void *key, const void *value, void *context) +{ + char nameString[256] = {0}, uuidString[256] = {0}, pathString[256] = {0}; + CFStringRef uuid = (CFStringRef)key; + CFStringGetCString(uuid, uuidString, sizeof(uuidString), kCFStringEncodingASCII); + CFDictionaryRef summary = (CFDictionaryRef)value; + + CFStringRef name = CFDictionaryGetValue(summary, kNameKey); + CFStringGetCString(name, nameString, sizeof(nameString), kCFStringEncodingASCII); + CFStringRef path = CFDictionaryGetValue(summary, kPathKey); + CFStringGetCString(path, pathString, sizeof(pathString), kCFStringEncodingASCII); + CFNumberRef addressNumber = CFDictionaryGetValue(summary, kAddressKey); + CFNumberRef sizeNumber = CFDictionaryGetValue(summary, kSizeKey); + int64_t address, size; + CFNumberGetValue(addressNumber, kCFNumberSInt64Type, &address); + CFNumberGetValue(sizeNumber, kCFNumberSInt64Type, &size); + + printf("%p - %p %s <%s> %s\n", (void*)address, (void*)address + size, nameString, uuidString, pathString); +} diff --git a/system_cmds/zlog.tproj/SymbolicationHelper.h b/system_cmds/zlog.tproj/SymbolicationHelper.h new file mode 100644 index 0000000..f9d90c5 --- /dev/null +++ b/system_cmds/zlog.tproj/SymbolicationHelper.h @@ -0,0 +1,35 @@ +// +// SymbolicationHelper.h +// zlog +// +// Created by Rasha Eqbal on 2/26/18. +// + +#ifndef SymbolicationHelper_h +#define SymbolicationHelper_h + +#include <CoreFoundation/CoreFoundation.h> +#include <CoreSymbolication/CoreSymbolication.h> + +/* + * Call this function on each address that needs to be symbolicated. + * + * sym: The CSSymbolicatorRef which will be used for symbolication. For example, to symbolicate + * kernel addresses create a CSSymbolicatorRef by calling CSSymbolicatorCreateWithMachKernel(). + * addr: The address that needs to be symbolicated. + * binaryImages: The dictionary that aggregates binary image info for offline symbolication. + */ +void PrintSymbolicatedAddress(CSSymbolicatorRef sym, mach_vm_address_t addr, CFMutableDictionaryRef binaryImages); + +/* + * Call this function to dump binary image info required for offline symbolication. + * + * binaryImages: The dictionary that stores this info. + * + * The preferred way to use this is to create a CFMutableDictionaryRef with a call to CFDictionaryCreateMutable() + * and pass it in to PrintSymbolicatedAddress() when symbolicating addresses. This will auto-populate the dictionary, + * which just needs to be passed in here to print the relevant information. + */ +void PrintBinaryImagesInfo(CFMutableDictionaryRef binaryImages); + +#endif /* SymbolicationHelper_h */ diff --git a/system_cmds/zlog.tproj/entitlements.plist b/system_cmds/zlog.tproj/entitlements.plist new file mode 100644 index 0000000..600122d --- /dev/null +++ b/system_cmds/zlog.tproj/entitlements.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.private.kernel.get-kext-info</key> + <true/> +</dict> +</plist> diff --git a/system_cmds/zlog.tproj/zlog.1 b/system_cmds/zlog.tproj/zlog.1 new file mode 100644 index 0000000..141caa9 --- /dev/null +++ b/system_cmds/zlog.tproj/zlog.1 @@ -0,0 +1,56 @@ +.\" Copyright (c) 2018, Apple Inc. All rights reserved. +.\" +.Dd February 21, 2018 +.Dt ZLOG 1 +.Os "Mac OS X" +.Sh NAME +.Nm zlog +.Nd show allocation backtraces for kernel zones +.Sh SYNOPSIS +.Nm +.Op Fl t +.Op Fl z Ar name Op Fl n Ar num | Fl l +.Op Fl h +.Sh DESCRIPTION +.Nm +displays allocation (and free, if used in corruption tracking mode with +the boot-arg "-zc") backtraces for zones that have zone logging enabled. +Zone logging can be turned on by using the boot-arg "zlog<N>=<name>", +where 'N' can range from 1 to 10, and 'name' is the name of the zone to +be tracked. +.Pp +.Nm +interprets the following options: +.Pp +.Bl -tag -width "disable -" +.\" -t +.It Fl t +(Default) list all the zones that have logging enabled +.\" -z +.It Fl z Ar name +show all allocation backtraces for zone +.Ar name +.\" -n +.It Fl n Ar num +Can be used in combination with the +.Fl z +option to show the top +.Ar num +backtraces with the most active references in the zone +.Ar name +.\" -l +.It Fl l +Can be used in combination with the +.Fl z +option to show the backtrace most likely contributing to a leak in the zone +.Ar name +(prints the backtrace with the most active references) +.\" -h +.It Fl h +show the help text +.El +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr zprint 1 , +.Xr ioclasscount 1 diff --git a/system_cmds/zlog.tproj/zlog.c b/system_cmds/zlog.tproj/zlog.c new file mode 100644 index 0000000..c091120 --- /dev/null +++ b/system_cmds/zlog.tproj/zlog.c @@ -0,0 +1,257 @@ +// +// zlog.c +// zlog +// +// Created by Rasha Eqbal on 1/4/18. +// + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/sysctl.h> +#include <mach/mach.h> +#include <mach/mach_error.h> +#include <mach_debug/mach_debug.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreSymbolication/CoreSymbolication.h> +#include "SymbolicationHelper.h" + +extern kern_return_t +mach_zone_get_btlog_records(host_priv_t host, + mach_zone_name_t name, + zone_btrecord_array_t *recsp, + mach_msg_type_number_t *recsCntp); +extern kern_return_t +mach_zone_get_zlog_zones(host_priv_t host, + mach_zone_name_array_t *namesp, + mach_msg_type_number_t *namesCntp); + +static int compare_zone_btrecords(const void *left, const void *right); +static void usage(FILE *stream, char **argv); +static void print_zone_info(const char *name); +static void get_zone_btrecords(const char *name, int topN); +static void list_zones_with_zlog_enabled(void); + +static void usage(FILE *stream, char **argv) +{ + fprintf (stream, "usage: %s [-t] [-z name [-n num | -l]] [-h]\n", argv[0]); + fprintf (stream, " -t : list all the zones that have logging enabled\n"); + fprintf (stream, " -z <name> : show all allocation backtraces for zone <name>\n"); + fprintf (stream, " -n <num> : show top <num> backtraces with the most active references in zone <name>\n"); + fprintf (stream, " -l : show the backtrace most likely contributing to a leak in zone <name>\n"); + fprintf (stream, " (prints the backtrace with the most active references)\n"); + fprintf (stream, " -h : print this help text\n"); + exit(stream != stdout); +} + +static int compare_zone_btrecords(const void *left, const void *right) +{ + zone_btrecord_t *btl = (zone_btrecord_t *)left; + zone_btrecord_t *btr = (zone_btrecord_t *)right; + + return (btr->ref_count - btl->ref_count); +} + +static void print_zone_info(const char *name) +{ + mach_zone_name_t zname; + mach_zone_info_t zone_info; + kern_return_t kr; + + strcpy(zname.mzn_name, name); + kr = mach_zone_info_for_zone(mach_host_self(), zname, &zone_info); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "error: call to mach_zone_info_for_zone() failed: %s\n", mach_error_string(kr)); + exit(1); + } + printf("zone name : %s\n", name); + printf("element size (bytes) : %lld\n", zone_info.mzi_elem_size); + printf("in-use size (bytes) : %lld\n", zone_info.mzi_count * zone_info.mzi_elem_size); + printf("total size (bytes) : %lld\n", zone_info.mzi_cur_size); + printf("\n"); +} + +static void get_zone_btrecords(const char *name, int topN) +{ + kern_return_t kr; + int i, j, index; + mach_zone_name_t zname; + unsigned int recs_count = 0; + zone_btrecord_t *recs, *recs_addr = NULL; + CSSymbolicatorRef kernelSym; + CFMutableDictionaryRef binaryImages; + + /* Create kernel symbolicator */ + kernelSym = CSSymbolicatorCreateWithMachKernel(); + if (CSIsNull(kernelSym)) { + fprintf(stderr, "error: CSSymbolicatorCreateWithMachKernel() returned NULL\n"); + exit(1); + } + /* Create dictionary to collect binary image info for offline symbolication */ + binaryImages = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + /* Query the kernel for backtrace records */ + strcpy(zname.mzn_name, name); + kr = mach_zone_get_btlog_records(mach_host_self(), zname, &recs_addr, &recs_count); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "error: call to mach_zone_get_btlog_records() failed: %s\n", mach_error_string(kr)); + exit(1); + } + + if (recs_count == 0) { + goto finish; + } + + recs = recs_addr; + if (topN == 1) { + /* Print the backtrace with the highest no. of refs */ + index = 0; + for (i = 0; i < recs_count; i++) { + if (recs[i].ref_count > recs[index].ref_count) { + index = i; + } + } + recs = recs_addr + index; + } else if (topN == 0) { + /* Print all backtraces */ + topN = recs_count; + } else { + /* Sort the records by no. of refs, and print the top <topN> */ + qsort(recs, recs_count, sizeof *recs, compare_zone_btrecords); + } + + printf("printing top %d (out of %d) allocation backtrace(s) for zone %s\n", topN, recs_count, zname.mzn_name); + + for (i = 0; i < topN; i++) { + printf("\nactive refs: %d operation type: %s\n", recs[i].ref_count, + (recs[i].operation_type == ZOP_ALLOC)? "ALLOC": (recs[i].operation_type == ZOP_FREE)? "FREE": "UNKNOWN"); + + for (j = 0; j < MAX_ZTRACE_DEPTH; j++) { + mach_vm_address_t addr = (mach_vm_address_t)recs[i].bt[j]; + if (!addr) { + break; + } + PrintSymbolicatedAddress(kernelSym, addr, binaryImages); + } + } + + /* Print relevant info for offline symbolication */ + PrintBinaryImagesInfo(binaryImages); + CFRelease(binaryImages); + +finish: + if ((recs_addr != NULL) && (recs_count != 0)) { + kr = vm_deallocate(mach_task_self(), (vm_address_t) recs_addr, (vm_size_t) (recs_count * sizeof *recs)); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "call to vm_deallocate() failed: %s\n", mach_error_string(kr)); + exit(1); + } + } + CSRelease(kernelSym); +} + +static void list_zones_with_zlog_enabled(void) +{ + kern_return_t kr; + mach_zone_name_t *name = NULL; + unsigned int name_count = 0, i; + + /* Get names for zones that have zone logging enabled */ + kr = mach_zone_get_zlog_zones(mach_host_self(), &name, &name_count); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "error: call to mach_zone_get_zlog_zones() failed: %s\n", mach_error_string(kr)); + exit(1); + } + + if (name_count == 0) { + printf("zlog not enabled for any zones.\n"); + } else { + printf("zlog enabled for zones...\n"); + } + + for (i = 0; i < name_count; i++) { + print_zone_info(name[i].mzn_name); + } + + if ((name != NULL) && (name_count != 0)) { + kr = vm_deallocate(mach_task_self(), (vm_address_t) name, (vm_size_t) (name_count * sizeof *name)); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "call to vm_deallocate() failed: %s\n", mach_error_string(kr)); + exit(1); + } + } +} + +#define VERSION_STRING "zlog output version: 1" + +static void +get_osversion(char *buffer, size_t buffer_len) +{ + int mib[] = {CTL_KERN, KERN_OSVERSION}; + int ret; + ret = sysctl(mib, sizeof(mib) / sizeof(int), buffer, &buffer_len, NULL, 0); + if (ret != 0) { + strlcpy(buffer, "Unknown", buffer_len); + return; + } +} + +int main(int argc, char *argv[]) +{ + int c, topN = 0; + const char *zone_name = NULL; + + /* Identifier string for SpeedTracer parsing */ + printf("%s\n\n", VERSION_STRING); + char version_buffer[32] = {0}; + get_osversion(version_buffer, sizeof(version_buffer)); + printf("Collected on build: %s\n\n", version_buffer); + + if (argc == 1) { + /* default when no arguments are specified */ + list_zones_with_zlog_enabled(); + printf("Run 'zlog -h' for usage info.\n"); + return 0; + } + + while ((c = getopt(argc, argv, "tz:n:lh")) != -1) { + switch(c) { + case 't': + list_zones_with_zlog_enabled(); + break; + case 'z': + zone_name = optarg; + break; + case 'n': + topN = atoi(optarg); + break; + case 'l': + topN = 1; + break; + case 'h': + usage(stdout, argv); + break; + case '?': + default: + usage(stderr, argv); + break; + } + } + + if (optind < argc) { + usage(stderr, argv); + } + + if (zone_name) { + print_zone_info(zone_name); + get_zone_btrecords(zone_name, topN); + } else { + /* -n or -l was specified without -z */ + if (topN != 0) { + usage(stderr, argv); + } + } + + return 0; +} |