iOS gets arbitrary thread stack information

Reason: It is easy to obtain the stack information when the program crashes, and it is easy for programmers to check which function caused the crash. However, when there is a freeze phenomenon and high CPU utilization, it is necessary to check the stack information of the thread. The system does not provide a method yet. , all with this article

When stuck, you must first obtain the thread, and then obtain the stack information according to the thread.

1. Get thread

Find the function in and mach-o/dyld.h, get the number of threads according to the function , and then according to the obtained threads, if you want to get the stacks of all threads, then traverse and save the stacks of each thread.mach-o/nlist.h``pthread.htask_threads()task_threads(mach_task_self(), &list, &count)_Nullable pthread_t pthread_from_mach_thread_np(mach_port_t);

#import <mach/mach.h>
#include <dlfcn.h>
#include <pthread.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <mach-o/dyld.h>
#include <mach-o/nlist.h>

#pragma -mark Convert NSThread to Mach thread


thread_t bs_machThreadFromNSThread(NSThread *nsthread) {
    char name[256];
    mach_msg_type_number_t count;
    thread_act_array_t list
    task_threads(mach_task_self(), &list, &count);

    NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
    NSString *originName = [nsthread name];
    [nsthread setName:[NSString stringWithFormat:@"%f", currentTimestamp]];

    if ([nsthread isMainThread]) {
        return (thread_t)main_thread_id;
    }
    for (int i = 0; i < count; ++i) {
    //获取到线程
        pthread_t pt = pthread_from_mach_thread_np(list[i]);
        if ([nsthread isMainThread]) {
            if (list[i] == main_thread_id) {
                return list[i];
            }
        }
        if (pt) {
            name[0] = '\0';
            pthread_getname_np(pt, name, sizeof name);
            //根据name进行判断是否是同一个线程
            if (!strcmp(name, [nsthread name].UTF8String)) {
                [nsthread setName:originName];
                return list[i];
            }
        }
    }
    [nsthread setName:originName];
    return mach_thread_self();
}
复制代码

2. Get the cursor on the top of the stack

BSStackFrameEntryAll stack information can be obtained according to this single item linked list

//根据这个链表可以获取到所有栈信息的地址
typedef struct BSStackFrameEntry{
// 上一个栈frame指针
    const struct BSStackFrameEntry *const previous;
    //当前栈 地址
    const uintptr_t return_address;
} BSStackFrameEntry;


//; 获取到线程 堆栈信息
NSString *_bs_backtraceOfThread(thread_t thread) {
//; 50个堆栈
    uintptr_t backtraceBuffer[50];
    int i = 0;
    NSMutableString *resultString = [[NSMutableString alloc] initWithFormat:@"Backtrace of Thread %u:\n", thread];

//    macho 上下文
    _STRUCT_MCONTEXT machineContext;

// ;   填充线程setate thread_get_state
    if(!bs_fillThreadStateIntoMachineContext(thread, &machineContext)) {
        return [NSString stringWithFormat:@"Fail to get information about thread: %u", thread];
    }
//;     获得当前上下文     return machineContext->__ss.BS_INSTRUCTION_ADDRESS; 的address 地址
//;    i386: machineContext->__ss->__eip x86 : __rip
//;     指令地址
    const uintptr_t instructionAddress = bs_mach_instructionAddress(&machineContext);
    backtraceBuffer[i] = instructionAddress;
    ++i;

//;  return machineContext->__ss.__lr; 链接寄存器
//; linkRegister = 0
    uintptr_t linkRegister = bs_mach_linkRegister(&machineContext);
    if (linkRegister) {
        backtraceBuffer[i] = linkRegister;
        i++;
    }

    if(instructionAddress == 0) {
        return @"Fail to get instruction address";
    }

//
    BSStackFrameEntry frame = {0};

    //获取当前上下文的指针
    const uintptr_t framePtr = bs_mach_framePointer(&machineContext);
//    将framPtr 数据同步到frame
    if(framePtr == 0 ||
       bs_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) {
        return @"Fail to get frame pointer";
    }

//    根据结构体进行移动,previous指向前一个函数地址,return_address 指向当前地址

//    一共获取50个堆栈信息 当previous ==0 或者 获取  向前移动指针失败 则终止

    for(; i < 50; i++) {
        backtraceBuffer[i] = frame.return_address;
        // 将vm 中的 指针frame.previous 指针数据 同步到frame上
        if(backtraceBuffer[i] == 0 ||
           frame.previous == 0 ||
           bs_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {
            break;
        }
    }

   
    int backtraceLength = i;
    Dl_info symbolicated[backtraceLength];
    // 符号化 将address 翻译成 可读符号
    bs_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0);
    for (int i = 0; i < backtraceLength; ++i) {
        [resultString appendFormat:@"%@", bs_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])];
    }
    [resultString appendFormat:@"\n"];
    return [resultString copy];
}

