Android Native 栈回溯流程分析

921 阅读40分钟

本文以 Android 12 中对 Native 代码 crash 后的栈回溯过程进行分析。

准备

在进入代码分析前,先对可能产生疑问的地方进行说明方便对代码理解。

关于其他方式请搜索其他文章。

捕获 crash

android 是类 linux 系统,因此它利用 linux 中的信号机制来捕获 crash 并获取用户栈上下文信息。

捕获信号

android 系统会对捕获如下信号:

代码所在目录:/system/core/debuggerd/

include/debuggerd/handler.h

static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {
  char value[PROP_VALUE_MAX] = "";
  bool enabled =
      !(__system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1") &&
        __system_property_get("debug.debuggerd.disable", value) > 0 && !strcmp(value, "1"));
  if (enabled) {
    sigaction(SIGABRT, action, nullptr); // 调用 about 程序产生的中断。
    sigaction(SIGBUS, action, nullptr); // 地址错误,如未对齐的地址。
    sigaction(SIGFPE, action, nullptr); // 算数运算错误,如除零,整数益出,无效浮点操作。
    sigaction(SIGILL, action, nullptr); // 终止程序运行。如执行错误,未知或特权指令时,操作系统会发送该指令。
    sigaction(SIGSEGV, action, nullptr);  // 段错误,一般访问错误的内存地址。
    sigaction(SIGSTKFLT, action, nullptr);
    sigaction(SIGSYS, action, nullptr);  // 非法系统调用。
    sigaction(SIGTRAP, action, nullptr); // 由断点指令或其它trap指令产生. Debugger模式下,设置断点,当程序运行到断点时候,就会引发SIGTRAP。
  }
  // DEBUGGER_SIGNAL
  sigaction(BIONIC_SIGNAL_DEBUGGER, action, nullptr);
}

获取 crash 函数的寄存器

信号处理函数具体细节忽略。仅需要关注第三个参数即ucontext_t类型。
ucontext_t->uc_mcontext.regs 数组中,保存了发生crash 函数当时的所有寄存器值。其中 pc 的值,就是产生 crash 的指令地址。

LR 寄存器:对应于 regs[30],函数返回地址。 SP 寄存器:对应于 regs[31],函数栈顶。
PC 寄存器:对应于 regs[32],当前指令地址。

handler/debuggerd_handler.cpp

// Handler that does crash dumping by forking and doing the processing in the child.
// Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump.
static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {
  // Make sure we don't change the value of errno, in case a signal comes in between the process
  // making a syscall and checking errno.
  ErrnoRestorer restorer;

  auto *ucontext = static_cast<ucontext_t*>(context);
...
}

/proc/{pid}/maps

通过它可以查看库、堆栈、文件等在进程内存中的位置信息和访问权限等信息。

在栈回溯中用来查找 crash 所在的库。

例:/proc/{pid}/maps 内容样式:

address           perms offset  dev   inode       pathname
00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon
00651000-00652000 r--p 00051000 08:02 173521      /usr/bin/dbus-daemon
00652000-00655000 rw-p 00052000 08:02 173521      /usr/bin/dbus-daemon
00e03000-00e24000 rw-p 00000000 00:00 0           [heap]
00e24000-011f7000 rw-p 00000000 00:00 0           [heap]
...
35b1800000-35b1820000 r-xp 00000000 08:02 135522  /usr/lib64/ld-2.15.so
35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522  /usr/lib64/ld-2.15.so
35b1a20000-35b1a21000 rw-p 00020000 08:02 135522  /usr/lib64/ld-2.15.so
35b1a21000-35b1a22000 rw-p 00000000 00:00 0
35b1c00000-35b1dac000 r-xp 00000000 08:02 135870  /usr/lib64/libc-2.15.so
35b1dac000-35b1fac000 ---p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870  /usr/lib64/libc-2.15.so
...
f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0    [stack:986]
...
7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0   [stack]
7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0   [vdso]

格式:

  • address: 表示使用的内存地址范围。
  • perms:表示内存的访问权限。 一共四位,前三位分别为读写执行,最后一位为 p 或 s,p 表示 private 即进程私有,s 表示 shared 即可共享。
  • offset:表示这段内存的内容在文件中的偏移。
  • dev:表示设备的主次版本号。
  • inode:表示文件的节点号。
  • pathname:可表示文件名或堆栈等。
    如:pc 值为 35b1800010 则表示 crash 发生在/usr/lib64/ld-2.15.so中。

eh_frame_hdr 和 eh_frame

eh_frame 和 eh_frame_hdr 是用来为支持异常的语言(如:c++),提供运行时信息,以便进行栈回溯。

eh_frame 与 debug_frame 格式基本相同,都遵循DWARF 格式,指令部分参考,DWARF 4

eh_frame_hdr 段

它内部包含一个二分搜索表用于快速查找 eh_frame 。

注意:下面介绍的格式和编码类型仅供参考,具体需要取查找官方文档。

格式:

EncodingFielddesc
unsigned byteversioneh_frame_hdr 格式的版本。
unsigned byteeh_frame_ptr_enceh_frame_ptr 字段的编码格式。
unsigned bytefde_count_encfde_count 字段的编码格式。如果值为 DW_EH_PE_omit,二分搜索表不展示。
unsigned bytetable_enc二分搜索项的编码格式。如果值为 DW_EH_PE_omit,二分搜索表不展示。
encodedeh_frame_ptr指向eh_frame节起始位置的编码值。
encodedfde_count二分搜索表项数量的编码值,也就是 fde 的数量
binary search table二分搜索表。包含fde_count个表项。每个表项有两个编码值,FDE 代表函数的起始指针位置和 FDE 在 elf 中偏移位置。按初始位置排序。

eh_frame 段

它会记录在栈帧中保存的寄存器的值。通过它可以找到执行某个指令时,返回地址等寄存器的值和栈帧基地址,从而进行栈回溯。

它包含一个或多个调用栈信息(Call Frame Information,简 CFI)。

CFI 由一个通用信息项(Common Information Entry,简 CIF)和多个帧描述项(Frame Description Entry,简 FDE)组成。

DWARF 是一个调试信息文件格式,被很多编译器和调试器用来提供源码级别调试。它满足了很多语言的需求(如:C,C++ 和 Fortran 等)并且被设计成支持扩展到其他语言。DWARF 是架构独立的,适用于任何处理器和操作系统。

CIE 格式:

EncodingFielddesc
LengthRequired4 字节无符号数, CIE 的长度,不包含字段自身。如果它的值是 0xffffffff 表示长度在 Extended Length 字段。 0xffffffff 转换成整数为 -1。
Extended LengthOptional8 字节无符号数,CIE 的长度,不包含 Length 和自身。它只有 Length 字段的值为 0xffffffff 时才展示。
CIE IDRequired4 字节无符号数,用来区分 CIE 或 FDE。值为 0 说明是 CIE。
VersionRequired1 字节,CFI 格式的版本
Augmentation StringRequirednul 结尾的字符串。提供额外描述信息。内容是大小写敏感。
Code Alignment FactorRequired一个无符号 LEB128 编码值,与高级定位指令的 delta 参数相乘
Data Alignment FactorRequired一个有符号 LEB128 编码值,与偏移指令的寄存器偏移参数相乘
Return Address RegisterRequired一个无符号 LEB128 编码值,表示函数返回地址的寄存器号
Augmentation Data LengthOptional一个无符号 LEB128 编码值,表示 Augmentation Data 的长度。 该字段仅在 Augmentation String 字段值中包含 "z" 时显示。
Augmentation DataOptional数据内容由 Augmentation String 内容决定。 该字段仅在 Augmentation String 字段值中包含 "z" 时显示。
Initial InstructionsRequired用来生成寄存器的初始值
Padding填充字节,用来地址对齐

FDE 格式:

EncodingFielddesc
LengthRequired4 字节无符号数, CIE 的长度,不包含字段自身。如果它的值是 0xffffffff 表示长度在 Extended Length 字段。
Extended LengthOptional8 字节无符号数,CIE 的长度,不包含 Length 和自身。它只有 Length 字段的值为 0xffffffff 时才展示。
CIE PointerRequired4 字节无符号数,从当前偏移值(该字段)减去该值,得到与它关联的 CIE 起始位置的偏移值
PC BeginRequired是一个编码值,表示 FDE 指令的初始地址,编码格式在 Augmentation Data 字段指定
PC RangeRequired指令数量
Augmentation Data LengthOptional一个无符号 LEB128 编码值,表示 Augmentation Data 的长度。 该字段仅在与它关联的 cie 中的 Augmentation String 字段值中包含 "z" 时显示。
Augmentation DataOptional它的内容是由与它关联的 cie 中的 Augmentation String 字段值决定。该字段仅在与它关联的 cie 中的 Augmentation String 字段值中包含 "z" 时显示。
Call Frame InstructionsRequired调用帧指令集
Padding填充字节,用来进行地址对齐

