OpenHarmony Faultloggerd 模块 libunwinder 回栈库解析

unwinder 库概览

  1. unwinder 库代码结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
unwinder
├── BUILD.gn # 构建脚本
├── arch_util.cpp
├── arm_exidx.cpp
├── arm_exidx_instructions.txt
├── dfx_accessors.cpp
├── dfx_ark.cpp
├── dfx_config.cpp
├── dfx_elf.cpp
├── dfx_elf_parser.cpp
├── dfx_frame_formatter.cpp
├── dfx_instr_statistic.cpp
├── dfx_instructions.cpp
├── dfx_map.cpp
├── dfx_maps.cpp
├── dfx_memory.cpp
├── dfx_mmap.cpp
├── dfx_ptrace.cpp
├── dfx_regs.cpp
├── dfx_regs_arm.cpp
├── dfx_regs_arm64.cpp
├── dfx_regs_x86_64.cpp
├── dfx_signal.cpp
├── dfx_symbols.cpp
├── dfx_xz_utils.cpp
├── dwarf_cfa.txt
├── dwarf_cfa_instructions.cpp
├── dwarf_op.cpp
├── dwarf_section.cpp
├── include
│   ├── arch_util.h
│   ├── arm_exidx.h
│   ├── dfx_accessors.h
│   ├── dfx_ark.h
│   ├── dfx_config.h
│   ├── dfx_elf.h
│   ├── dfx_elf_define.h
│   ├── dfx_elf_parser.h
│   ├── dfx_frame_formatter.h
│   ├── dfx_instr_statistic.h
│   ├── dfx_instructions.h
│   ├── dfx_map.h
│   ├── dfx_maps.h
│   ├── dfx_memory.h
│   ├── dfx_mmap.h
│   ├── dfx_ptrace.h
│   ├── dfx_regs.h
│   ├── dfx_regs_get.h
│   ├── dfx_signal.h
│   ├── dfx_symbol.h
│   ├── dfx_symbols.h
│   ├── dfx_xz_utils.h
│   ├── dwarf_cfa_instructions.h
│   ├── dwarf_define.h
│   ├── dwarf_op.h
│   ├── dwarf_section.h
│   ├── unwind_context.h
│   ├── unwind_loc.h
│   ├── unwinder.h
│   └── unwinder_config.h
└── unwinder.cpp
  1. 功能
  • 内存与寄存器访问
  • elf解析
  • arm_exidx 节回栈表解析
  • eh_frame_hdr / eh_frame 节Dwarf回栈表解析
  • minidebug_info 节解析
  • 支持Hap包 CompressNativeLibs 方案的elf解析
  • QUT回栈方案实现
  • FP回栈方案实现
  • unwind 接口封装

内存与寄存器读写

DfxAccessors 数据访问抽象

DfxAccessors 类是unwinder库中抽象出的数据访问类。

根据不同类型的回栈,实现有 DfxAccessorsLocalDfxAccessorsRemoteDfxAccessorsCustomize 子类,分别对应本地回栈、远程回栈和自定义回栈(自定义内存寄存器访问方式,例如离线回栈场景)。

对于数据访问的类型,DfxAccessors 类抽象出了三个方法:

1
2
3
4
5
6
7
8
9
10
11
class DfxAccessors {
public:
DfxAccessors(int bigEndian = UNWIND_BYTE_ORDER) : bigEndian_(bigEndian) {} // 大小端访问差异
virtual ~DfxAccessors() = default;

virtual int AccessMem(uintptr_t addr, uintptr_t *val, void *arg) = 0; // 读内存
virtual int AccessReg(int regIdx, uintptr_t *val, void *arg) = 0; // 读寄存器
virtual int FindUnwindTable(uintptr_t pc, UnwindTableInfo& uti, void *arg) = 0; // 根据pc查找回栈表地址

int bigEndian_ = UNWIND_BYTE_ORDER;
};
  1. DfxAccessorsLocal 类:本地回栈数据访问实现
  • AccessMem: 先通过判断地址是否在栈空间内来校验地址合法性,直接采用地址解引用的方式来读取内存。
  • AccessReg: 直接基于context通过寄存器下标来读取寄存器值,其中context是内部定义的 UnwindContext 结构体。
