aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Foundation/GTMStackTrace.c92
-rw-r--r--Foundation/GTMStackTrace.h82
-rw-r--r--Foundation/GTMStackTraceTest.m80
-rw-r--r--GTM.xcodeproj/project.pbxproj12
-rw-r--r--ReleaseNotes.txt2
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.h2
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"