DWARF官方文档中的例子:

这个例子解析的是 debug_frame 段。

函数机代码: image.png

  • R8 is the return address
  • s = same_value rule
  • u = undefined rule
  • rN = register(N) rule
  • cN = offset(N) rule
  • a = architectural rule

<fs>: 表示栈帧大小。

所有寄存器变化的情况如下表: image.png

表格中的列:

  • Location 列:表示代码指令地址。
  • CFA 列:上一帧调用处栈指针的值(可以理解为当前栈帧的基地址)。其他寄存器的值都是相对于它计算出来的。
    例:foo+12 处:R6=c-8 值是 CFA - 8 即 [R7] + FS - 8。
  • RX:表示编号为 X 的寄存器。寄存器的求值是根据指令进行。这些指令对应相应的寄存器规则。
    寄存器的规则:
    • undefined 不需要恢复值。
    • same value 与上一帧中寄存器的值一样,未被修改。
    • offset(N) 寄存器上一次的值保存在 CFA + N 地址处。N 是一个有符号数。
    • val_offset(N) 寄存器上一次的值是 CFA + N 。N 是一个有符号数。
    • register(R) 寄存器上一次的值保存在另一个寄存器 R 中。
    • expression(E) 寄存器上一次值所在的地址由一个 DWARF 表达式 E 产生。
    • val_expression(E) 寄存器上一次的值由一个 DWARF 表达式 E 产生。
    • architectural 规则由外部定义。

上面的表格便于查看,实际上 CIE 和 FDE 的编码数据如下:

cie 数据格式:

image.png

  1. <fs> = frame size
  2. <caf> = code alignment factor
  3. <daf> = data alignment factor

图中 DW_CFA_XXXX 都是 DWARF 规范中定义的指令,用来设置 CFA 和寄存器规则。

fde 数据格式: image.png

可以看出通过 eh_frame 和 eh_frame_hdr 段,可以还原出寄存器在上一帧中的值。

另外注意这里不会每一行代码对应一条记录,而是只记录与上一次改变有差异的部分。

从 native 回溯到 java 层

从 native 函数回溯到 java 层函数:

  1. 获取 dex pc 即 java 层调用函数的内存地址。 dex pc 内存地址会作为寄存器 val_expression(E) 表达式的结果生成。
    val_expression 中指令开头会有如下两个指令,用来识别是否有 dex pc
//   OP_const4u (0x0c)  'D' 'E' 'X' '1'
//   OP_drop (0x13)
  1. art 虚拟机会在全局变量__dex_debug_descriptor中保存所有的 dex 的地址,因此找到 dex pc 后可以根据这个变量定位到 dex。

art/runtime/jit/debugger_interface.cc

  // The following globals mirror the ones above, but are used to register dex files.
  void __attribute__((noinline)) __dex_debug_register_code() {
    __asm__("");
  }
  void (*__dex_debug_register_code_ptr)() = __dex_debug_register_code;
  JITDescriptor __dex_debug_descriptor GUARDED_BY(g_dex_debug_lock) {}; 

JITDescriptor 它内部包含一个链表用来关联所有的 dex 或 jit 生成的 elf 文件。

查找 JIT 代码

android art 在 jit 编译时,会将热点代码优化为本地代码并缓存。

如果要回溯这部分中的代码,需要借助__jit_debug_descriptor变量。

art/runtime/jit/debugger_interface.cc

  // The root data structure describing of all JITed methods.
  JITDescriptor __jit_debug_descriptor GUARDED_BY(g_jit_debug_lock) {};

android 文档 image.png

源码分析

代码目录:/system/unwinding/libunwindstack

栈回溯主要分三步,后续分析根据这三步进行。

这里回溯与发生 crash 的程序在同一进程也可以不同。

...
  // 构造 UnwinderFromPid 对象。 设置最大要回溯的栈帧数量,crash 进程 id, 当前处理器架构类型
  unwindstack::UnwinderFromPid unwinder(256, vm_pid, unwindstack::Regs::CurrentArch());
  if (!unwinder.Init()) { // 初始化 unwinder
    LOG(FATAL) << "Failed to init unwinder object.";
  }
  ...
  // 从信号处理程序中获取的寄存器数组
  unwinder->SetRegs(thread.registers.get());
  // 开始解析
  unwinder->Unwind();
...

初始化

UnwinderFromPid 构造函数仅是简单的初始化。
max_frames:最大调用栈数量。
pid:crash 进程的 id。
arch:处理器类型。
maps:用来解析 crash 进程的虚拟内存(/proc/{pid}/maps)

libunwindstack/include/unwindstack/Unwinder.h

  UnwinderFromPid(size_t max_frames, pid_t pid, ArchEnum arch, Maps* maps = nullptr)
      : Unwinder(max_frames, arch, maps), pid_(pid) {}
  1. 解析 /proc/{pid}/maps 将结果保存在 Maps 的 maps_ (std::vector<std::unique_ptr>)中。
  2. 初始化 process_memory_。用来从指定的内存地址读取内存中的数据。
  3. 初始化 jit_debug_ 和 dex_files_,它们都使用的是 GlobalDebugImpl 模板类,分别用来查找 elf 和 dex 文件。
    jit_debug_ 主要查找经过 jit 后的 elf 文件。
    dex_files_ 主要查找 dex 中的函数。
    libunwindstack/Unwinder.cpp
bool UnwinderFromPid::Init() {
  CHECK(arch_ != ARCH_UNKNOWN);
  if (initted_) { // 防止多次初始化
    return true;
  }
  initted_ = true;

  if (maps_ == nullptr) { 
    // 根据是否在当前进程,进行不同初始化。
    // LocalMaps 和 RemoteMaps 逻辑相同,
    // 区别在于前者使用 /proc/self/maps
    // 后者使用 /proc/pid_/maps
    if (pid_ == getpid()) {
      maps_ptr_.reset(new LocalMaps());
    } else {
      maps_ptr_.reset(new RemoteMaps(pid_));
    }
    // 解析 /proc/self/maps。
    // 这里以本进程,进行分析。
    if (!maps_ptr_->Parse()) {
      ClearErrors();
      last_error_.code = ERROR_INVALID_MAP;
      return false;
    }
    maps_ = maps_ptr_.get();
  }
  // 与上面类似,根据是否在当前进程,进行不同初始化。  
  if (process_memory_ == nullptr) {
    if (pid_ == getpid()) {
      // Local unwind, so use thread cache to allow multiple threads
      // to cache data even when multiple threads access the same object.
      process_memory_ = Memory::CreateProcessMemoryThreadCached(pid_);
    } else {
      // Remote unwind should be safe to cache since the unwind will
      // be occurring on a stopped process.
      process_memory_ = Memory::CreateProcessMemoryCached(pid_);
    }
  }

  jit_debug_ptr_ = CreateJitDebug(arch_, process_memory_);
  jit_debug_ = jit_debug_ptr_.get();
  SetJitDebug(jit_debug_);
#if defined(DEXFILE_SUPPORT)
  dex_files_ptr_ = CreateDexFiles(arch_, process_memory_);
  dex_files_ = dex_files_ptr_.get();
  SetDexFiles(dex_files_);
#endif

  return true;
}

解析 /proc/{pid}/maps

解析 /proc/{pid}/maps 内容并保存。

previous map 和 prev map 区别如下:

  //  1000-2000  r--p 000000 00:00 0 libc.so
  //  2000-3000  ---p 000000 00:00 0 
  //  3000-4000  r-xp 003000 00:00 0 libc.so

第三个 mapinfo 的 prev map 是第二个,previous map 是第一个。

libunwindstack/Maps.cpp

bool Maps::Parse() {
  MapInfo* prev_map = nullptr;
  MapInfo* prev_real_map = nullptr;
  // GetMapsFile 根据是否为本进程返回 /proc/self/maps,或 /proc/{pid}/maps
  return android::procinfo::ReadMapFile(GetMapsFile(),
                      [&](const android::procinfo::MapInfo& mapinfo) {
    // Mark a device map in /dev/ and not in /dev/ashmem/ specially.
    auto flags = mapinfo.flags;
    if (strncmp(mapinfo.name.c_str(), "/dev/", 5) == 0 &&
        strncmp(mapinfo.name.c_str() + 5, "ashmem/", 7) != 0) {
      flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP;
    }
    // maps_ 是 std::vector<std::unique_ptr<MapInfo>> 类型
    maps_.emplace_back(new MapInfo(prev_map, prev_real_map, mapinfo.start, mapinfo.end,
                                   mapinfo.pgoff, flags, mapinfo.name));
    prev_map = maps_.back().get(); // 前一个 mapinfo 的引用
    //IsBlank 当 offset、flag 和 name 都没有值,返回 true
    if (!prev_map->IsBlank()) {  
      prev_real_map = prev_map; //
    }
  });
}