1
2
3
4
5
6
7
8
9
struct UnwindContext {
bool stackCheck = false;
uintptr_t stackBottom = 0;
uintptr_t stackTop = 0;
int pid;
std::shared_ptr<DfxRegs> regs = nullptr;
std::shared_ptr<DfxMap> map = nullptr;
struct UnwindTableInfo di;
};
  • FindUnwindTable: 通过 dl_iterate_phdr 接口来遍历当前进程的 program header 的方式来找到 unwindtable 的地址,具体实现由 DfxELF::FindUnwindTableLocal 静态函数实现。

arm32 架构的回栈表有 .arm_exidx 表承载,arm64 架构的回栈表由 .eh_frame_hdr 表和 .eh_frame 表承载。

  1. DfxAccessorsRemote类:远程回栈数据访问实现
  • AccessMem:通过 ptrace 系统调用的 PTRACE_PEEKDATA 来读取目标进程的内存(一次只能读4字节),需要额外判断在arm64场景下需要读两次,同时根据大小端来进行拼接。
  • AccessReg: 同local方式,基于内部 UnwindContext 结构体对象进行访问。
  • FindUnwindTable: 基于内部 UnwindContext 结构体对象解析 map 对应的 elf 对象来找到 unwindtable 的地址。
1
2
3
4
5
6
auto elf = ctx->map->GetElf();
if (elf == nullptr) {
LOGE("FindUnwindTable elf is null");
return UNW_ERROR_INVALID_ELF;
}
int ret = elf->FindUnwindTableInfo(pc, ctx->map, uti);
  1. DfxAccessorsCustomize 类:自定义回栈数据访问实现由实例化时传入 UnwindAccessors 数据访问抽象结构体对象来承载。

如下代码所示,即所有的访问由用户自定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct UnwindAccessors {
int (*FindUnwindTable)(uintptr_t, UnwindTableInfo &, void *);
int (*AccessMem)(uintptr_t, uintptr_t *, void *);
int (*AccessReg)(int, uintptr_t *, void *);
};

class DfxAccessorsCustomize : public DfxAccessors {
public:
DfxAccessorsCustomize(std::shared_ptr<UnwindAccessors> accessors) : accessors_(accessors) {}
virtual ~DfxAccessorsCustomize() = default;

int AccessMem(uintptr_t addr, uintptr_t *val, void *arg) override;
int AccessReg(int regIdx, uintptr_t *val, void *arg) override;
int FindUnwindTable(uintptr_t pc, UnwindTableInfo& uti, void *arg) override;
private:
std::shared_ptr<UnwindAccessors> accessors_;
};

DfxMemory 内存访问实现

DfxMemory 类是 unwinder 库中定义的内存访问类,需要传入 DfxAccessors 类对象进行实例化,提供了对 DfxAccessors 管理的内存读写源数据的多样化访问方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class DfxMemory {
public:
// 构造与析构
DfxMemory() = default;
explicit DfxMemory(std::shared_ptr<DfxAccessors> acc) : acc_(acc);
virtual ~DfxMemory() = default;

void SetCtx(void* ctx) { ctx_ = ctx; } // 设置进程context
void SetAlign(bool alignAddr, int alignBytes) // 设置地址对齐和字节对齐值

bool ReadReg(int regIdx, uintptr_t *val); // 读寄存器基础实现,调用accessors提供的AccessReg方法
bool ReadMem(uintptr_t addr, uintptr_t *val); // 读内存基础实现

// 读取指定长度内存实现
virtual size_t Read(uintptr_t& addr, void* val, size_t size, bool incre = false);

// 读取 不同整型长度 指针 字符串 数据的方法
virtual bool ReadU8(uintptr_t& addr, uint8_t *val, bool incre = false);
virtual bool ReadS8(uintptr_t& addr, int8_t *val, bool incre = false)
virtual bool ReadU16(uintptr_t& addr, uint16_t *val, bool incre = false);
virtual bool ReadS16(uintptr_t& addr, int16_t *val, bool incre = false)
virtual bool ReadU32(uintptr_t& addr, uint32_t *val, bool incre = false);
virtual bool ReadS32(uintptr_t& addr, int32_t *val, bool incre = false)
virtual bool ReadU64(uintptr_t& addr, uint64_t *val, bool incre = false);
virtual bool ReadS64(uintptr_t& addr, int64_t *val, bool incre = false)
virtual bool ReadUptr(uintptr_t& addr, uintptr_t *val, bool incre = false);
virtual bool ReadString(uintptr_t& addr, std::string* str, size_t maxSize, bool incre = false);

// 读取 Prel31/Uleb128/Sleb128 编码的方法
virtual bool ReadPrel31(uintptr_t& addr, uintptr_t *val);
virtual uint64_t ReadUleb128(uintptr_t& addr);
virtual int64_t ReadSleb128(uintptr_t& addr);

// 读取 DWARF Exception Header Encoding的方法
virtual void SetDataOffset(uintptr_t offset);
virtual void SetFuncOffset(uintptr_t offset);
virtual size_t GetEncodedSize(uint8_t encoding);
virtual uintptr_t ReadEncodedValue(uintptr_t& addr, uint8_t encoding);

private:
std::shared_ptr<DfxAccessors> acc_;
void* ctx_ = nullptr;
bool alignAddr_ = false;
int alignBytes_ = 0;
uintptr_t dataOffset_ = 0;
uintptr_t funcOffset_ = 0;
};

