aboutsummaryrefslogtreecommitdiff
path: root/Foundation
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-05-09 21:06:16 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-05-09 21:06:16 +0000
commit2ce0b6a3abc20dcb03c624fb0598e0ada95096c5 (patch)
tree972d930b60b448bdfaa3e6477923cc9d133031ab /Foundation
parentf90bcf3263b80b96754977ddbd5309704cf817fb (diff)
fix a iphone unittesting problem.
add stack trace.
Diffstat (limited to 'Foundation')
-rw-r--r--Foundation/GTMStackTrace.c92
-rw-r--r--Foundation/GTMStackTrace.h82
-rw-r--r--Foundation/GTMStackTraceTest.m80
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