创建 jit_debug_ 和 dex_files_

jit_debug_ 和 dex_files_,主要是参数类型不同,一个对应Elf,一个对应DexFile

具体的使用,后面继续分析。

libunwindstack/JitDebug.cpp

std::unique_ptr<JitDebug> CreateJitDebug(ArchEnum arch, std::shared_ptr<Memory>& memory,
                                         std::vector<std::string> search_libs) {
  // search_libs 搜索指定的库。
  // __jit_debug_descriptor 全局变量名。
  return CreateGlobalDebugImpl<Elf>(arch, memory, search_libs, "__jit_debug_descriptor");
}

libunwindstack/DexFiles.cpp

std::unique_ptr<DexFiles> CreateDexFiles(ArchEnum arch, std::shared_ptr<Memory>& memory,
                                         std::vector<std::string> search_libs) {
  return CreateGlobalDebugImpl<DexFile>(arch, memory, search_libs, "__dex_debug_descriptor");
}

栈回溯

栈回溯的整体逻辑就在 Unwind 函数中。

  1. 利用信号处理函数中获取的 pc(crash 发生时的指令地址)和 sp(栈顶地址)。
  2. 从 maps 中找到 pc 所在的 mapinfo(包含映射文件的名字,内存起止地址,偏移量和权限)。
  3. 根据 mapinfo 生成 elf 对象(伴随加载和解析)。
  4. 如果 elf 解析失败,可能 pc 指向 Jit 生成的代码中,因此尝试使用 jit_debug_ 查找 elf。
  5. 如果存在 dex pc,说明可以解析出 java 层的函数,使用 dex_files_ 解析。
  6. 检查 pc 是否指向信号处理函数的返回地址,如果是进行修正。
  7. 否则调用 elf 的 step 函数进行栈回溯。
    step 调用中会交换 pc 为返回地址。 sp 为栈帧地址。
  8. 从 elf 符号表中解析函数名称和偏移量。

两个参数默认为 null。
libunwindstack/Unwinder.cpp

void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip,
                      const std::vector<std::string>* map_suffixes_to_ignore) {
  CHECK(arch_ != ARCH_UNKNOWN);
  ClearErrors();
  // frames_ 是 vector<unwindstack::FrameData> 类型,FrameData 表示栈帧数据。
  frames_.clear();
  elf_from_memory_not_file_ = false;
   
  // Clear any cached data from previous unwinds.
  process_memory_->Clear();
  // 根据 crash 发生时的指令地址,找到对应的文件映射。
  if (maps_->Find(regs_->pc()) == nullptr) {
    regs_->fallback_pc();  
  }
    
  bool return_address_attempt = false;
  bool adjust_pc = false;
  // 调用栈数量达到最大停止解析。
  for (; frames_.size() < max_frames_;) {
    uint64_t cur_pc = regs_->pc(); // 指令执行地址
    uint64_t cur_sp = regs_->sp(); // 函数栈顶
    // 根据 pc 值找到所在内存的文件映射。
    MapInfo* map_info = maps_->Find(regs_->pc());
    uint64_t pc_adjustment = 0;
    uint64_t step_pc; // 对 elf 的回溯使用,
    uint64_t rel_pc; // 相对 elf 入口的地址
    Elf* elf;
    if (map_info == nullptr) { // 如果没找到设置错误码
      step_pc = regs_->pc();
      rel_pc = step_pc;
      last_error_.code = ERROR_INVALID_MAP;
      elf = nullptr;
    } else {
      // 如果文件的名字在 map_suffixes_to_ignore 中跳过解析。
      if (ShouldStop(map_suffixes_to_ignore, map_info->name())) {
        break;
      }
      // 加载和解析 elf 文件
      elf = map_info->GetElf(process_memory_, arch_);
      // If this elf is memory backed, and there is a valid file, then set
      // an indicator that we couldn't open the file.
      const std::string& map_name = map_info->name();
      if (!elf_from_memory_not_file_ && map_info->memory_backed_elf() && !map_name.empty() &&
          map_name[0] != '[' && !android::base::StartsWith(map_name, "/memfd:")) {
        elf_from_memory_not_file_ = true; // elf 文件是从内存中加载的。
      }
      step_pc = regs_->pc();
      // 将 pc 转换为相对 elf 文件入口处的内存地址。
     // pc - map_info->start() + load_bias_ + map_info->elf_offset();
      rel_pc = elf->GetRelPc(step_pc, map_info); 
      // Everyone except elf data in gdb jit debug maps uses the relative pc.
      if (!(map_info->flags() & MAPS_FLAGS_JIT_SYMFILE_MAP)) {
        step_pc = rel_pc;
      }
      if (adjust_pc) { // 可以理解为从第二帧开始。
        // 根据处理器类型和 rel_pc 计算需要调整的值。
        // 对应 ARCH_ARM64 来说,rel_pc < 4 ? 0 : 4
        pc_adjustment = GetPcAdjustment(rel_pc, elf, arch_);
      } else {
        pc_adjustment = 0;
      }
      step_pc -= pc_adjustment; // 调整 pc 的值
      // elf 解析失败,可能在 jit 生成代码文件中。用 jit_debug_ 尝试加载。
      // If the pc is in an invalid elf file, try and get an Elf object
      // using the jit debug information.
      if (!elf->valid() && jit_debug_ != nullptr && (map_info->flags() & PROT_EXEC)) {
        uint64_t adjusted_jit_pc = regs_->pc() - pc_adjustment; // 这里没有用相对地址
        Elf* jit_elf = jit_debug_->Find(maps_, adjusted_jit_pc);
        if (jit_elf != nullptr) {
          // The jit debug information requires a non relative adjusted pc.
          step_pc = adjusted_jit_pc;
          elf = jit_elf;
        }
      }
    }

    FrameData* frame = nullptr;
    if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
        std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(),
                  basename(map_info->name().c_str())) == initial_map_names_to_skip->end()) {
      // 如果存在指向 java 层位置的地址,尝试进行解析。            
      if (regs_->dex_pc() != 0) {
        // Add a frame to represent the dex file.
        FillInDexFrame();
        // Clear the dex pc so that we don't repeat this frame later.
        regs_->set_dex_pc(0);

        // Make sure there is enough room for the real frame.
        if (frames_.size() == max_frames_) {
          last_error_.code = ERROR_MAX_FRAMES_EXCEEDED;
          break;
        }
      }
      // 构建 FrameData 数据结构。表示函数栈帧
      frame = FillInFrame(map_info, elf, rel_pc, pc_adjustment);

      // Once a frame is added, stop skipping frames.
      initial_map_names_to_skip = nullptr;
    }
    adjust_pc = true;

    bool stepped = false;
    bool in_device_map = false;
    bool finished = false;
    if (map_info != nullptr) {
      // MAPS_FLAGS_DEVICE_MAP 表示 /dev 的映射
      if (map_info->flags() & MAPS_FLAGS_DEVICE_MAP) {
        // Do not stop here, fall through in case we are
        // in the speculative unwind path and need to remove
        // some of the speculative frames.
        in_device_map = true;
      } else {
        // 获取栈顶地址对应的 mapinfo
        MapInfo* sp_info = maps_->Find(regs_->sp());
        // 如果是 /dev 映射跳过
        if (sp_info != nullptr && sp_info->flags() & MAPS_FLAGS_DEVICE_MAP) {
          // Do not stop here, fall through in case we are
          // in the speculative unwind path and need to remove
          // some of the speculative frames.
          in_device_map = true;
        } else {
          bool is_signal_frame = false;
          // 检查 pc 是否指向信号处理函数的返回地址,如果是进行修正。
          if (elf->StepIfSignalHandler(rel_pc, regs_, process_memory_.get())) {
            stepped = true;  // 解析成功设置为 true
            is_signal_frame = true; // 表示为信号处理函数栈帧。
            // 进行回溯。 
          } else if (elf->Step(step_pc, regs_, process_memory_.get(), &finished,
                               &is_signal_frame)) {
            stepped = true; // 解析成功设置为 true
          }
          if (is_signal_frame && frame != nullptr) {
            // Need to adjust the relative pc because the signal handler
            // pc should not be adjusted.
            frame->rel_pc = rel_pc;
            frame->pc += pc_adjustment;
            step_pc = rel_pc;
          }
          elf->GetLastError(&last_error_);
        }
      }
    }
    // 开始解析 step_pc 所在函数名
    if (frame != nullptr) {
       // resolve_names_ 用来控制是否解析函数名
      if (!resolve_names_ ||
          !elf->GetFunctionName(step_pc, &frame->function_name, &frame->function_offset)) {
        frame->function_name = "";
        frame->function_offset = 0;
      }
    }
    // 跳过解析。
    if (finished) {
      break;
    }
    
    if (!stepped) {  // 回溯失败
        // 首次为 false,为 true 时表示连续失败
      if (return_address_attempt) { 
        // Only remove the speculative frame if there are more than two frames
        // or the pc in the first frame is in a valid map.
        // This allows for a case where the code jumps into the middle of
        // nowhere, but there is no other unwind information after that.
        if (frames_.size() > 2 || (frames_.size() > 0 && maps_->Find(frames_[0].pc) != nullptr)) {
          // Remove the speculative frame.
          frames_.pop_back();
        }
        break;
      } else if (in_device_map) { // pc 或 sp 在 /dev 映射中,不再进行后续解析
        // Do not attempt any other unwinding, pc or sp is in a device
        // map.
        break;
      } else {
        // Steping didn't work, try this secondary method.
        if (!regs_->SetPcFromReturnAddress(process_memory_.get())) {
          break;
        }
        return_address_attempt = true;
      }
    } else { // 解析成功,
      return_address_attempt = false; // 重置为 false
      if (max_frames_ == frames_.size()) { // 如果达到指定数量,设置错误信息
        last_error_.code = ERROR_MAX_FRAMES_EXCEEDED;
      }
    }
    // 解析过程即是 pc 和上一帧 sp 交换的过程,因此如果没有改变,停止继续解析
    // If the pc and sp didn't change, then consider everything stopped.
    if (cur_pc == regs_->pc() && cur_sp == regs_->sp()) {
      last_error_.code = ERROR_REPEATED_FRAME;
      break;
    }
  }
}