【说明】

DfxMmap 文件读写实现

DfxMmap 类继承自 DfxMemory 类,提供对指定文件的读写功能,同时管理着通过 mmap 指定文件或地址的数据。

疑问:目前看 DfxMmap 类似乎并不需要继承 DfxMemory 类。

当前 DfxMmap 主要用作加载 Elf 文件或数据,根据文件或数据的格式的不同,分为基础 Elf 文件(包含hap文件中的elf文件形式)、minidebuginfo 节解压后 Elf 格式数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class DfxMmap : public DfxMemory {
public:
DfxMmap() = default;
virtual ~DfxMmap() { Clear(); }

bool Init(const std::string &file); // 加载基础Elf文件接口
bool Init(uint8_t *decompressedData, size_t size); // 加载minidebuginfo类Elf文件接口,数据由xz解析后传入
void Clear();

inline void* Get()
{
if (mmap_ != MAP_FAILED) {
return mmap_;
}
return nullptr;
}
inline size_t Size() { return size_; }

size_t Read(uintptr_t& addr, void* val, size_t size, bool incre = false) override;

private:
void *mmap_ = MAP_FAILED;
size_t size_ = 0;
};

ELF 基础解析

libunwinder 库中基于 DfxElf 类和 ElfParser 类来完成 Elf 文件的基础解析,其中 DfxElf 是抽象出的 Elf 文件对象,ElfParser 是用于解析 Elf 文件的工具类。

公共定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct ElfLoadInfo { // elf load段定义
uint64_t offset = 0;
uint64_t tableVaddr = 0;
size_t tableSize = 0;
};

struct ElfSymbol { // elf内符号定义
uint32_t name;
unsigned char info;
unsigned char other;
uint16_t shndx;
uint64_t value;
uint64_t size;
uint64_t strOffset;
uint64_t strSize;
};

struct ElfShdr { // section header
uint32_t name; // Section name (string tbl index)
uint32_t type; // Section type
uint64_t flags; // Section flags
uint64_t addr; // Section virtual addr at execution
uint64_t offset; // Section file offset
uint64_t size; // Section size in bytes
uint32_t link; // Link to another section
uint32_t info; // Additional section information
uint64_t addrAlign; // Section alignment
uint64_t entSize; // Entry size if section holds table
};

struct ShdrInfo { // simple section header 定义
uint64_t addr = 0;
uint64_t offset = 0;
uint64_t size = 0;
};

struct __attribute__((packed)) DwarfEhFrameHdr { // elf eh_frame_hdr 定义
unsigned char version;
unsigned char ehFramePtrEnc;
unsigned char fdeCountEnc;
unsigned char tableEnc;
ElfW(Addr) ehFrame;
};

struct MiniDebugInfo { // minidebug_info 定义
uint64_t offset = 0;
uintptr_t size = 0;
};

