diff options
author | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-05-09 21:06:16 +0000 |
---|---|---|
committer | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-05-09 21:06:16 +0000 |
commit | 2ce0b6a3abc20dcb03c624fb0598e0ada95096c5 (patch) | |
tree | 972d930b60b448bdfaa3e6477923cc9d133031ab /Foundation | |
parent | f90bcf3263b80b96754977ddbd5309704cf817fb (diff) |
fix a iphone unittesting problem.
add stack trace.
Diffstat (limited to 'Foundation')
-rw-r--r-- | Foundation/GTMStackTrace.c | 92 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.h | 82 | ||||
-rw-r--r-- | Foundation/GTMStackTraceTest.m | 80 |
3 files changed, 254 insertions, 0 deletions
diff --git a/Foundation/GTMStackTrace.c b/Foundation/GTMStackTrace.c new file mode 100644 index 0000000..febf623 --- /dev/null +++ b/Foundation/GTMStackTrace.c @@ -0,0 +1,92 @@ +// +// 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 "GTMStackTrace.h" + +// Structure representing a small portion of a stack, starting from the saved +// frame pointer, and continuing through the saved program counter. +struct StackFrame { + void *saved_fp; +#if defined (__ppc__) || defined(__ppc64__) + void *padding; +#endif + void *saved_pc; +}; + +// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the +// current frame pointer. We then use the frame pointer to walk the stack +// picking off program counters and other saved frame pointers. This works +// great on i386, but PPC requires a little more work because the PC (or link +// register) isn't always stored on the stack. +// +int GTMGetStackProgramCounters(void *outPcs[], int size) { + if (!outPcs || (size < 1)) return 0; + + struct StackFrame *fp; +#if defined (__ppc__) || defined(__ppc64__) + outPcs[0] = __builtin_return_address(0); + fp = (struct StackFrame *)__builtin_frame_address(1); +#elif defined (__i386__) || defined(__x86_64__) + fp = (struct StackFrame *)__builtin_frame_address(0); +#else +#error architecture not supported +#endif + + int level = 0; + while (level < size) { + if (fp == NULL) { + level--; + break; + } + outPcs[level] = fp->saved_pc; + level++; + fp = (struct StackFrame *)fp->saved_fp; + } + + return level; +} + +CFStringRef GTMStackTraceCreate(void) { + // The maximum number of stack frames that we will walk. We limit this so + // that super-duper recursive functions (or bugs) don't send us for an + // infinite loop. + static const int kMaxStackTraceDepth = 100; + void *pcs[kMaxStackTraceDepth]; + int depth = kMaxStackTraceDepth; + depth = GTMGetStackProgramCounters(pcs, depth); + + CFMutableStringRef trace = CFStringCreateMutable(kCFAllocatorDefault, 0); + + for (int i = 0; i < depth; i++) { + Dl_info info = { 0 }; + dladdr(pcs[i], &info); + const char *symbol = info.dli_sname; + const char *fname = info.dli_fname; + + CFStringAppendFormat(trace, NULL, + CFSTR("#%-2d 0x%08lx %s () [%s]\n"), + i, pcs[i], + (symbol ? symbol : "??"), + (fname ? fname : "??")); + } + + return trace; +} diff --git a/Foundation/GTMStackTrace.h b/Foundation/GTMStackTrace.h new file mode 100644 index 0000000..cfb9c95 --- /dev/null +++ b/Foundation/GTMStackTrace.h @@ -0,0 +1,82 @@ +// +// GTMStackTrace.h +// +// 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 <CoreFoundation/CoreFoundation.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/// Returns a string containing a nicely formatted stack trace. +// The caller owns the returned CFStringRef and is responsible for releasing it. +// +// ***************************************************************************** +// The symbolic names returned for Objective-C methods will be INCORRECT. This +// is because dladdr() doesn't properly resolve Objective-C names. The symbol's +// address will be CORRECT, so will be able to use atos or gdb to get a properly +// resolved Objective-C name. -- 5/15/2007 +// TODO: write dladdr() replacement that works with Objective-C symbols. +// ***************************************************************************** +// +// This function gets the stack trace for the current thread, and is safe to +// use in production multi-threaded code. Typically this function will be used +// along with some loggins, as in the following: +// +// MyAppLogger(@"Should never get here:\n%@", GTMStackTrace()); +// +// Here is a sample stack trace returned from this function: +// +// #0 0x00002d92 D () [/Users/me/./StackLog] +// #1 0x00002e45 C () [/Users/me/./StackLog] +// #2 0x00002e53 B () [/Users/me/./StackLog] +// #3 0x00002e61 A () [/Users/me/./StackLog] +// #4 0x00002e6f main () [/Users/me/./StackLog] +// #5 0x00002692 tart () [/Users/me/./StackLog] +// #6 0x000025b9 tart () [/Users/me/./StackLog] +// +// If you're using this with Objective-C, you may want to use the GTMStackTrace() +// variant that autoreleases the returned string. +// +CFStringRef GTMStackTraceCreate(void); + +/// Wrapper that autoreleases the returned CFStringRef. +// This is simply for the convenience of those using Objective-C. +// +#if __OBJC__ +#include "GTMGarbageCollection.h" +#define GTMStackTrace() [GTMNSMakeCollectable(GTMStackTraceCreate()) autorelease] +#endif + +/// Returns an array of program counters from the current thread's stack. +// *** You should probably use GTMStackTrace() instead of this function *** +// However, if you actually want all the PCs in "void *" form, then this +// funtion is more convenient. +// +// Args: +// outPcs - an array of "void *" pointers to the program counters found on the +// current thread's stack. +// size - the size of outPcs +// +// Returns: +// The number of program counters actually added to outPcs. +// +int GTMGetStackProgramCounters(void *outPcs[], int size); + +#ifdef __cplusplus +} +#endif diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m new file mode 100644 index 0000000..7845a2d --- /dev/null +++ b/Foundation/GTMStackTraceTest.m @@ -0,0 +1,80 @@ +// +// GTMStackTraceTest.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. +// + +#import <SenTestingKit/SenTestingKit.h> +#import <Foundation/Foundation.h> +#import "GTMStackTrace.h" +#import "GTMSenTestCase.h" + +@interface GTMStackTraceTest : SenTestCase +@end + +@implementation GTMStackTraceTest + +- (void)testStackTraceBasic { + NSString *stacktrace = GTMStackTrace(); + NSArray *stacklines = [stacktrace componentsSeparatedByString:@"\n"]; + + STAssertGreaterThan([stacklines count], (NSUInteger)3, + @"stack trace must have > 3 lines"); + STAssertLessThan([stacklines count], (NSUInteger)25, + @"stack trace must have < 25 lines"); + + NSString *firstFrame = [stacklines objectAtIndex:0]; + NSRange range = [firstFrame rangeOfString:@"GTMStackTrace"]; + STAssertNotEquals(range.location, (NSUInteger)NSNotFound, + @"First frame should contain GTMStackTrace, stack trace: %@", + stacktrace); +} + +- (void)testProgramCountersBasic { + void *pcs[10]; + int depth = 10; + depth = GTMGetStackProgramCounters(pcs, depth); + + STAssertGreaterThan(depth, 3, @"stack trace must have > 3 lines"); + STAssertLessThanOrEqual(depth, 10, @"stack trace must have < 10 lines"); + + // pcs is an array of program counters from the stack. pcs[0] should match + // the call into GTMGetStackProgramCounters, which is tough for us to check. + // However, we can verify that pcs[1] is equal to our current return address + // for our current function. + void *current_pc = __builtin_return_address(0); + STAssertEquals(pcs[1], current_pc, @"pcs[1] should equal the current PC"); +} + +- (void)testProgramCountersMore { + void *pcs0[0]; + int depth0 = 0; + depth0 = GTMGetStackProgramCounters(pcs0, depth0); + STAssertEquals(depth0, 0, @"stack trace must have 0 lines"); + + void *pcs1[1]; + int depth1 = 1; + depth1 = GTMGetStackProgramCounters(pcs1, depth1); + STAssertEquals(depth1, 1, @"stack trace must have 1 lines"); + + void *pcs2[2]; + int depth2 = 2; + depth2 = GTMGetStackProgramCounters(pcs2, depth2); + STAssertEquals(depth2, 2, @"stack trace must have 2 lines"); + void *current_pc = __builtin_return_address(0); + STAssertEquals(pcs2[1], current_pc, @"pcs[1] should equal the current PC"); +} + +@end |