Elf::Step 内部会调用该函数进行回溯。

  1. 使用 debug_frame 段进行回溯
  2. 如果 1 失败,使用 eh_frame 段进行回溯
  3. 尝试 2 失败,使用 gnu_debugdata 段进行回溯。

这三个流程基本一致,只是使用的段不同,这里以 eh_frame 进行分析。

libunwindstack/ElfInterface.cpp

bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished,
                        bool* is_signal_frame) {
  last_error_.code = ERROR_NONE;
  last_error_.address = 0;

  // Try the debug_frame first since it contains the most specific unwind
  // information.
  DwarfSection* debug_frame = debug_frame_.get();
  if (debug_frame != nullptr &&
      debug_frame->Step(pc, regs, process_memory, finished, is_signal_frame)) {
    return true;
  }

  // Try the eh_frame next.
  DwarfSection* eh_frame = eh_frame_.get();
  if (eh_frame != nullptr && eh_frame->Step(pc, regs, process_memory, finished, is_signal_frame)) {
    return true;
  }

  if (gnu_debugdata_interface_ != nullptr &&
      gnu_debugdata_interface_->Step(pc, regs, process_memory, finished, is_signal_frame)) {
    return true;
  }
...
  }
  return false;
}

在前面的流程中定位到了 crash 发生的 elf 文件。这里会进一步定位到函数。

eh_frame 中的每个 FDE 对应为一个函数。

  1. 解析 pc 所在的 FDE。
  2. 根据 FDE 和它对应的 CIE 解析出 pc 所在位置关联寄存器的规则。
  3. 调用 Eval 函数计算函数返回地址和 CFA ,用来更新 pc 和 sp,完成栈回溯。
    image.png

DwarfLocations:中记录了 pc_start 和 pc_end 这一地址范围内关联寄存器的规则(offset(N), register(R) 等)。
DwarfLocation:寄存器规则和参数。
DwarfLocationEnum:表示寄存器规则。
DwarfFde:对应 FDE 结构。
DwarfCie:对应 CIE 结构。

libunwindstack/DwarfSection.cpp

bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished,
                        bool* is_signal_frame) {
  // Lookup the pc in the cache.
  auto it = loc_regs_.upper_bound(pc); // 找到所在 pc 的 DwarfLocations
  if (it == loc_regs_.end() || pc < it->second.pc_start) {
    last_error_.code = DWARF_ERROR_NONE;
    // 解析出 pc 所在的 FDE。
    const DwarfFde* fde = GetFdeFromPc(pc); 
    if (fde == nullptr || fde->cie == nullptr) {
      last_error_.code = DWARF_ERROR_ILLEGAL_STATE;
      return false;
    }
    // 找到 pc 处的所有寄存器规则   
    // Now get the location information for this pc.
    DwarfLocations loc_regs;
    // 1. 优先解析 CIE 指令部分,获取寄存器的初始值。
    // 2. 解析 FDE 指令部分,获取 pc 处记录的寄存器规则。
    if (!GetCfaLocationInfo(pc, fde, &loc_regs, regs->Arch())) {
      return false;
    }
    loc_regs.cie = fde->cie;
   
    // Store it in the cache.
    it = loc_regs_.emplace(loc_regs.pc_end, std::move(loc_regs)).first;
  }
    
  *is_signal_frame = it->second.cie->is_signal_frame;

  // Now eval the actual registers.
  return Eval(it->second.cie, process_memory, it->second, regs, finished);
}

计算寄存器的值,并更新 pc 和 sp,完成栈回溯。

  1. 计算 CFA 的值。其他寄存器都基于它求值。
  2. 计算其他寄存器在上一帧中的值。
  3. 更新 pc 和 sp,完成栈回溯。用函数返回地址更新 pc,用 CFA 更新 sp。这里 CFA 可以看作是当前函数栈帧基地址。
    注意:dex_pc 的设置,通过它可以从 c 跨越到 java 层。
template <typename AddressType>
bool DwarfSectionImpl<AddressType>::Eval(const DwarfCie* cie, Memory* regular_memory,
                                         const DwarfLocations& loc_regs, Regs* regs,
                                         bool* finished) {
  // 转换为 RegsImpl 类型。 
  RegsImpl<AddressType>* cur_regs = reinterpret_cast<RegsImpl<AddressType>*>(regs);
  if (cie->return_address_register >= cur_regs->total_regs()) {
    last_error_.code = DWARF_ERROR_ILLEGAL_VALUE;
    return false;
  }
  // 获取代表 cfa 的寄存器规则 
  // Get the cfa value;
  auto cfa_entry = loc_regs.find(CFA_REG); // CFA_REG = -1
  ...
  const DwarfLocation* loc = &cfa_entry->second;
  // Only a few location types are valid for the cfa.
  switch (loc->type) { // 根据规则,计算 cfa 的值
    case DWARF_LOCATION_REGISTER:
     ...
  }
  // 计算其他寄存器的值。
  for (const auto& entry : loc_regs) {
    uint32_t reg = entry.first;
    // Already handled the CFA register.
    if (reg == CFA_REG) continue; // cfa 已经计算过,跳过

    AddressType* reg_ptr;
    if (reg >= cur_regs->total_regs()) {
    ...
    } else {
      reg_ptr = eval_info.regs_info.Save(reg);
      // 计算出其他寄存器在上一帧中的值
      if (!EvalRegister(&entry.second, reg, reg_ptr, &eval_info)) {
        return false;
      }
    }
  }
  
  // Find the return address location.
  if (eval_info.return_address_undefined) {
    cur_regs->set_pc(0);
  } else { // 将 pc 更新为函数的返回地址。意味着回到了上一级函数。
    cur_regs->set_pc((*cur_regs)[cie->return_address_register]);
  }

  // If the pc was set to zero, consider this the final frame. Exception: if
  // this is the sigreturn frame, then we want to try to recover the real PC
  // using the return address (from LR or the stack), so keep going.
  *finished = (cur_regs->pc() == 0 && !cie->is_signal_frame) ? true : false;
  // cfa 指向栈帧地址,即上一级函数的栈顶。
  cur_regs->set_sp(eval_info.cfa);

  return true;
}    

计算寄存器的值。
求值过程参照官方文档相关指令部分,这里需要注意对 dex pc 的设置。

template <typename AddressType>
bool DwarfSectionImpl<AddressType>::EvalRegister(const DwarfLocation* loc, uint32_t reg,
                                                 AddressType* reg_ptr, void* info) {
  EvalInfo<AddressType>* eval_info = reinterpret_cast<EvalInfo<AddressType>*>(info);
  Memory* regular_memory = eval_info->regular_memory;
  switch (loc->type) { // 寄存器规则
    ...
    case DWARF_LOCATION_EXPRESSION:
    case DWARF_LOCATION_VAL_EXPRESSION: {
      AddressType value;
      bool is_dex_pc = false;
      if (!EvalExpression(*loc, regular_memory, &value, &eval_info->regs_info, &is_dex_pc)) {
        return false;
      }
      if (loc->type == DWARF_LOCATION_EXPRESSION) {
        if (!regular_memory->ReadFully(value, reg_ptr, sizeof(AddressType))) {
          last_error_.code = DWARF_ERROR_MEMORY_INVALID;
          last_error_.address = value;
          return false;
        }
      } else {
        *reg_ptr = value;
        if (is_dex_pc) { // 存在 java 层的地址
          eval_info->regs_info.regs->set_dex_pc(value);
        }
      }
      break;
    }
    ...
  }
  return true;
}    

