diff options
-rw-r--r-- | Foundation/GTMStackTrace.c | 92 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.h | 82 | ||||
-rw-r--r-- | Foundation/GTMStackTraceTest.m | 80 | ||||
-rw-r--r-- | GTM.xcodeproj/project.pbxproj | 12 | ||||
-rw-r--r-- | ReleaseNotes.txt | 2 | ||||
-rw-r--r-- | UnitTesting/GTMNSObject+UnitTesting.h | 2 |
6 files changed, 270 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 diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index d279b99..0891078 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -100,6 +100,9 @@ F42E09550D199BBF00D5DDE0 /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2930D198D24009257D2 /* GTMSystemVersion.m */; }; F42E095E0D199BD600D5DDE0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; F42E09AE0D19A62F00D5DDE0 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; }; + F431221C0DD4E3B800F45252 /* GTMStackTrace.c in Sources */ = {isa = PBXBuildFile; fileRef = F43122190DD4E3B800F45252 /* GTMStackTrace.c */; }; + F431221D0DD4E3B800F45252 /* GTMStackTrace.h in Headers */ = {isa = PBXBuildFile; fileRef = F431221A0DD4E3B800F45252 /* GTMStackTrace.h */; }; + F431221F0DD4E3C900F45252 /* GTMStackTraceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F431221B0DD4E3B800F45252 /* GTMStackTraceTest.m */; }; F435DE7C0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */; }; F435DE7D0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */; }; F435DE8B0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE8A0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff */; }; @@ -261,6 +264,9 @@ F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "GTM-Info.plist"; sourceTree = "<group>"; }; F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; }; + F43122190DD4E3B800F45252 /* GTMStackTrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = GTMStackTrace.c; sourceTree = "<group>"; }; + F431221A0DD4E3B800F45252 /* GTMStackTrace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMStackTrace.h; sourceTree = "<group>"; }; + F431221B0DD4E3B800F45252 /* GTMStackTraceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMStackTraceTest.m; sourceTree = "<group>"; }; F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.ppc64.tiff"; sourceTree = "<group>"; }; F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.x86_64.tiff"; sourceTree = "<group>"; }; F435DE8A0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.ppc64.tiff"; sourceTree = "<group>"; }; @@ -579,6 +585,9 @@ F47A79850D746EE9002302AB /* GTMScriptRunner.h */, F47A79860D746EE9002302AB /* GTMScriptRunner.m */, F47A79870D746EE9002302AB /* GTMScriptRunnerTest.m */, + F43122190DD4E3B800F45252 /* GTMStackTrace.c */, + F431221A0DD4E3B800F45252 /* GTMStackTrace.h */, + F431221B0DD4E3B800F45252 /* GTMStackTraceTest.m */, F48FE2920D198D24009257D2 /* GTMSystemVersion.h */, F48FE2930D198D24009257D2 /* GTMSystemVersion.m */, F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */, @@ -682,6 +691,7 @@ F41D258B0DBD21A300774EEB /* GTMBase64.h in Headers */, F435E0890DC63F6D0069CDE8 /* GTMHTTPFetcher.h in Headers */, F435E27F0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h in Headers */, + F431221D0DD4E3B800F45252 /* GTMStackTrace.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -950,6 +960,7 @@ 8BC04CDE0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */, F41D258F0DBD21B900774EEB /* GTMBase64Test.m in Sources */, F435E3940DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m in Sources */, + F431221F0DD4E3C900F45252 /* GTMStackTraceTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -979,6 +990,7 @@ F41D258C0DBD21A300774EEB /* GTMBase64.m in Sources */, F435E08A0DC63F6D0069CDE8 /* GTMHTTPFetcher.m in Sources */, F435E2800DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m in Sources */, + F431221C0DD4E3B800F45252 /* GTMStackTrace.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 29cf35a..65af31d 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -101,6 +101,8 @@ Changes since 1.0.0 - GTMDelegatingTableColumn get an overhaul to match the 10.5 sdk so it's closer to a dropin for previous sdks. +- Added GTMStackTrace. + Release 1.0.0 14-January-2008 diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h index c06e001..7253fd4 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.h +++ b/UnitTesting/GTMNSObject+UnitTesting.h @@ -23,6 +23,8 @@ #if GTM_MACOS_SDK #import <ApplicationServices/ApplicationServices.h> +#elif GTM_IPHONE_SDK +#import <CoreGraphics/CoreGraphics.h> #endif #import "GTMSenTestCase.h" |