缘由: 在程序崩溃的时分很简单获取到仓库信息,程序员很简单检查到由于哪个函数导致的崩溃,但是卡顿现象和高CPU利用率的时分要检查线程的仓库信息,体系暂未提供办法,一切有了这篇文章

卡顿的时分首先要获取到线程,依据线程再获取仓库信息。

1. 获取线程

mach-o/dyld.hmach-o/nlist.h``pthread.h中找到函数task_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. 获取栈顶游标

依据BSStackFrameEntry这个单项链表能够获取到一切栈信息

//依据这个链表能够获取到一切栈信息的地址
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);
}

符号化

backtraceBuffer中的指针地址符号化填充到Dl_info结构体中

首先遍历符号表

//符号化
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;
}

参考代码

BSBacktraceLogger