本文以 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 。
注意:下面介绍的格式和编码类型仅供参考,具体需要取查找官方文档。
格式:
Encoding | Field | desc |
---|---|---|
unsigned byte | version | eh_frame_hdr 格式的版本。 |
unsigned byte | eh_frame_ptr_enc | eh_frame_ptr 字段的编码格式。 |
unsigned byte | fde_count_enc | fde_count 字段的编码格式。如果值为 DW_EH_PE_omit,二分搜索表不展示。 |
unsigned byte | table_enc | 二分搜索项的编码格式。如果值为 DW_EH_PE_omit,二分搜索表不展示。 |
encoded | eh_frame_ptr | 指向eh_frame 节起始位置的编码值。 |
encoded | fde_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 格式:
Encoding | Field | desc |
---|---|---|
Length | Required | 4 字节无符号数, CIE 的长度,不包含字段自身。如果它的值是 0xffffffff 表示长度在 Extended Length 字段。 0xffffffff 转换成整数为 -1。 |
Extended Length | Optional | 8 字节无符号数,CIE 的长度,不包含 Length 和自身。它只有 Length 字段的值为 0xffffffff 时才展示。 |
CIE ID | Required | 4 字节无符号数,用来区分 CIE 或 FDE。值为 0 说明是 CIE。 |
Version | Required | 1 字节,CFI 格式的版本 |
Augmentation String | Required | nul 结尾的字符串。提供额外描述信息。内容是大小写敏感。 |
Code Alignment Factor | Required | 一个无符号 LEB128 编码值,与高级定位指令的 delta 参数相乘 |
Data Alignment Factor | Required | 一个有符号 LEB128 编码值,与偏移指令的寄存器偏移参数相乘 |
Return Address Register | Required | 一个无符号 LEB128 编码值,表示函数返回地址的寄存器号 |
Augmentation Data Length | Optional | 一个无符号 LEB128 编码值,表示 Augmentation Data 的长度。 该字段仅在 Augmentation String 字段值中包含 "z" 时显示。 |
Augmentation Data | Optional | 数据内容由 Augmentation String 内容决定。 该字段仅在 Augmentation String 字段值中包含 "z" 时显示。 |
Initial Instructions | Required | 用来生成寄存器的初始值 |
Padding | 填充字节,用来地址对齐 |
FDE 格式:
Encoding | Field | desc |
---|---|---|
Length | Required | 4 字节无符号数, CIE 的长度,不包含字段自身。如果它的值是 0xffffffff 表示长度在 Extended Length 字段。 |
Extended Length | Optional | 8 字节无符号数,CIE 的长度,不包含 Length 和自身。它只有 Length 字段的值为 0xffffffff 时才展示。 |
CIE Pointer | Required | 4 字节无符号数,从当前偏移值(该字段)减去该值,得到与它关联的 CIE 起始位置的偏移值 |
PC Begin | Required | 是一个编码值,表示 FDE 指令的初始地址,编码格式在 Augmentation Data 字段指定 |
PC Range | Required | 指令数量 |
Augmentation Data Length | Optional | 一个无符号 LEB128 编码值,表示 Augmentation Data 的长度。 该字段仅在与它关联的 cie 中的 Augmentation String 字段值中包含 "z" 时显示。 |
Augmentation Data | Optional | 它的内容是由与它关联的 cie 中的 Augmentation String 字段值决定。该字段仅在与它关联的 cie 中的 Augmentation String 字段值中包含 "z" 时显示。 |
Call Frame Instructions | Required | 调用帧指令集 |
Padding | 填充字节,用来进行地址对齐 |
DWARF官方文档中的例子:
这个例子解析的是 debug_frame 段。
函数机代码:
- R8 is the return address
- s = same_value rule
- u = undefined rule
- rN = register(N) rule
- cN = offset(N) rule
- a = architectural rule
<fs>: 表示栈帧大小。
所有寄存器变化的情况如下表:
表格中的列:
- 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 数据格式:
- <fs> = frame size
- <caf> = code alignment factor
- <daf> = data alignment factor
图中 DW_CFA_XXXX 都是 DWARF 规范中定义的指令,用来设置 CFA 和寄存器规则。
fde 数据格式:
可以看出通过 eh_frame 和 eh_frame_hdr 段,可以还原出寄存器在上一帧中的值。
另外注意这里不会每一行代码对应一条记录,而是只记录与上一次改变有差异的部分。
从 native 回溯到 java 层
从 native 函数回溯到 java 层函数:
- 获取 dex pc 即 java 层调用函数的内存地址。
dex pc 内存地址会作为寄存器 val_expression(E) 表达式的结果生成。
val_expression 中指令开头会有如下两个指令,用来识别是否有 dex pc
// OP_const4u (0x0c) 'D' 'E' 'X' '1'
// OP_drop (0x13)
-
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) {};
源码分析
代码目录:/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) {}
- 解析
/proc/{pid}/maps
将结果保存在 Maps 的 maps_ (std::vector<std::unique_ptr>)中。 - 初始化 process_memory_。用来从指定的内存地址读取内存中的数据。
- 初始化 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 函数中。
- 利用信号处理函数中获取的 pc(crash 发生时的指令地址)和 sp(栈顶地址)。
- 从 maps 中找到 pc 所在的 mapinfo(包含映射文件的名字,内存起止地址,偏移量和权限)。
- 根据 mapinfo 生成 elf 对象(伴随加载和解析)。
- 如果 elf 解析失败,可能 pc 指向 Jit 生成的代码中,因此尝试使用 jit_debug_ 查找 elf。
- 如果存在 dex pc,说明可以解析出 java 层的函数,使用 dex_files_ 解析。
- 检查 pc 是否指向信号处理函数的返回地址,如果是进行修正。
- 否则调用 elf 的 step 函数进行栈回溯。
step 调用中会交换 pc 为返回地址。 sp 为栈帧地址。 - 从 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 内部会调用该函数进行回溯。
- 使用 debug_frame 段进行回溯
- 如果 1 失败,使用 eh_frame 段进行回溯
- 尝试 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 对应为一个函数。
- 解析 pc 所在的 FDE。
- 根据 FDE 和它对应的 CIE 解析出 pc 所在位置关联寄存器的规则。
- 调用 Eval 函数计算函数返回地址和 CFA ,用来更新 pc 和 sp,完成栈回溯。
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,完成栈回溯。
- 计算 CFA 的值。其他寄存器都基于它求值。
- 计算其他寄存器在上一帧中的值。
- 更新 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);
});
}
- 找到 "__dex_debug_descriptor" 指向的内存地址。根据
JITDescriptor
解析并创建DexFile
。 - 找到 pc 所对应的
DexFile
。
Symfile 作为模板参数可能是DexFile
或Elf
类型。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;
};
- 找到
JITDescriptor
中 first_entry 指向得地址,解析出JITCodeEntry
。 JITCodeEntry
的 symfile_addr 指向的地址就是 dex 文件所在地址。- 创建
DexFile
对象。 - 读取下一个
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 函数:
- 根据 addr 对应的 mapinfo 得到得文件名和偏移量创建
DexFile
。 - 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);
...
- 查找 __jit_debug_descriptor 变量指向的地址。
- 通过
JITDescriptor
找到经过 Jit 后所有的 elf 地址,并创建Elf
对象。 - 返回 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
加载主要有两种方式:
- 利用 mmap 将 elf 文件加载到内存。在栈回溯与 elf 不是同一个进程,或者文件名没有路径信息或权限等原因可能会失败。
- 从进程内存中读取。在 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_;
}
- 解析段。
- 根据 PT_LOAD 类型中第一个具有执行权限的段计算 load_bias(elf 起始位置的内存地址)。
- 保存 eh_frame_hdr 相关信息。
- 保存动态链接段信息相关信息。
- 解析节。
- 解析字符串表
- 符号表
- 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 的解析。具体结构和字段意义参照前面介绍。
- 解析 eh_frame_hdr 段中的信息只要是二分搜索表。后面会用来快速查找 fde 结构。
- eh_frame_hdr 不存在或解析失败,则尝试记录 eh_frame 在 elf 中的位置(偏移量和长度)。
- 继续尝试记录 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);
}
}
}