0%

ELF格式

问题

  • 下列是一个c++的崩溃,使用addr2line可以根据地址反解获取到具体的崩溃代码,原理是什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    pid: 13195, tid: 32264, name: utonavi.minimap  >>> com.autonavi.minimap <<<
    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    x0 0000000000000000 x1 00000073a1384e80 x2 0000000000000037 x3 0000000000000002
    x4 0000007346c91677 x5 00000073a1384eb7 x6 676f6c616964227b x7 5f49554e227b3a22
    x8 0000000000000041 x9 00000073a1384e80 x10 000000729a8fb741 x11 4e4556455f49554e
    x12 5f49554e223a2254 x13 0a7d7d2254494e49 x14 000000000001c020 x15 0000000034155555
    x16 0000007367374138 x17 000000751e920160 x18 000000729a8fa210 x19 00000073673b9f88
    x20 0000000000000000 x21 0000000000000000 x22 000000729a8fb740 x23 00000073673b9f58
    x24 000000729a8fc000 x25 000000729a8fb759 x26 0000000000000000 x27 000000000000000c
    x28 000000729a8fb740 x29 000000729a8fb7f0
    sp 000000729a8fb740 lr 00000073668f4cbc pc 0000000000000000

    backtrace:
    #00 pc 0000000000000000 <unknown>
    #01 pc 000000000024fcb8 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #02 pc 000000000028aa34 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #03 pc 000000000020a220 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #04 pc 0000000000293c44 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #05 pc 0000000000293ab4 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #06 pc 00000000002237c8 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #07 pc 0000000000213414 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #08 pc 0000000000213794 /data/app/~~wdXHTUGs3NOnlBosse3xOg==/com.autonavi.minimap-BalUzJbyL60S0Dsc4ib0eg==/lib/arm64/libamapvcs.so
    #09 pc 00000000000f55c8 /apex/com.android.runtime/lib64/bionic/libc.so (_ZL15__pthread_startPv+208)
    #10 pc 000000000008efbc /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68)

    image.png

  • 经常听到c++的同学说带符号的so,带符号的so和不带符号的so有什么区别?

    文件格式

    ELF文件有三种类型,可以通过ELF Header中的e_type成员进行区分:

  • 可重定位文件(Relocatable File):ETL_REL。一般为.o文件,可以与其他目标文件链接来创建可执行文件或共享目标文件的代码和数据。

  • 可执行文件(Executable File):ET_EXEC。它是一个可以执行的程序,例如linux中的bash,gcc等。

  • 共享目标文件(Shared Object File):ET_DYN。一般为.so文件。目前常见于Android中

1.png
如图所示,为ELF文件的基本结构,主要由四部分组成:

  • ELF Header 描述了整个ELF文件的基础信息,以及Program Header和Section Header的信息。
  • ELF Program Header Table 这部分内容主要是程序运行时需要,且每一个entry可能包含多个section表
  • ELF Section Header Table 这部分内容主要是用于程序链接,且每一个entry只包含一个section表
  • ELF Sections (symtab表和dynsym表都属于section)

image.png

3436285-7da1de406279d376.png

ELF Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private final char[] magic = new char[4];
private EiClass eiClass; // class类型
private EiData eiData; // 编码类型(大端/小端)
private EiVersion eiVersion; // 版本
private EiOSAbi eiOSAbi; // os类型
private final char[] eiPad = new char[6]; // 填充
private char eiAbiVersion;
private char eiNidentSize; // ident[]大小

private EType eType; // ELF类型 3种类型,可重定位文件,可执行文件,共享目标文件
private EMachine eMachine; // 平台
private EVersion eVersion; //版本
private long eEntry; //8 程序入口地址
private long ePhoff; //8 程序段表头的偏移
private long eShoff; //8 节表头的偏移
private int eFlags; //4 和处理器相关的一个标记
private int elfHeaderSize;// 2 ELF表头的大小
private int ePhEntrySize; //2 程序表头的Entry大小
private int ePhNum; //2 程序表头的Entry数量
private int eShEntrySize; //2 节表头Entry大小
private int eShNum;//2 节表头Entry数量
private int e_ShStrIndex; //2 节表中字符Entry的索引位置

image.png

ELF Program Header

1
2
3
4
5
6
7
8
public PHType pType; //类型
public PHFlag pFlags; // 该字段给出了与段相关的标记,主要就是判断该段是干啥的
public long pOffset; // 偏移地址
public long pVirtualAddr; //运行时在内存中的虚拟地址
public long pPhysicalAddr; // 物理地址,寻址相关
public long pFiles; // 该字段给出了在文件中该段的大小
public long pMems; // 该字段给出了在内存中该段的大小
public long pAlign; //对齐方式

image.png
PHDR:此类型header元素描述了program header table自身的信息。
LOAD:代码段,数据段等信息
DYNAMIC:描述了动态链接的相关信息,比如依赖了哪些库。
NOTE:附加信息
GNU_RELRO:链接相关
GNU_EH_FRAME: 异常处理相关

ELF Section Header

1
2
3
4
5
6
7
8
9
10
public int nameOffset; // 相对于.shstrtab表中虚拟地址的偏移,主要是要获取字符串的名字
public SHType sType; // 节类型
public SHFlag sFlag; // 节标志位
public long sAddr; // 节的虚拟地址空间,如果该节加载到内存,则使用此地址
public long sOffset; // 该节的相对于文件的偏移地址
public long sSize; // 节的大小
public int sLink; // 该节依赖的节区信息
public int sInfo; // 该节依赖的节区信息
public long sAddrAlign; // 节对齐方式
public long sEntrySize; // 节 entry 大小,eg: symtab entry size

image.png

Symbal Table

1
2
3
4
5
6
public int symNameOff;//4 相对于字符表中的index位置
public int symInfo; // 1 用于标示此符号的属性,占一个字节(2个字),两个标示位,第一个标示位(低四位)标志作用域,第二个标示位(高四位)标示符号类型
public int symOther; //1 固定值为0。
public int symShndx;//2 节区头部表索引
public long symValue;//8 函数地址
public long symSize;//8 st_value 地址开始,共占的长度大小
  • dynstr,strtab,shstrtab的区别
    • dynstr 动态字符串表,主要给dynsym表中使用
    • strtab 字符串表,主要给symtab表使用
    • shstrtab 给 section header table中使用

      演示

      通过解析ELF文件,获取到符号表中的一个函数地址(代码逻辑地址),直接逻辑地址+基地址求出内存中的虚拟地址,通过这个虚拟地址直接调用函数。