解析 dex pc

dex pc 地址是 java 层中函数地址,这也是从 native 跨越到 java 层的关键。

libunwindstack/DwarfOp.cpp

template <typename AddressType>
bool DwarfOp<AddressType>::Eval(uint64_t start, uint64_t end) {
  is_register_ = false;
  stack_.clear(); // 用来模拟栈
  memory_->set_cur_offset(start); // 移动到 start 处。
  dex_pc_set_ = false; 

  // Unroll the first Decode calls to be able to check for a special
  // sequence of ops and values that indicate this is the dex pc.
  // The pattern is:
  //   OP_const4u (0x0c)  'D' 'E' 'X' '1'
  //   OP_drop (0x13)
  if (memory_->cur_offset() < end) {
    if (!Decode()) { // 解析并执行操作码
      return false;
    }
  } else { // 大于 end 指令结束
    return true;
  }
  bool check_for_drop;
  
  // 表示这存在 dex pc 指针。   
  if (cur_op_ == 0x0c && operands_.back() == 0x31584544) {
    check_for_drop = true;
  } else {
    check_for_drop = false;
  }
  if (memory_->cur_offset() < end) {
    if (!Decode()) { 
      return false;
    }
  } else {
    return true;
  }
  
  if (check_for_drop && cur_op_ == 0x13) {
    dex_pc_set_ = true; // 存在指向 java 层的指针。
  }
  ...                                
  return true;
}    

回溯到 java 层

根据 dex pc 找到对应的 java 层函数。
libunwindstack/Unwinder.cpp

// Inject extra 'virtual' frame that represents the dex pc data.
// The dex pc is a magic register defined in the Mterp interpreter,
// and thus it will be restored/observed in the frame after it.
// Adding the dex frame first here will create something like:
//   #7 pc 0015fa20 core.vdex   java.util.Arrays.binarySearch+8
//   #8 pc 006b1ba1 libartd.so  ExecuteMterpImpl+14625
//   #9 pc 0039a1ef libartd.so  art::interpreter::Execute+719
void Unwinder::FillInDexFrame() {
  size_t frame_num = frames_.size();
  frames_.resize(frame_num + 1); // 添加一个栈帧。
  FrameData* frame = &frames_.at(frame_num);
  frame->num = frame_num;

  uint64_t dex_pc = regs_->dex_pc();
  frame->pc = dex_pc;
  frame->sp = regs_->sp();

  MapInfo* info = maps_->Find(dex_pc);
  ...
  if (!resolve_names_) { // 默认是 true, 即需要解析函数名
    return;
  }

#if defined(DEXFILE_SUPPORT)
  if (dex_files_ == nullptr) {
    return;
  }

  dex_files_->GetFunctionName(maps_, dex_pc, &frame->function_name, &frame->function_offset);
#endif
}

找到 pc 所在的 dex 文件并解析出对应的函数名称。
libunwindstack/GlobalDebugImpl.h

bool GetFunctionName(Maps* maps, uint64_t pc, SharedString* name, uint64_t* offset) {
  // NB: If symfiles overlap in PC ranges, this will check all of them.
  return ForEachSymfile(maps, pc, [pc, name, offset](Symfile* file) {
     return file->GetFunctionName(pc, name, offset);
  });
}    
  1. 找到 "__dex_debug_descriptor" 指向的内存地址。根据JITDescriptor解析并创建DexFile
  2. 找到 pc 所对应的DexFile

Symfile 作为模板参数可能是DexFileElf 类型。IsValidPc在两种类型下的处理不同。

dex 或 elf 在内存中的加载到方式不同。前者是整体加载,后者是仅加载 PT_LOAD 类型的段。所以判断 pc 是否在某个 dex 或 elf 方法不同。

dex : pc 在 dex 所在的内存地址 + dex 大小范围内。
elf : pc 段(PT_LOAD 类型)起始内存地址 + 段大小。

  // Invoke callback for all symfiles that contain the given PC.
  // Returns true if any callback returns true (which also aborts the iteration).
  template <typename Callback /* (Symfile*) -> bool */>
  bool ForEachSymfile(Maps* maps, uint64_t pc, Callback callback) {
    // Use a single lock, this object should be used so infrequently that
    // a fine grain lock is unnecessary.
    std::lock_guard<std::mutex> guard(lock_);
    if (descriptor_addr_ == 0) {
      // 遍历 mapinfo 找到 global_variable_name_ 变量对应内存地址。  
      // global_variable_name_ :"__jit_debug_descriptor" 或 "__dex_debug_descriptor"
      FindAndReadVariable(maps, global_variable_name_);
      if (descriptor_addr_ == 0) {
        return false;
      }
    }
    ...
    
    // Update all entries and retry.
    ReadAllEntries(maps);
    for (auto& it : entries_) { //  找到 pc 所在的 dex 
      Symfile* symfile = it.second.get();
      // Note that the entry could become invalid since the ReadAllEntries above,
      // but that is ok.  We don't want to fail or refresh the entries yet again.
      // This is as if we found the entry in time and it became invalid after return.
      // This is relevant when ART moves/packs JIT entries. That is, the entry is
      // technically deleted, but only because it was copied into merged uber-entry.
      // So the JIT method is still alive and the deleted data is still correct.  
      if (symfile->IsValidPc(pc) && callback(symfile)) {
        return true;
      }
    }

    return false;
  }    

根据JITDescriptor找到所有的 dex 文件地址,创建DexFile

  // Read all JIT entries while assuming there might be concurrent modifications.
  // If there is a race, the method will fail and the caller should retry the call.
  bool ReadAllEntries(Maps* maps, bool* race) {
    // New entries might be added while we iterate over the linked list.
    // In particular, an entry could be effectively moved from end to start due to
    // the ART repacking algorithm, which groups smaller entries into a big one.
    // Therefore keep reading the most recent entries until we reach a fixed point.
    std::map<UID, std::shared_ptr<Symfile>> entries;
    for (size_t i = 0; i < kMaxHeadRetries; i++) {
      size_t old_size = entries.size();
      if (!ReadNewEntries(maps, &entries, race)) {
        return false;
      }
      if (entries.size() == old_size) {
        entries_.swap(entries);
        return true;
      }
    }
    return false;  // Too many retries.
  }    

JITCodeEntry是一个链表,内部描述了引用文件的地址和大小。

__jit_debug_descriptor 和 __dex_debug_descriptor 都是JITDescriptor类型,前者的 JITCodeEntry引用的是 elf 文件,后者引用的是 dex 文件。

  struct JITDescriptor {
    uint32_t version;
    uint32_t action_flag;
    Uintptr_T relevant_entry;
    Uintptr_T first_entry;
    // Android-specific fields:
    uint8_t magic[8];
    uint32_t flags;
    uint32_t sizeof_descriptor;
    uint32_t sizeof_entry;
    uint32_t seqlock;
    Uint64_T timestamp;
  };                                           
  struct JITCodeEntry {
    Uintptr_T next;
    Uintptr_T prev;
    Uintptr_T symfile_addr;
    Uint64_T symfile_size;
    // Android-specific fields:
    Uint64_T timestamp;
    uint32_t seqlock;
  };                                           
  1. 找到JITDescriptor中 first_entry 指向得地址,解析出JITCodeEntry
  2. JITCodeEntry的 symfile_addr 指向的地址就是 dex 文件所在地址。
  3. 创建DexFile对象。
  4. 读取下一个JITCodeEntry,继续 2 和 3。
// Read new JIT entries (head of linked list) until we find one that we have seen before.
  // This method uses seqlocks extensively to ensure safety in case of concurrent modifications.
  bool ReadNewEntries(Maps* maps, std::map<UID, std::shared_ptr<Symfile>>* entries, bool* race) {
    // Read the address of the head entry in the linked list.
    UID uid;
    // 读取 first_entry 保存的地址
    if (!ReadNextField(descriptor_addr_ + offsetof(JITDescriptor, first_entry), &uid, race)) {
      return false;
    }

    // Follow the linked list.
    while (uid.address != 0) {
    ...
      // Read the entry.
      JITCodeEntry data{};
      if (!memory_->ReadFully(uid.address, &data, jit_entry_size_)) {
        return false;
      }
      data.symfile_addr = StripAddressTag(data.symfile_addr);
      ...
      // Copy and load the symfile.
      auto it = entries_.find(uid);
      if (it != entries_.end()) {
        // The symfile was already loaded - just copy the reference.
        entries->emplace(uid, it->second);
      } else if (data.symfile_addr != 0) { // 这里 symfile_addr 指向的是 dexfile 的地址。
        std::shared_ptr<Symfile> symfile;
       // 加载 dex 文件。
        bool ok = this->Load(maps, memory_, data.symfile_addr, data.symfile_size.value, symfile);
        ...
        if (ok) {
          entries->emplace(uid, symfile);
        }
      }
     
      // Go to next entry.
      UID next_uid;
      if (!ReadNextField(uid.address + offsetof(JITCodeEntry, next), &next_uid, race)) {
        return false;  // The next pointer was modified while we were reading it.
      }
      ...
    }

    return true;
  }                                           

