You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过代码获取并NSLog所有线程的调用栈(非仅当前线程)

How to Log All Thread Call Stacks on Main Thread Condition Trigger

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:

  1. Monitor your main thread for the trigger condition (timeout, unexpected window exit, etc.).
  2. When the condition hits, enumerate every thread in your app.
  3. 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—NSThread only gives you access to the current thread or threads you've explicitly created.
  • Stack Capture: backtrace() and backtrace_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 NSWindowDidExitFullScreenNotification and 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 symbolicatecrash tool 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:35:57