// 将vm中的src数据同步到dst中
kern_return_t bs_mach_copyMem(const void *const src, void *const dst, const size_t numBytes){
    vm_size_t bytesCopied = 0;
    return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied);

}
复制代码

Symbolic

backtraceBufferSymbolically fill the pointer address in Dl_infothe structure into the structure

first traverse the symbol table

//符号化

void bs_symbolicate(const uintptr_t* const backtraceBuffer,
                    Dl_info* const symbolsBuffer,
                    const int numEntries,
                    const int skippedEntries){

    int i = 0;

    if(!skippedEntries && i < numEntries) {
        bs_dladdr(backtraceBuffer[i], &symbolsBuffer[i]);
        i++;
    }
    
    for(; i < numEntries; i++) {
        bs_dladdr(CALL_INSTRUCTION_FROM_RETURN_ADDRESS(backtraceBuffer[i]), &symbolsBuffer[i]);
    }
}

// address: 内存地址
bool bs_dladdr(const uintptr_t address, Dl_info* const info) {

    info->dli_fname = NULL;
    info->dli_fbase = NULL;
    info->dli_sname = NULL;
    info->dli_saddr = NULL;
    
    //获取imageIdex 然后可以根据idx 得到ASLR的值,还有mach_header
    const uint32_t idx = bs_imageIndexContainingAddress(address);

    if(idx == UINT_MAX) {
        return false;
    }

//    获取当前 image header
    const struct mach_header* header = _dyld_get_image_header(idx);
//    imageVMAddrSlide: ASLR
    const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);

//    addressWithSlide: macho 中的地址 = 内存地址 -  ASLR
    const uintptr_t addressWithSlide = address - imageVMAddrSlide;
    
//    segmentBase 获取到macho seg的 base地址
    const uintptr_t segmentBase = bs_segmentBaseOfImageIndex(idx) + imageVMAddrSlide;
    if(segmentBase == 0) {
        return false;
    }
    info->dli_fname = _dyld_get_image_name(idx);
    info->dli_fbase = (void*)header;
    // Find symbol tables and get whichever symbol is closest to the address.
    const BS_NLIST* bestMatch = NULL;
    uintptr_t bestDistance = ULONG_MAX;
    uintptr_t cmdPtr = bs_firstCmdAfterHeader(header);
    if(cmdPtr == 0) {
        return false;
    }

    for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {
        const struct load_command* loadCmd = (struct load_command*)cmdPtr;
        if(loadCmd->cmd == LC_SYMTAB) {
            const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;
//            符号表初始地址 = segmentBase + symOff (偏移量)
            const BS_NLIST* symbolTable = (BS_NLIST*)(segmentBase + symtabCmd->symoff);
//            stringTable 地址= segmentBase+偏移量
            const uintptr_t stringTable = segmentBase + symtabCmd->stroff;
//            symtabCmd->nsyms 符号表个数
            for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) {
                // If n_value is 0, the symbol refers to an external object.
//                取出symbolBase 符号中在stringtable的偏移量,for循环最大的一个addressWithSlide >= symbolBase

//                就是需要的字符串,然后再stringtable中找出字符即可。
                if(symbolTable[iSym].n_value != 0) {
                    uintptr_t symbolBase = symbolTable[iSym].n_value;//符号 在stringtable的偏移量
                    uintptr_t currentDistance = addressWithSlide - symbolBase;
                    if((addressWithSlide >= symbolBase) &&
                       (currentDistance <= bestDistance)) {
                        bestMatch = symbolTable + iSym;
                        bestDistance = currentDistance;
                    }
                }
            }
            if(bestMatch != NULL) {
            // 内存中字符串的地址 = 符号表中的符号的偏移量+ASLR
                info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);
                //stringtable + 偏移量 = 字符串地址
                info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);

                if(*info->dli_sname == '_') {
                    info->dli_sname++;
                }
                // This happens if all symbols have been stripped.
                if(info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) {
                    info->dli_sname = NULL;
                }
                break;
            }
        }
        cmdPtr += loadCmd->cmdsize;
    }
    return true;
}
复制代码

Reference Code

BSBacktraceLogger

Guess you like

Origin juejin.im/post/7166905505934213128
Recommended