如何通过代码获取并NSLog所有线程的调用栈(非仅当前线程)
Alright, let's break down how to solve this problem—logging every thread's call stack when your main thread hits a specific condition (like a timeout) to track down that elusive fullscreen NSWindow bug. This is exactly the kind of tool you need for those hard-to-reproduce issues where the desktop unexpectedly shows up with multi-NSScreen fullscreen setups, especially when drivers (audio, video, camera, USB, Bluetooth, third-party ones) might be the culprit.
Core Approach
The plan is straightforward:
- Monitor your main thread for the trigger condition (timeout, unexpected window exit, etc.).
- When the condition hits, enumerate every thread in your app.
- Capture and log each thread's call stack so you can trace what was running when the bug occurred.
Full Implementation Code
First, here's a method to capture and log all thread call stacks. We'll use Mach APIs to get every thread since NSThread doesn't have a built-in way to list all threads in the process:
#import <mach/mach.h> #import <pthread.h> #import <execinfo.h> - (void)logAllThreadCallStacks { // Fetch all threads in the current task thread_act_array_t threads; mach_msg_type_number_t threadCount; kern_return_t kr = task_threads(mach_task_self(), &threads, &threadCount); if (kr != KERN_SUCCESS) { NSLog(@"Failed to retrieve thread list: %s", mach_error_string(kr)); return; } for (mach_msg_type_number_t i = 0; i < threadCount; i++) { thread_t machThread = threads[i]; // Convert Mach thread to pthread_t for easier handling pthread_t pthread; kr = pthread_from_mach_thread_np(machThread, &pthread); if (kr != 0) { NSLog(@"Couldn't convert Mach thread to pthread: %s", strerror(kr)); continue; } // Get thread name (if set) const char *threadName = pthread_getname_np(pthread); NSString *readableName = threadName ? [NSString stringWithUTF8String:threadName] : @"Unnamed Thread"; // Capture call stack using backtrace() void *stackBuffer[128]; int frameCount = backtrace(stackBuffer, 128); char **stackSymbols = backtrace_symbols(stackBuffer, frameCount); // Log the thread's details and stack NSLog(@"=== Thread: %@ (pthread: %p) ===", readableName, pthread); if (stackSymbols) { for (int j = 0; j < frameCount; j++) { NSLog(@"%s", stackSymbols[j]); } free(stackSymbols); } else { NSLog(@"Failed to generate stack symbols for this thread"); } // Clean up Mach thread port kr = mach_port_deallocate(mach_task_self(), machThread); if (kr != KERN_SUCCESS) { NSLog(@"Failed to deallocate thread port: %s", mach_error_string(kr)); } } // Free the thread array vm_deallocate(mach_task_self(), (vm_address_t)threads, threadCount * sizeof(thread_t)); }
Next, set up the condition trigger. Let's use a timeout example—say you want to log stacks if the main thread is blocked for more than 5 seconds after setting up fullscreen windows:
- (void)setupMainThreadTimeoutMonitor { const NSTimeInterval timeoutDuration = 5.0; // Adjust based on your bug's timing dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Wait for the timeout period [NSThread sleepForTimeInterval:timeoutDuration]; // Check if the main thread is stuck (or your custom condition) // For example: if your fullscreen windows are no longer active when they should be if (!self.areFullscreenWindowsActive) { // Dispatch back to main to log (since some APIs need main thread) dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"=== MAIN THREAD TIMEOUT / BUG TRIGGERED! LOGGING ALL THREAD STACKS ==="); [self logAllThreadCallStacks]; // Optional: Save logs to a file for later review NSString *logFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"BugStacks.log"]; NSString *logHeader = @"=== Bug Triggered at %@ ===\n\n"; NSString *headerWithTime = [NSString stringWithFormat:logHeader, [NSDate date]]; [headerWithTime writeToFile:logFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; // Append the stack logs to the file here if needed }); } }); }
Key Details to Note
- Thread Enumeration: Mach APIs are the reliable way to get every thread in your app—
NSThreadonly gives you access to the current thread or threads you've explicitly created. - Stack Capture:
backtrace()andbacktrace_symbols()are POSIX functions that work across all threads, giving you a detailed call stack including framework symbols. - Condition Adaptation: Instead of a timeout, you can trigger the log when you detect the bug directly—like listening for
NSWindowDidExitFullScreenNotificationand checking if the exit was unexpected. - Driver Clues: Look for threads with symbols from IOKit, CoreAudio, AVFoundation, or third-party driver frameworks in the logs—these are likely suspects for your fullscreen issue.
Pro Tips for Debugging
- Save Logs to File: NSLog can get lost if the app crashes, so writing stacks to a document directory file ensures you have a record.
- Symbolicate Stacks: If you see memory addresses instead of readable symbols, use Xcode's
symbolicatecrashtool to translate them into method names. - Test on Affected Devices: Since this is driver-related, run the debug build on the exact devices where the bug occurs to get accurate stack traces.
内容的提问来源于stack exchange,提问作者Keith Knauber