根据地址加载 dex 文件。
Create 函数:

  1. 根据 addr 对应的 mapinfo 得到得文件名和偏移量创建DexFile
  2. 1 失败,从 addr 指向得内存中读取数据创建DexFile
    libunwindstack/DexFiles.cpp
template <>
bool GlobalDebugInterface<DexFile>::Load(Maps* maps, std::shared_ptr<Memory>& memory, uint64_t addr,
                                         uint64_t size, /*out*/ std::shared_ptr<DexFile>& dex) {
  dex = DexFile::Create(addr, size, memory.get(), maps->Find(addr));
  return dex.get() != nullptr;
}

查找经 Jit 编译后的代码

查找过程与上面相同。

void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip,
                      const std::vector<std::string>* map_suffixes_to_ignore) {    
...   
      if (!elf->valid() && jit_debug_ != nullptr && (map_info->flags() & PROT_EXEC)) {
        uint64_t adjusted_jit_pc = regs_->pc() - pc_adjustment;
        Elf* jit_elf = jit_debug_->Find(maps_, adjusted_jit_pc);
...
  1. 查找 __jit_debug_descriptor 变量指向的地址。
  2. 通过JITDescriptor找到经过 Jit 后所有的 elf 地址,并创建Elf对象。
  3. 返回 pc 所在的Elf对象。
    libunwindstack/GlobalDebugImpl.h
  Symfile* Find(Maps* maps, uint64_t pc) {
    // NB: If symfiles overlap in PC ranges (which can happen for both ELF and DEX),
    // this will check all of them and return one that also has a matching function.
    Symfile* result = nullptr;
    bool found = ForEachSymfile(maps, pc, [pc, &result](Symfile* file) {
      result = file;
      SharedString name;
      uint64_t offset;
      return file->GetFunctionName(pc, &name, &offset);
    });
    if (found) {
      return result;  // Found symfile with symbol that also matches the PC.
    }
    // There is no matching symbol, so return any symfile for which the PC is valid.
    // This is a useful fallback for tests, which often have symfiles with no functions.
    return result;
  }    

对上面生成DexFile 对应这里生成Elf
libunwindstack/JitDebug.cpp

template <>
bool GlobalDebugInterface<Elf>::Load(Maps*, std::shared_ptr<Memory>& memory, uint64_t addr,
                                     uint64_t size, /*out*/ std::shared_ptr<Elf>& elf) {
  std::unique_ptr<MemoryBuffer> copy(new MemoryBuffer());
  if (!copy->Resize(size) || !memory->ReadFully(addr, copy->GetPtr(0), size)) {
    return false;
  }
  elf.reset(new Elf(copy.release()));
  return elf->Init() && elf->valid();
}    

pc 指向信号处理函数返回地址

在回溯过程中,pc 有可能指向了信号处理函数的返回地址,但这个地址其实是__kernel_rt_sigreturn系统调用的地址,所以需要进行修改。

信号处理函数会将返回地址会指向 __kernel_rt_sigreturn系统调用地址。以便在回到用户态之前先恢复用户态栈的上下文信息。

// The relative pc expectd by this function is relative to the start of the elf.
bool Elf::StepIfSignalHandler(uint64_t rel_pc, Regs* regs, Memory* process_memory) {
  if (!valid_) {
    return false;
  }

  // Convert the rel_pc to an elf_offset.
  if (rel_pc < static_cast<uint64_t>(load_bias_)) {
    return false;
  }
  // load_bias_ 相当于 elf 入口地址
  // 这里转换为在 elf 中的偏移。
  return regs->StepIfSignalHandler(rel_pc - load_bias_, this, process_memory);
}    

判断特定指令

bool RegsArm64::StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) {
  uint64_t data;
  Memory* elf_memory = elf->memory();
  // Read from elf memory since it is usually more expensive to read from
  // process memory.
  if (!elf_memory->ReadFully(elf_offset, &data, sizeof(data))) {
    return false;
  }

  // Look for the kernel sigreturn function.
  // __kernel_rt_sigreturn:
  // 0xd2801168     mov x8, #0x8b
  // 0xd4000001     svc #0x0
  if (data != 0xd4000001d2801168ULL) {
    return false;
  }

  // SP + sizeof(siginfo_t) + uc_mcontext offset + X0 offset.
  if (!process_memory->ReadFully(regs_[ARM64_REG_SP] + 0x80 + 0xb0 + 0x08, regs_.data(),
                                 sizeof(uint64_t) * ARM64_REG_LAST)) {
    return false;
  }
  return true;
}    

加载和解析 elf

elf 文件的格式信息,可以参考其他文章中的介绍 elf

下面看 elf 的加载和解析。

libunwindstack/MapInfo.cpp

Elf* MapInfo::GetElf(const std::shared_ptr<Memory>& process_memory, ArchEnum expected_arch) {
  {
    ...
    // 没有缓存,准备加载 Elf。  
    // 创建 Memory,用来读取 elf 在内存中的数据。
    Memory* memory = CreateMemory(process_memory);
    ...
    
    elf().reset(new Elf(memory)); // 创建 elf 对象
    // If the init fails, keep the elf around as an invalid object so we
    // don't try to reinit the object.
    elf()->Init(); // 解析 elf 文件
  ...
  return elf().get();
}

加载 elf

加载主要有两种方式:

  1. 利用 mmap 将 elf 文件加载到内存。在栈回溯与 elf 不是同一个进程,或者文件名没有路径信息或权限等原因可能会失败。
  2. 从进程内存中读取。在 1 失败时使用。
    主要利用两个函数读取进程内存:process_vm_readv 和 ptrace。

elf 在内存中可能根据权限 R 和 RX,分为两部分映射,所以使用 MemoryRanges 来关联两块儿内存。

Memory* MapInfo::CreateMemory(const std::shared_ptr<Memory>& process_memory) {
  if (end() <= start()) { // 地址错误,直接返回 null。
    return nullptr;
  }

  set_elf_offset(0); // 重置
    
    // 如果是 /dev 内存,直接返回 null。
  // Fail on device maps.
  if (flags() & MAPS_FLAGS_DEVICE_MAP) {
    return nullptr;
  }
    
  // First try and use the file associated with the info.
  if (!name().empty()) { // 将 elf 文件加载到内存
    Memory* memory = GetFileMemory();
    if (memory != nullptr) {
      return memory;
    }
  }

  if (process_memory == nullptr) {
    return nullptr;
  }

  set_memory_backed_elf(true); // 表示 elf 从内存中读取

  // Need to verify that this elf is valid. It's possible that
  // only part of the elf file to be mapped into memory is in the executable
  // map. In this case, there will be another read-only map that includes the
  // first part of the elf file. This is done if the linker rosegment
  // option is used.
  std::unique_ptr<MemoryRange> memory(new MemoryRange(process_memory, start(), end() - start(), 0));
  if (Elf::IsValidElf(memory.get())) {
    set_elf_start_offset(offset());

    // Might need to peek at the next map to create a memory object that
    // includes that map too.
    if (offset() != 0 || name().empty() || next_real_map() == nullptr ||
        offset() >= next_real_map()->offset() || next_real_map()->name() != name()) {
      return memory.release();
    }

    // There is a possibility that the elf object has already been created
    // in the next map. Since this should be a very uncommon path, just
    // redo the work. If this happens, the elf for this map will eventually
    // be discarded.
    MemoryRanges* ranges = new MemoryRanges;
    ranges->Insert(new MemoryRange(process_memory, start(), end() - start(), 0));
    ranges->Insert(new MemoryRange(process_memory, next_real_map()->start(),
                                   next_real_map()->end() - next_real_map()->start(),
                                   next_real_map()->offset() - offset()));

    return ranges;
  }

  // Find the read-only map by looking at the previous map. The linker
  // doesn't guarantee that this invariant will always be true. However,
  // if that changes, there is likely something else that will change and
  // break something.
  if (offset() == 0 || name().empty() || prev_real_map() == nullptr ||
      prev_real_map()->name() != name() || prev_real_map()->offset() >= offset()) {
    set_memory_backed_elf(false);
    return nullptr;
  }
  ...

  MemoryRanges* ranges = new MemoryRanges;
  ranges->Insert(new MemoryRange(process_memory, prev_real_map()->start(),
                                 prev_real_map()->end() - prev_real_map()->start(), 0));
  ranges->Insert(new MemoryRange(process_memory, start(), end() - start(), elf_offset()));

  return ranges;
}

使用文件映射方式加载 elf 时,需要考虑 offset。

offset 非 0 有几种情况:

  • elf 是被内嵌在文件中的 如:2000-3000 r--p 000300 00:00 0 xxx/xxx/xxx.apk
    so 文件是被包含在 xxx.apk 文件中的。 内嵌情况下 offset 有两种表示:
    • elf 的起始位置在当前 mapinfo 代表的内存区域起始位置处的偏移。
    • elf 的可执行部分的起始位置在当前 mapinfo 代表的内存区域起始位置处的偏移。elf 文件真正的起始位置是在这段区域中之前的只读区域中。
  • 整个文件就是 elf 文件。
    如:35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so
    offset 表示:mapinfo 代表的内存是相对于 ld-2.15.so 的 offset 处开始。
Memory* MapInfo::GetFileMemory() {
  std::unique_ptr<MemoryFileAtOffset> memory(new MemoryFileAtOffset);
  if (offset() == 0) { // 如果偏移为 0,说明当前内存区域是从名为 name 的文件起始处开始映射的。
    if (memory->Init(name(), 0)) {
      return memory.release();
    }
    return nullptr;
  }
    
  // These are the possibilities when the offset is non-zero.
  // - There is an elf file embedded in a file, and the offset is the
  //   the start of the elf in the file.
  // - There is an elf file embedded in a file, and the offset is the
  //   the start of the executable part of the file. The actual start
  //   of the elf is in the read-only segment preceeding this map.
  // - The whole file is an elf file, and the offset needs to be saved.
  //
  // Map in just the part of the file for the map. If this is not
  // a valid elf, then reinit as if the whole file is an elf file.
  // If the offset is a valid elf, then determine the size of the map
  // and reinit to that size. This is needed because the dynamic linker
  // only maps in a portion of the original elf, and never the symbol
  // file data.
  uint64_t map_size = end() - start(); //计算需要映射的文件大小。
  // 将文件的 [offset, offset + map_size] 加载到内存。
  if (!memory->Init(name(), offset(), map_size)) {
    return nullptr;
  }
    
  // Check if the start of this map is an embedded elf.
  uint64_t max_size = 0;
  // 如果校验成功,说明这个文件是被包含在这块内存中的。、
  // 即 offset 表示的是这个文件的起始位置,相对于这块儿内存起始位置的偏移。
  if (Elf::GetInfo(memory.get(), &max_size)) {
    // offset 表示的是 elf 文件在文件中的偏移
    // 记录内存中 elf 文件的起始位置在映射区域中的偏移。
    set_elf_start_offset(offset()); 
    if (max_size > map_size) { // 如果文件的大小要比 mapinfo 中的大,重新加载
      if (memory->Init(name(), offset(), max_size)) {
        return memory.release();
      }
      // Try to reinit using the default map_size.
      if (memory->Init(name(), offset(), map_size)) { 
        return memory.release();
      }
      set_elf_start_offset(0);
      return nullptr;
    }
    return memory.release();
  }

  // 这里去掉偏移,尝试加载完整文件,并校验是否为 elf 格式。
  // 如果校验成功说明,当前文件就是 elf 文件。
  // No elf at offset, try to init as if the whole file is an elf.
  if (memory->Init(name(), 0) && Elf::IsValidElf(memory.get())) {
    set_elf_offset(offset()); // 记录当前内存区域的起始位置相对于 elf 文件起始处的偏移。
     // prev_real_map:  2000-3000  r--p 000000 00:00 0 libc.so   
     //  当前 mapinfo:  3000-4000  r-xp 003000 00:00 0 libc.so 
     // 存在当前文件的只读区域,则不设置 set_elf_start_offset
    // Need to check how to set the elf start offset. If this map is not
    // the r-x map of a r-- map, then use the real offset value. Otherwise,
    // use 0.
    if (prev_real_map() == nullptr || prev_real_map()->offset() != 0 ||
        prev_real_map()->flags() != PROT_READ || prev_real_map()->name() != name()) {
      set_elf_start_offset(offset());
    }
    return memory.release();
  }
  // 尝试从之前的只读区域中加载 elf
  // See if the map previous to this one contains a read-only map
  // that represents the real start of the elf data.
  if (InitFileMemoryFromPreviousReadOnlyMap(memory.get())) {
    return memory.release();
  }

  // Failed to find elf at start of file or at read-only map, return
  // file object from the current map.
  if (memory->Init(name(), offset(), map_size)) {
    return memory.release();
  }
  return nullptr;
}

解析 elf

elf 格式定义在 elf.h 头文件中。

具体的解析是由 ElfInterfaceXX 进行的。在 64 位处理器下使用ElfInterface64,而在 32 位处理器下,只有 arm 架构使用ElfInterfaceArm,其他(EM_386、EM_MIPS)使用ElfInterface32

libunwindstack/Elf.cpp

bool Elf::Init() {
  load_bias_ = 0;
  if (!memory_) {
    return false;
  }
  // 这里以 arm64 位例:会创建 ElfInterface64 对象。
  // ElfInterface64 实际是 ElfInterfaceImpl<ElfTypes64>;
  interface_.reset(CreateInterfaceFromMemory(memory_.get()));
  if (!interface_) {
    return false;
  }
  // 解析 elf 的头和段。
  // 内部仅调用的 ReadAllHeaders 方法。
  valid_ = interface_->Init(&load_bias_);
  if (valid_) {
    interface_->InitHeaders(); // 解析 eh_frame_hdr
    InitGnuDebugdata(); // 但独立初始化 gnu_debugdata 
  } else {
    interface_.reset(nullptr);
  }
  return valid_;
}
  1. 解析段。
  • 根据 PT_LOAD 类型中第一个具有执行权限的段计算 load_bias(elf 起始位置的内存地址)。
  • 保存 eh_frame_hdr 相关信息。
  • 保存动态链接段信息相关信息。
  1. 解析节。
  • 解析字符串表
  • 符号表
  • SHT_PROGBITS 或 SHT_NOBITS 类型的节 如:.data、.text、.debug_frame、.gnu_debugdata、.eh_frame、.eh_frame_hdr

注:elf 中的节(section)和段(segment)其实是一样的,只是从链接的角度看 elf 文件是按节存储的,从装载的角度看,elf 是按段存储的。在目标文件链接成可执行文件时,会将相同权限的节进行合并看作是一个段。

template <typename ElfTypes>
bool ElfInterfaceImpl<ElfTypes>::ReadAllHeaders(int64_t* load_bias) {
  EhdrType ehdr; // 64 位 elf 文件头部
  // 解析头部信息
  if (!memory_->ReadFully(0, &ehdr, sizeof(ehdr))) {
    last_error_.code = ERROR_MEMORY_INVALID;
    last_error_.address = 0;
    return false;
  }
  // 
  // If we have enough information that this is an elf file, then allow
  // malformed program and section headers.
  ReadProgramHeaders(ehdr, load_bias); // 解析程序头表
  ReadSectionHeaders(ehdr);  // 解析根据节头表
  return true;
}

解析程序段

  • 根据 PT_LOAD 类型中第一个具有执行权限的段计算 elf 入口地址。
  • 保存 eh_frame_hdr 相关信息。后续用来解析函数调用栈。
  • 保存动态链接相关信息。

libunwindstack/ElfInterface.cpp

template <typename ElfTypes>
void ElfInterfaceImpl<ElfTypes>::ReadProgramHeaders(const EhdrType& ehdr, int64_t* load_bias) {
  uint64_t offset = ehdr.e_phoff;
  bool first_exec_load_header = true;
  for (size_t i = 0; i < ehdr.e_phnum; i++, offset += ehdr.e_phentsize) {
    PhdrType phdr;
    if (!memory_->ReadFully(offset, &phdr, sizeof(phdr))) {
      return;
    }

    switch (phdr.p_type) {
    case PT_LOAD: // 会被加载到内存中的段
    {
      if ((phdr.p_flags & PF_X) == 0) { // 跳过非可执行的段
        continue;
      }

      pt_loads_[phdr.p_offset] = LoadInfo{phdr.p_offset, phdr.p_vaddr,
                                          static_cast<size_t>(phdr.p_memsz)};
      // Only set the load bias from the first executable load header.
      if (first_exec_load_header) { 
        // 根据第一个可执行段指定的虚拟地址和在 elf 文中的偏移计算差值
        // 作为 elf 入口地址
        *load_bias = static_cast<uint64_t>(phdr.p_vaddr) - phdr.p_offset;
      }
      first_exec_load_header = false;
      break;
    }

    case PT_GNU_EH_FRAME: // GCC 的异常信息段
      // This is really the pointer to the .eh_frame_hdr section.
      eh_frame_hdr_offset_ = phdr.p_offset;
      eh_frame_hdr_section_bias_ = static_cast<uint64_t>(phdr.p_vaddr) - phdr.p_offset;
      eh_frame_hdr_size_ = phdr.p_memsz;
      break;

    case PT_DYNAMIC: // 动态链接信息
      dynamic_offset_ = phdr.p_offset;
      dynamic_vaddr_start_ = phdr.p_vaddr;
      if (__builtin_add_overflow(dynamic_vaddr_start_, phdr.p_memsz, &dynamic_vaddr_end_)) {
        dynamic_offset_ = 0;
        dynamic_vaddr_start_ = 0;
        dynamic_vaddr_end_ = 0;
      }
      break;

    default:
      HandleUnknownType(phdr.p_type, phdr.p_offset, phdr.p_filesz);
      break;
    }
  }
}

解析相应的节

根据节头表,解析相应的节。

libunwindstack/ElfInterface.cpp

template <typename ElfTypes>
void ElfInterfaceImpl<ElfTypes>::ReadSectionHeaders(const EhdrType& ehdr) {
  uint64_t offset = ehdr.e_shoff; // 在 elf 中的偏移
  uint64_t sec_offset = 0;
  uint64_t sec_size = 0;
  // Get the location of the section header names.
  // If something is malformed in the header table data, we aren't going
  // to terminate, we'll simply ignore this part.
  ShdrType shdr; // 节的数据结构
  // e_shstrndx 字符串表索引
  // e_shnum 节的数量
  if (ehdr.e_shstrndx < ehdr.e_shnum) { // 说明存在字符串表
    // 获取该索引在 elf 中的偏移
    // e_shentsize 表示节头表中每一项的大小。
    // sh_offset 就是字符串表在 elf 中的偏移。
    uint64_t sh_offset = offset + ehdr.e_shstrndx * ehdr.e_shentsize;
    // 获取字符串表信息
    if (memory_->ReadFully(sh_offset, &shdr, sizeof(shdr))) {
      sec_offset = shdr.sh_offset; // 字符串表在 elf 中的偏移
      sec_size = shdr.sh_size; // 字符串表的大小
    }
  }
  // 根据节头表解析每一节。
  // Skip the first header, it's always going to be NULL.
  offset += ehdr.e_shentsize;
  for (size_t i = 1; i < ehdr.e_shnum; i++, offset += ehdr.e_shentsize) {
    if (!memory_->ReadFully(offset, &shdr, sizeof(shdr))) {
      return;
    }
    // 如果是符号表或动态库的符号表
    if (shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) {
      // Need to go get the information about the section that contains
      // the string terminated names.
      ShdrType str_shdr;
      if (shdr.sh_link >= ehdr.e_shnum) {
        continue;
      }
      // 符号表或动态库的符号表中的 sh_link 指向字符串表在节头表中的索引
      // 计算出字符串表在 elf 中的偏移位置
      uint64_t str_offset = ehdr.e_shoff + shdr.sh_link * ehdr.e_shentsize;
      // 解析字符串表
      if (!memory_->ReadFully(str_offset, &str_shdr, sizeof(str_shdr))) {
        continue;
      }
      if (str_shdr.sh_type != SHT_STRTAB) { // 如果不是字符串表,跳过
        continue;
      }
      symbols_.push_back(new Symbols(shdr.sh_offset, shdr.sh_size, shdr.sh_entsize,
                                     str_shdr.sh_offset, str_shdr.sh_size));
     // 如果是程序会需要的数据或不含内容的节                                
    } else if ((shdr.sh_type == SHT_PROGBITS || shdr.sh_type == SHT_NOBITS) && sec_size != 0) {
      // Look for the .debug_frame and .gnu_debugdata.
      if (shdr.sh_name < sec_size) { // 索引不能超过表大小
        std::string name;
        // sh_name 的值是字符串表中的索引
        // 解析节的名字
        if (memory_->ReadString(sec_offset + shdr.sh_name, &name, sec_size - shdr.sh_name)) {
          if (name == ".debug_frame") {
            debug_frame_offset_ = shdr.sh_offset;
            debug_frame_size_ = shdr.sh_size;
            debug_frame_section_bias_ = static_cast<uint64_t>(shdr.sh_addr) - shdr.sh_offset;
          } else if (name == ".gnu_debugdata") {
            gnu_debugdata_offset_ = shdr.sh_offset;
            gnu_debugdata_size_ = shdr.sh_size;
          } else if (name == ".eh_frame") {
            eh_frame_offset_ = shdr.sh_offset;
            eh_frame_section_bias_ = static_cast<uint64_t>(shdr.sh_addr) - shdr.sh_offset;
            eh_frame_size_ = shdr.sh_size;
          } else if (eh_frame_hdr_offset_ == 0 && name == ".eh_frame_hdr") {
            eh_frame_hdr_offset_ = shdr.sh_offset;
            eh_frame_hdr_section_bias_ = static_cast<uint64_t>(shdr.sh_addr) - shdr.sh_offset;
            eh_frame_hdr_size_ = shdr.sh_size;
          } else if (name == ".data") {
            data_offset_ = shdr.sh_offset;
            data_vaddr_start_ = shdr.sh_addr;
            if (__builtin_add_overflow(data_vaddr_start_, shdr.sh_size, &data_vaddr_end_)) {
              data_offset_ = 0;
              data_vaddr_start_ = 0;
              data_vaddr_end_ = 0;
            }
          } else if (name == ".text") {
            text_addr_ = shdr.sh_addr;
            text_size_ = shdr.sh_size;
          }
        }
      }
    } else if (shdr.sh_type == SHT_STRTAB) { // 字符串表
      // In order to read soname, keep track of address to offset mapping.
      strtabs_.push_back(std::make_pair<uint64_t, uint64_t>(static_cast<uint64_t>(shdr.sh_addr),
                                                            static_cast<uint64_t>(shdr.sh_offset)));
    } else if (shdr.sh_type == SHT_NOTE) {
      if (shdr.sh_name < sec_size) {
        std::string name;
        if (memory_->ReadString(sec_offset + shdr.sh_name, &name, sec_size - shdr.sh_name) &&
            name == ".note.gnu.build-id") {
          gnu_build_id_offset_ = shdr.sh_offset;
          gnu_build_id_size_ = shdr.sh_size;
        }
      }
    }
  }
}

解析 eh_frame_hdr 段

这里开始进行 eh_frame_hdr 的解析。具体结构和字段意义参照前面介绍。

  1. 解析 eh_frame_hdr 段中的信息只要是二分搜索表。后面会用来快速查找 fde 结构。
  2. eh_frame_hdr 不存在或解析失败,则尝试记录 eh_frame 在 elf 中的位置(偏移量和长度)。
  3. 继续尝试记录 debug_frame 在 elf 中的位置(偏移量和长度)。
template <typename ElfTypes>
void ElfInterfaceImpl<ElfTypes>::InitHeaders() {
  if (eh_frame_hdr_offset_ != 0) {
    DwarfEhFrameWithHdr<AddressType>* eh_frame_hdr = new DwarfEhFrameWithHdr<AddressType>(memory_);
    eh_frame_.reset(eh_frame_hdr);
    // 记录 eh_frame_hdr 段在 elf 中的位置(偏移量和长度)
    if (!eh_frame_hdr->EhFrameInit(eh_frame_offset_, eh_frame_size_, eh_frame_section_bias_) ||
        // 解析 eh_frame_hdr 节的头部信息。这里主要是获取到二分搜索表。  
        !eh_frame_->Init(eh_frame_hdr_offset_, eh_frame_hdr_size_, eh_frame_hdr_section_bias_)) {
      eh_frame_.reset(nullptr);
    }
  }
   // 不存在 eh_frame_hdr 段或解析失败。
   // 但存在 eh_frame 段仅记录数据在 elf 中的位置
  if (eh_frame_.get() == nullptr && eh_frame_offset_ != 0) {
    // If there is an eh_frame section without an eh_frame_hdr section,
    // or using the frame hdr object failed to init.
    eh_frame_.reset(new DwarfEhFrame<AddressType>(memory_));
    if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_, eh_frame_section_bias_)) {
      eh_frame_.reset(nullptr);
    }
  }
  // 不存在 eh_frame_hdr 和 eh_frame 重新相关记录信息。
  if (eh_frame_.get() == nullptr) {
    eh_frame_hdr_offset_ = 0;
    eh_frame_hdr_section_bias_ = 0;
    eh_frame_hdr_size_ = static_cast<uint64_t>(-1);
    eh_frame_offset_ = 0;
    eh_frame_section_bias_ = 0;
    eh_frame_size_ = static_cast<uint64_t>(-1);
  }

  if (debug_frame_offset_ != 0) {
    debug_frame_.reset(new DwarfDebugFrame<AddressType>(memory_));
    if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_, debug_frame_section_bias_)) {
      debug_frame_.reset(nullptr);
      debug_frame_offset_ = 0;
      debug_frame_size_ = static_cast<uint64_t>(-1);
    }
  }
}