| #import "LPStackTraceGenerator.h" | |
| #include <execinfo.h> | |
| #include <mach/mach.h> | |
| #if defined(__x86_64__) || defined(__arm64__) | |
| // To get the FP_LINK_OFFSET and ISALIGNED for the new architecture, | |
| // please check the lastest version of | |
| // https://opensource.apple.com/source/Libc/Libc-1272.250.1/gen/thread_stack_pcs.c.auto.html | |
| #if defined(__x86_64__) || defined(__arm64__) | |
| #define FP_LINK_OFFSET 1 | |
| #endif | |
| #if defined(__x86_64__) | |
| #define ISALIGNED(a) ((((uintptr_t)(a)) & 0xf) == 0) | |
| #elif defined(__arm64__) | |
| #define ISALIGNED(a) ((((uintptr_t)(a)) & 0x1) == 0) | |
| #endif | |
| #define ISVALID(a) \ | |
| ((a) >= stackbot && (a) <= stacktop && frame != NULL && (uintptr_t)a >= 4096 && ISALIGNED(a)) | |
| static _STRUCT_MCONTEXT GetContext(pthread_t pthread); | |
| static void *GetFP(_STRUCT_MCONTEXT ctx); | |
| static void *GetPC(_STRUCT_MCONTEXT ctx); | |
| static bool SafeReadMemory(vm_address_t src, void *dest, size_t len); | |
| /** | |
| * Stack unwind by using frame pointer. | |
| * | |
| * @param buffer Output. Array of addresses. | |
| * @param max Size of buffer. | |
| * @param *frames Output. Number of acutal frames. | |
| * @param thread Target Thread. | |
| * @param frame Frame pointer of target thread. | |
| */ | |
| static void TheadStackUnwind(vm_address_t *buffer, unsigned max, unsigned *frames, pthread_t thread, | |
| void *frame); | |
| static NSString *getThreadName(pthread_t thread); | |
| static inline _STRUCT_MCONTEXT GetContext(pthread_t pthread) { | |
| thread_t thread = pthread_mach_thread_np(pthread); | |
| _STRUCT_MCONTEXT ctx; | |
| #if defined(__x86_64__) | |
| mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; | |
| thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count); | |
| #elif defined(__arm64__) | |
| mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; | |
| thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count); | |
| #endif | |
| return ctx; | |
| } | |
| static inline void *GetFP(_STRUCT_MCONTEXT ctx) { | |
| #if defined(__x86_64__) | |
| void *fp = (void *)ctx.__ss.__rbp; | |
| #elif defined(__arm64__) | |
| void *fp = (void *)ctx.__ss.__fp; | |
| #endif | |
| return fp; | |
| } | |
| static inline void *GetPC(_STRUCT_MCONTEXT ctx) { | |
| #if defined(__x86_64__) | |
| void *pc = (void *)ctx.__ss.__rip; | |
| #elif defined(__arm64__) | |
| void *pc = (void *)ctx.__ss.__pc; | |
| #endif | |
| return pc; | |
| } | |
| static bool SafeReadMemory(vm_address_t src, void *dest, size_t len) { | |
| vm_size_t readSize = len; | |
| return vm_read_overwrite(mach_task_self(), src, len, (pointer_t)dest, &readSize) == KERN_SUCCESS; | |
| } | |
| static void TheadStackUnwind(vm_address_t *buffer, unsigned max, unsigned *frames, pthread_t thread, | |
| void *frame) { | |
| void *next; | |
| void *stacktop = pthread_get_stackaddr_np(thread); | |
| void *stackbot = stacktop - pthread_get_stacksize_np(thread); | |
| *frames = 0; | |
| // make sure return address is never out of bounds | |
| stacktop -= (FP_LINK_OFFSET + 1) * sizeof(void *); | |
| if (!ISVALID(frame)) return; | |
| while (max--) { | |
| buffer[*frames] = *(vm_address_t *)(((void **)frame) + FP_LINK_OFFSET); | |
| (*frames)++; | |
| if (!SafeReadMemory((vm_address_t)frame, &next, sizeof(next))) { | |
| NSLog(@"Unable to read memory in stack unwinding. Address: %llu", (uint64_t)frame); | |
| return; | |
| } | |
| if (!ISVALID(next) || next <= frame) return; | |
| frame = next; | |
| } | |
| } | |
| static NSString *getThreadName(pthread_t thread) { | |
| char name[128]; | |
| if (pthread_getname_np(thread, name, sizeof(name)) != 0) { | |
| return @""; | |
| } | |
| return [NSString stringWithCString:name encoding:NSASCIIStringEncoding]; | |
| } | |
| int LPSnapshotThreadStackTrace(void **buffer, int size, pthread_t thread) { | |
| if (buffer == NULL || size == 0) { | |
| return 0; | |
| } | |
| unsigned int num_frames; | |
| _STRUCT_MCONTEXT ctx = GetContext(thread); | |
| // Current instruction. | |
| void *frame = GetFP(ctx); | |
| buffer[0] = GetPC(ctx); | |
| TheadStackUnwind((vm_address_t *)buffer + 1, --size, &num_frames, thread, frame); | |
| // Index 0 is used by the current instruction. | |
| num_frames += 1; | |
| while (num_frames >= 1 && buffer[num_frames - 1] == NULL) { | |
| num_frames -= 1; | |
| } | |
| return num_frames; | |
| } | |
| int LPSnapshotAllThreadsStackTrace(LPThreadStackTrace *buffer, int size) { | |
| if (buffer == NULL || size == 0) { | |
| return 0; | |
| } | |
| mach_msg_type_number_t threads_count; | |
| thread_act_array_t threads_list; | |
| task_threads(mach_task_self(), &threads_list, &threads_count); | |
| int count; | |
| for (count = 0; count < threads_count && count < size; ++count) { | |
| pthread_t pthread = pthread_from_mach_thread_np(threads_list[count]); | |
| int frames = LPSnapshotThreadStackTrace(buffer[count].stacktrace, | |
| kLPThreadStackTraceMaxFramesCount, pthread); | |
| buffer[count].frames = frames; | |
| buffer[count].thread = pthread; | |
| } | |
| return count; | |
| } | |
| NSArray<NSString *> *LPStackTraceString(void **buffer, int size) { | |
| if (buffer == NULL || size == 0) { | |
| return @[]; | |
| } | |
| char **strings = backtrace_symbols(buffer, size); | |
| if (strings == NULL) { | |
| return @[]; | |
| } | |
| NSMutableArray<NSString *> *result = [NSMutableArray array]; | |
| for (int i = 0; i < size; i++) { | |
| [result addObject:[NSString stringWithCString:strings[i] encoding:NSASCIIStringEncoding]]; | |
| } | |
| free(strings); | |
| return result; | |
| } | |
| NSArray<NSArray<NSString *> *> *LPStackTracesString(LPThreadStackTrace *buffer, int size) { | |
| if (buffer == NULL || size == 0) { | |
| return @[]; | |
| } | |
| NSMutableArray<NSArray<NSString *> *> *result = [NSMutableArray array]; | |
| for (int i = 0; i < size; i++) { | |
| NSMutableArray<NSString *> *threadResult = [NSMutableArray array]; | |
| [threadResult addObject:[NSString stringWithFormat:@"Thread %d name: %@", i, | |
| getThreadName(buffer[i].thread)]]; | |
| [threadResult addObjectsFromArray:LPStackTraceString(buffer[i].stacktrace, buffer[i].frames)]; | |
| [result addObject:threadResult]; | |
| } | |
| return result; | |
| } | |
| #endif |