DfxElf 文件对象

类成员对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DfxElf final {
private:
bool valid_ = false; // 有效性标记:elf是否解析成功
uint8_t classType_; // elf文件类型:EI_CLASS32 or EI_CLASS64
int64_t loadBias_ = 0; // elf文件加载到内存中的对齐偏移: 取第一个load段的 vaddr - offset
uint64_t loadBase_ = static_cast<uint64_t>(-1); // elf在进程地址空间中的加载地址:mapstart - mapoffset - loadbias
uint64_t startPc_ = static_cast<uint64_t>(-1); // elf文件在进程地址空间中的起始pc(第一个load段的vaddr + loadbase)
uint64_t endPc_ = 0; // elf文件在进程地址空间中的终止pc
std::string buildId_ = ""; // elf文件的buildid

struct UnwindTableInfo uti_; // elf文件的回栈表信息
bool hasTableInfo_ = false; // elf文件是否包含回栈表信息

std::shared_ptr<DfxMmap> mmap_; // elf文件原始数据对象, 用于访问elf文件
std::unique_ptr<ElfParser> elfParse_; // elf文件解析器
std::vector<ElfSymbol> elfSymbols_; // elf文件非func符号
std::vector<ElfSymbol> funcSymbols_; // elf文件func符号

bool enableMiniDebugInfo_ = false; // elf文件是否开启minidebuginfo模式
std::shared_ptr<DfxElf> embeddedElf_ = nullptr; // minidebuginfo模式下elf文件内嵌的unstripped elf文件
std::shared_ptr<MiniDebugInfo> miniDebugInfo_ = nullptr; // minidebuginfo模式下minidebug_info节基础信息(offset\size)
std::shared_ptr<std::vector<uint8_t>> embeddedElfData_ = nullptr; // minidebuginfo模式下minidebug_info节原始数据
}

重要类成员函数

  1. 初始化函数
1
2
3
4
5
static std::shared_ptr<DfxElf> Create(const std::string& file); // 通用场景下的elf创建
static std::shared_ptr<DfxElf> CreateFromHap(const std::string& file, std::shared_ptr<DfxMap> prevMap, uint64_t& offset);// hap so 场景下的elf创建
explicit DfxElf(const std::string& file);
explicit DfxElf(const int fd, const size_t elfSz, const off_t offset); // hap so 场景下的elf初始化
DfxElf(uint8_t *decompressedData, size_t size); // minidebug_info中的elf初始化
  1. 常用工具函数
1
2
3
4
5
6
7
8
9
bool Read(uintptr_t pos, void *buf, size_t size); // elf读取函数,指定偏移和大小
uint64_t GetRelPc(uint64_t pc, uint64_t mapStart, uint64_t mapOffset); // pc 转换成 relative pc,用于addr2line
const std::unordered_map<uint64_t, ElfLoadInfo>& GetPtLoads(); // 获取elf的四个load段
bool GetFuncInfo(uint64_t addr, ElfSymbol& elfSymbol); // 根据地址获取符号信息
bool GetSectionInfo(ShdrInfo& shdr, const std::string secName); // 根据名称查询section对象
int FindUnwindTableInfo(uintptr_t pc, std::shared_ptr<DfxMap> map, struct UnwindTableInfo& uti); // 根据pc匹配unwindtable
static int FindUnwindTableLocal(uintptr_t pc, struct UnwindTableInfo& uti); // 进程内根据pc匹配unwindtable
static std::string ToReadableBuildId(const std::string& buildIdHex); // build id 从内存数据转成可读16进制
bool InitHeaders(); // elf总解析入口

ElfParser 如何解析Elf文件

Elf文件解析由 ElfParser 基类和 ElfParser32ElfParser64 子类构成,支持对arm32和arm64的Elf文件解析。

Todo

arm_exidx 节回栈表解析

TODO

eh_frame_hdr / eh_frame 节Dwarf回栈表解析

TODO

minidebug_info 节解析

TODO

支持Hap包 CompressNativeLibs 方案的elf解析

TODO

QUT回栈方案实现

TODO

FP回栈方案实现

TODO

unwind 接口封装

TODO