blob: 4470a11683f17015535668b72a3a875abe94d95e [file] [log] [blame]
//
// GTMStackTrace.m
//
// Copyright 2007-2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//
#include <stdlib.h>
#include <dlfcn.h>
#include <mach-o/nlist.h>
#include <objc/runtime.h>
#include "GTMStackTrace.h"
struct GTMClassDescription {
const char *class_name;
Method *class_methods;
unsigned int class_method_count;
Method *instance_methods;
unsigned int instance_method_count;
};
#pragma mark Private utility functions
static struct GTMClassDescription *GTMClassDescriptions(NSUInteger *total_count) {
int class_count = objc_getClassList(nil, 0);
struct GTMClassDescription *class_descs
= calloc(class_count, sizeof(struct GTMClassDescription));
if (class_descs) {
Class *classes = calloc(class_count, sizeof(Class));
if (classes) {
objc_getClassList(classes, class_count);
for (int i = 0; i < class_count; ++i) {
class_descs[i].class_methods
= class_copyMethodList(object_getClass(classes[i]),
&class_descs[i].class_method_count);
class_descs[i].instance_methods
= class_copyMethodList(classes[i],
&class_descs[i].instance_method_count);
class_descs[i].class_name = class_getName(classes[i]);
}
free(classes);
} else {
// COV_NF_START - Don't know how to force this in a unittest
free(class_descs);
class_descs = NULL;
class_count = 0;
// COV_NF_END
}
}
if (total_count) {
*total_count = class_count;
}
return class_descs;
}
static void GTMFreeClassDescriptions(struct GTMClassDescription *class_descs,
NSUInteger count) {
if (!class_descs) return;
for (NSUInteger i = 0; i < count; ++i) {
if (class_descs[i].instance_methods) {
free(class_descs[i].instance_methods);
}
if (class_descs[i].class_methods) {
free(class_descs[i].class_methods);
}
}
free(class_descs);
}
static NSUInteger GTMGetStackAddressDescriptorsForAddresses(void *pcs[],
struct GTMAddressDescriptor outDescs[],
NSUInteger count) {
if (count < 1 || !pcs || !outDescs) return 0;
NSUInteger class_desc_count;
// Get our obj-c class descriptions. This is expensive, so we do it once
// at the top. We go through this because dladdr doesn't work with
// obj methods.
struct GTMClassDescription *class_descs
= GTMClassDescriptions(&class_desc_count);
if (class_descs == NULL) {
class_desc_count = 0;
}
// Iterate through the stack.
for (NSUInteger i = 0; i < count; ++i) {
const char *class_name = NULL;
BOOL is_class_method = NO;
size_t smallest_diff = SIZE_MAX;
struct GTMAddressDescriptor *currDesc = &outDescs[i];
currDesc->address = pcs[i];
Method best_method = NULL;
// Iterate through all the classes we know of.
for (NSUInteger j = 0; j < class_desc_count; ++j) {
// First check the class methods.
for (NSUInteger k = 0; k < class_descs[j].class_method_count; ++k) {
void *imp = (void *)method_getImplementation(class_descs[j].class_methods[k]);
if (imp <= currDesc->address) {
size_t diff = (size_t)currDesc->address - (size_t)imp;
if (diff < smallest_diff) {
best_method = class_descs[j].class_methods[k];
class_name = class_descs[j].class_name;
is_class_method = YES;
smallest_diff = diff;
}
}
}
// Then check the instance methods.
for (NSUInteger k = 0; k < class_descs[j].instance_method_count; ++k) {
void *imp = (void *)method_getImplementation(class_descs[j].instance_methods[k]);
if (imp <= currDesc->address) {
size_t diff = (size_t)currDesc->address - (size_t)imp;
if (diff < smallest_diff) {
best_method = class_descs[j].instance_methods[k];
class_name = class_descs[j].class_name;
is_class_method = NO;
smallest_diff = diff;
}
}
}
}
// If we have one, store it off.
if (best_method) {
currDesc->symbol = sel_getName(method_getName(best_method));
currDesc->is_class_method = is_class_method;
currDesc->class_name = class_name;
}
Dl_info info = { NULL, NULL, NULL, NULL };
// Check to see if the one returned by dladdr is better.
dladdr(currDesc->address, &info);
if ((size_t)currDesc->address - (size_t)info.dli_saddr < smallest_diff) {
currDesc->symbol = info.dli_sname;
currDesc->is_class_method = NO;
currDesc->class_name = NULL;
}
currDesc->filename = info.dli_fname;
if (!currDesc->symbol) {
currDesc->symbol = "???";
currDesc->is_class_method = NO;
currDesc->class_name = NULL;
}
}
GTMFreeClassDescriptions(class_descs, class_desc_count);
return count;
}
static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor descs[],
NSUInteger count) {
NSMutableString *trace = [NSMutableString string];
for (NSUInteger i = 0; i < count; i++) {
// Newline between all the lines
if (i) {
[trace appendString:@"\n"];
}
NSString *fileName = nil;
if (descs[i].filename) {
fileName = [NSString stringWithCString:descs[i].filename
encoding:NSUTF8StringEncoding];
fileName = [fileName lastPathComponent];
} else {
fileName = @"??";
}
if (descs[i].class_name) {
[trace appendFormat:@"#%-2lu %-35s %#0*lX %s[%s %s]",
(unsigned long)i,
[fileName UTF8String],
// sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2
// for the 0x prefix
(int)(sizeof(void *) * 2 + 2),
(unsigned long)descs[i].address,
(descs[i].is_class_method ? "+" : "-"),
descs[i].class_name,
(descs[i].symbol ? descs[i].symbol : "??")];
} else {
[trace appendFormat:@"#%-2lu %-35s %#0*lX %s()",
(unsigned long)i,
[fileName UTF8String],
// sizeof(void*) * 2 is the length of the hex address (32 vs 64) and + 2
// for the 0x prefix
(int)(sizeof(void *) * 2 + 2),
(unsigned long)descs[i].address,
(descs[i].symbol ? descs[i].symbol : "??")];
}
}
return trace;
}
#pragma mark Public functions
NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[],
NSUInteger count) {
if (count < 1 || !outDescs) return 0;
NSUInteger result = 0;
NSArray *addresses = [NSThread callStackReturnAddresses];
NSUInteger addrCount = [addresses count];
if (addrCount) {
void **pcs = calloc(addrCount, sizeof(void*));
if (pcs) {
void **pcsScanner = pcs;
for (NSNumber *address in addresses) {
NSUInteger addr = [address unsignedIntegerValue];
*pcsScanner = (void *)addr;
++pcsScanner;
}
if (count < addrCount) {
addrCount = count;
}
// Fill in the desc structures
result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, addrCount);
}
free(pcs);
}
return result;
}
NSString *GTMStackTrace(void) {
// If we don't have enough frames, return an empty string
NSString *result = @"";
NSArray *addresses = [NSThread callStackReturnAddresses];
NSUInteger count = [addresses count];
if (count) {
void **pcs = calloc(count, sizeof(void*));
struct GTMAddressDescriptor *descs
= calloc(count, sizeof(struct GTMAddressDescriptor));
if (pcs && descs) {
void **pcsScanner = pcs;
for (NSNumber *address in addresses) {
NSUInteger addr = [address unsignedIntegerValue];
*pcsScanner = (void *)addr;
++pcsScanner;
}
// Fill in the desc structures
count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count);
// Build the trace
// We skip 1 frame because the +[NSThread callStackReturnAddresses] will
// start w/ this frame.
const size_t kTracesToStrip = 1;
if (count > kTracesToStrip) {
result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
(count - kTracesToStrip));
}
}
free(pcs);
free(descs);
}
return result;
}
NSString *GTMStackTraceFromException(NSException *e) {
NSString *trace = @"";
// collect the addresses
NSArray *addresses = [e callStackReturnAddresses];
NSUInteger count = [addresses count];
if (count) {
void **pcs = calloc(count, sizeof(void*));
struct GTMAddressDescriptor *descs
= calloc(count, sizeof(struct GTMAddressDescriptor));
if (pcs && descs) {
void **pcsScanner = pcs;
for (NSNumber *address in addresses) {
NSUInteger addr = [address unsignedIntegerValue];
*pcsScanner = (void *)addr;
++pcsScanner;
}
// Fill in the desc structures
count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count);
// Build the trace
trace = GTMStackTraceFromAddressDescriptors(descs, count);
}
free(pcs);
free(descs);
}
return trace;
}