OpenSBI ELF rela.dyn和.dynsym动态链接过程

在OpenSBI中重定位分成了两种,根据是否配置了FW_PIC宏来区分,

配置了FW_PIC,即本文描述的rela.dyn和.dynsym的动态链接。

未配置FW_PIC是加载地址和链接地址不相等情况下的代码拷贝重定位。

rela.dyn

.rela.dyn节是什么节呢?该节保存的是重定位信息,数据内容是包含带有显式加数的重定位条目,每个条目固定大小(24个字节)。话又说回来,什么是重定位?
这里引用oracle网站的解答:重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享目标文件可包含进程的程序映像的正确信息。重定位项即是这些数据。
了解了重定位,现在回到开头的问题,.rela.dyn节是什么节?.rela.dyn在动态链接的目标文件中保存的是需要被重定位的变量数据。
使用readelf读取fw_payload.elf 中的rel.dyn的偏移和大小
$ riscv64-linux-gnu-readelf -S build/platform/generic/firmware/fw_payload.elf
There are 26 section headers, starting at offset 0xf66c0:


Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000080000000  00000120
       0000000000012d20  0000000000000000 WAX       0     0     8
  [ 2] .rodata           PROGBITS         0000000080013000  00013120
       00000000000020a0  0000000000000000   A       0     0     8
  [ 3] .dynstr           STRTAB           00000000800150a0  000151c0
       000000000000028b  0000000000000000   A       0     0     1
  [ 4] .gnu.hash         GNU_HASH         0000000080015330  00015450
       0000000000000100  0000000000000000   A      10     0     8
  [ 5] .data             PROGBITS         0000000080016000  00016120
       0000000000000ef8  0000000000000000  WA       0     0     8
  [ 6] .dynamic          DYNAMIC          0000000080016ef8  00017018
       0000000000000110  0000000000000010  WA       3     0     8
  [ 7] .got              PROGBITS         0000000080017008  00017128
       0000000000000120  0000000000000008  WA       0     0     8
  [ 8] .got.plt          PROGBITS         0000000080017128  00017248
       0000000000000010  0000000000000008  WA       0     0     8
  [ 9] .htif             PROGBITS         0000000080017138  00017258
       0000000000000010  0000000000000000  WA       0     0     8
  [10] .dynsym           DYNSYM           0000000080017148  00017268
       0000000000000378  0000000000000018   A       3     2     8
  [11] .rela.dyn         RELA             00000000800174c0  000175e0
       00000000000016c8  0000000000000018   A      10     0     8
  [12] .bss              NOBITS           0000000080019000  00018ca8
       0000000000022d70  0000000000000000  WA       0     0     8
  [13] .payload          PROGBITS         0000000080200000  00018cb0
       0000000000002128  0000000000000000  AX       0     0     16
  [14] .debug_line       PROGBITS         0000000000000000  0001add8
       0000000000034428  0000000000000000           0     0     1
  [15] .debug_info       PROGBITS         0000000000000000  0004f200
       000000000003cc8c  0000000000000000           0     0     1
  [16] .debug_abbrev     PROGBITS         0000000000000000  0008be8c
       000000000000ba21  0000000000000000           0     0     1
  [17] .debug_aranges    PROGBITS         0000000000000000  000978b0
       0000000000000fd0  0000000000000000           0     0     16
  [18] .debug_str        PROGBITS         0000000000000000  00098880
       0000000000006b7e  0000000000000001  MS       0     0     1
  [19] .debug_ranges     PROGBITS         0000000000000000  0009f400
       0000000000009930  0000000000000000           0     0     16
  [20] .debug_loc        PROGBITS         0000000000000000  000a8d30
       000000000003e388  0000000000000000           0     0     1
  [21] .comment          PROGBITS         0000000000000000  000e70b8
       0000000000000029  0000000000000001  MS       0     0     1
  [22] .debug_frame      PROGBITS         0000000000000000  000e70e8
       0000000000007560  0000000000000000           0     0     8
  [23] .symtab           SYMTAB           0000000000000000  000ee648
       0000000000004c38  0000000000000018          24   404     8
  [24] .strtab           STRTAB           0000000000000000  000f3280
       0000000000003351  0000000000000000           0     0     1
  [25] .shstrtab         STRTAB           0000000000000000  000f65d1
       00000000000000ed  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

.rela.dyn 偏移为0x175e0, 大小为0x16c8

读取.rela.dyn

$ xxd -s 0x175e0 -l 0x16c8 build/platform/generic/firmware/fw_payload.elf
000175e0: c060 0180 0000 0000 0300 0000 0000 0000  .`..............
000175f0: 400d 0080 0000 0000 f860 0180 0000 0000  @........`......
00017600: 0300 0000 0000 0000 040e 0080 0000 0000  ................
00017610: 0061 0180 0000 0000 0300 0000 0000 0000  .a..............
00017620: 0061 0180 0000 0000 0861 0180 0000 0000  .a.......a......
00017630: 0300 0000 0000 0000 0061 0180 0000 0000  .........a......
00017640: 3061 0180 0000 0000 0300 0000 0000 0000  0a..............
00017650: 4a2b 0080 0000 0000 3861 0180 0000 0000  J+......8a......
00017660: 0300 0000 0000 0000 ee2b 0080 0000 0000  .........+......
00017670: 4061 0180 0000 0000 0300 0000 0000 0000  @a..............
00017680: ca2a 0080 0000 0000 a061 0180 0000 0000  .*.......a......
00017690: 0300 0000 0000 0000 b861 0180 0000 0000  .........a......
000176a0: b061 0180 0000 0000 0300 0000 0000 0000  .a..............
000176b0: a07e 0380 0000 0000 b861 0180 0000 0000  .~.......a......
000176c0: 0300 0000 0000 0000 f434 0080 0000 0000  .........4......
000176d0: c061 0180 0000 0000 0300 0000 0000 0000  .a..............
000176e0: fa35 0080 0000 0000 c861 0180 0000 0000  .5.......a......
000176f0: 0300 0000 0000 0000 1c35 0080 0000 0000  .........5......
00017700: d061 0180 0000 0000 0300 0000 0000 0000  .a..............
00017710: 4235 0080 0000 0000 e861 0180 0000 0000  B5.......a......
00017720: 0300 0000 0000 0000 ae35 0080 0000 0000  .........5......
00017730: f061 0180 0000 0000 0300 0000 0000 0000  .a..............
00017740: 9435 0080 0000 0000 f861 0180 0000 0000  .5.......a......
00017750: 0300 0000 0000 0000 c835 0080 0000 0000  .........5......
.....
已知每个条目固定大小24个字节,那么我们可以计算出一共有243个条目,即0x16c8/24=243,现在我们看看每个条目的构成,即每个条目的结构组成.
使用readelf命令查看动态链接表
$ riscv64-linux-gnu-readelf -rW build/platform/generic/firmware/fw_payload.elf


Relocation section '.rela.dyn' at offset 0x175e0 contains 243 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
00000000800160c0  0000000000000003 R_RISCV_RELATIVE                          80000d40
00000000800160f8  0000000000000003 R_RISCV_RELATIVE                          80000e04
......
0000000080017108  0000000000000003 R_RISCV_RELATIVE                          8000278e
0000000080017110  0000000000000003 R_RISCV_RELATIVE                          8000b448
0000000080017118  0000000000000003 R_RISCV_RELATIVE                          80016d60
0000000080017120  0000000000000003 R_RISCV_RELATIVE                          80016590
0000000080016798  0000000200000002 R_RISCV_64             00000000800168c8 fdt_serial_uart8250 + 0
0000000080016a08  0000002100000002 R_RISCV_64             0000000080016ac8 fdt_poweroff_gpio + 0

和我们计算出来的条目数一致。

typedef struct
{
  Elf64_Addr    r_offset;               /* Address */
  Elf64_Xword   r_info;                 /* Relocation type and symbol index */
  Elf64_Sxword  r_addend;               /* Addend */
} Elf64_Rela;

每个条目分别由3个部分组成:r_offset、r_info和r_addend,每个部分占8个字节,正好3*8=24个字节(每个条目的大小)

r_offset

    本字段保存的是重定位所作用的位置。对于重定位文件来说,此值是受重定位作用的存储单元在节中的字节偏移量;对于可执行文件或共享目标文件来说,此值是受重定位作用的存储单元的虚拟地址。(可变值(通常是位置无关的)虚拟内存地址,在重定位过程中保存“patched”值)

r_info

    该字段指定必须对其进行重定位的符号表索引以及要应用的重定位类型。例如,调用指令的重定位项包含所调用的函数的符号表索引。如果索引是未定义的符号索引 STN_UNDEF,则重定位将使用零作为符号值。重定位类型特定于处理器。重定位项的重定位类型或符号表索引是将 ELF64_R_TYPE 或 ELF64_R_SYM 分别应用于项的 r_info 成员所得的结果,计算过程如下:

#define ELF64_R_SYM(i)                  ((i) >> 32)
#define ELF64_R_TYPE(i)                 ((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type)          ((((Elf64_Xword) (sym)) << 32) + (type))

r_addend

    该字段是一个常量加数,用于计算存储在可重定位字段中的值。

第一个条目分析

我们先看看.rela.dyn节的第一个条目,已知偏移量是0x175e0,那么我们从0x175e0的地方开始取24个字节,如下:

$ xxd -s 0x175e0 -l 24  build/platform/generic/firmware/fw_payload.elf
000175e0: c060 0180 0000 0000 0300 0000 0000 0000  .`..............
000175f0: 400d 0080 0000 0000                      @.......

r_offset

    r_offset是0x800160c0, 该处的代码或数据会被(修改)重定位,需要进行扫描类型的重定位得看r_info

r_info

    r_info是0x3,类型是R_RISCV_RELATIVE

/* RISC-V relocations.  */
#define R_RISCV_NONE             0
#define R_RISCV_32               1
#define R_RISCV_64               2
#define R_RISCV_RELATIVE         3
#define R_RISCV_COPY             4
#define R_RISCV_JUMP_SLOT        5
#define R_RISCV_TLS_DTPMOD32     6
#define R_RISCV_TLS_DTPMOD64     7
#define R_RISCV_TLS_DTPREL32     8
#define R_RISCV_TLS_DTPREL64     9
#define R_RISCV_TLS_TPREL32     10
#define R_RISCV_TLS_TPREL64     11
#define R_RISCV_BRANCH          16
#define R_RISCV_JAL             17

根据类型可以进行计算存储在重定位字段中的值,由于是RELATIVE类型,所以计算法是B+A,B表示执行过程中将共享目标文件装入内存的基本地址。通常,生成的共享目标文件的基本虚拟地址为0,A表示常量加数,用于计算存储在可重定位字段中的值。

r_addend

    r_addend是0x80000d40,该字段即上面说的B+A中的A

现在我们可以看出B是0,A是0x80000d40,B+A=0x80000d40,符号名是0x80000d40,说明第一条目标是符号名称为0x80000d40的重定位条目。

以上第一个条目和使用命令riscv64-linux-gnu-readelf -rW build/platform/generic/firmware/fw_payload.elf看到的一模一样

在qemu上运行验证结果

查看opensbi重定位,究竟定位的是什么

启动一个终端

$ qemu-system-riscv64 -M virt -m 256M -nographic -bios build/platform/generic/firmware/fw_payload.bin -s -S

另启动一个终端

$ gdb-multiarch --tui  build/platform/generic/firmware/fw_payload.elf -ex 'target remote localhost:1234'
(gdb) b _start
Breakpoint 1 at 0x80000000: file /workspace/richard/project/risc-v/opensbi/firmware/fw_base.S, line 51.
(gdb) layout regs
(gdb) c

执行s指令将程序运行到 fw_base.S中 "2:标签"

2:  /*判断.rele.dyn每个条目的类型*/
    REG_L   t5, -(REGBYTES*2)(t0)   /* t5 <-- relocation info:type */
    li  t3, R_RISCV_RELATIVE    /* reloc type R_RISCV_RELATIVE */
    /*如果type不是R_RISCV_RELATIVE,则跳转到3f*/
    bne t5, t3, 3f
    /*
        当 type为R_RISCV_RELATIVE,程序走这里
        以第一个条目为例
        t3是r_offset 0x800160c0
        t5是r_addend 0x80000d40
        将运行时地址存放在Got表项中
    */
    REG_L   t3, -(REGBYTES*3)(t0)  /*将r_offset加载到t3中*/
    REG_L   t5, -(REGBYTES)(t0) /* t5 <-- r_addend */
    add t5, t5, t2   /*t2(加载地址-链接地址= offset),t2是0*/
    add t3, t3, t2
    REG_S   t5, 0(t3)       /* store runtime address to the GOT entry */
    j   5f

根据之前的分析,第一个.rela.dyn条目的内容如下

r_offset : 0x800160c0
r_addend : 0x80000d40

还没有重定位前,上面两个地址的内容如下

(gdb) x 0x800160c0
0x800160c0 <ipi_smode_ops+48>:  0x00000000
(gdb) x 0x80000d40
0x80000d40 <sbi_ipi_process_smode>:     0xe4221141

第一轮执行完"2:"标签后的内容如下:

(gdb) x 0x800160c0
0x800160c0 <ipi_smode_ops+48>:  0x80000d40
(gdb) x 0x80000d40
0x80000d40 <sbi_ipi_process_smode>:     0xe4221141

在0x800160c0中的内容填入了0x80000d40的地址

在查看其他.rela.dyn中的条目,

(gdb) x 0x80000e04
0x80000e04 <sbi_ipi_process_halt>:      0xe0221141

这些函数都来自lib中

.dynsym

.dynsym 中存放了共享库所需要在runtime加载的函数对应的symbols。

.dynsym 保存的是一个动态符号表,其中每个条目的大小是固定的24个字节,先看看.dynsym节的属性,即描述条目信息

$ riscv64-linux-gnu-readelf -S build/platform/generic/firmware/fw_payload.elf

There are 26 section headers, starting at offset 0xf66c0:

Section Headers:

  [Nr] Name              Type             Address           Offset

       Size              EntSize          Flags  Link  Info  Align

......

  [ 6] .dynamic          DYNAMIC          0000000080016ef8  00017018

       0000000000000110  0000000000000010  WA       3     0     8

  [ 7] .got              PROGBITS         0000000080017008  00017128

       0000000000000120  0000000000000008  WA       0     0     8

  [ 8] .got.plt          PROGBITS         0000000080017128  00017248

       0000000000000010  0000000000000008  WA       0     0     8

  [ 9] .htif             PROGBITS         0000000080017138  00017258

       0000000000000010  0000000000000000  WA       0     0     8

  [10] .dynsym           DYNSYM           0000000080017148  00017268

       0000000000000378  0000000000000018   A       3     2     8

  [11] .rela.dyn         RELA             00000000800174c0  000175e0

       00000000000016c8  0000000000000018   A      10     0     8

  [12] .bss              NOBITS           0000000080019000  00018ca8

       0000000000022d70  0000000000000000  WA       0     0     8

.......

dynsym section的offset为0x17268, 大小为0x378

.dynsym 中每个条目大小是0x18字节,所以0x378/0x18 = 37,一共37个条目。每个条目的构成都是相同的。下面查看哪些字段构成一个条目:

typedef struct
{
  Elf64_Word    st_name;                /* Symbol name (string tbl index) 4字节*/
  unsigned char st_info;                /* Symbol type and binding        1字节*/
  unsigned char st_other;               /* Symbol visibility              1字节*/
  Elf64_Section st_shndx;               /* Section index                  2字节*/
  Elf64_Addr    st_value;               /* Symbol value                   8字节*/
  Elf64_Xword   st_size;                /* Symbol size                    8字节*/
} Elf64_Sym;

我是64位系统,所以看64位的符号表结构体

st_name:长度是4个字节,该字段表示符号名称在动态符号字符串表中的偏移量或者说索引

st_info:  长度是1个字节,该字段表示符号的类型和绑定属性。

st_other: 长度是1个字节,表示符号的可见性。

st_shndx:长度是2个字节,每个符号条目的定义都与某些节对应,该字段保存的是相关节头表的索引,为0表示未定义。

st_value:长度是8个字节,表示符号的值,可能是地址或者位置偏移量

st_size:  长度是8个字节,表示的是符号的大小,如全局函数指针的大小。大多数符号都有大小,如果没有则此字段的内容是0。

直接用xxd命令查看第二个段

第二个段的偏移:0x17268+24*2 = 0x17298

$ xxd -s 0x17298 -l 24 build/platform/generic/firmware/fw_payload.elf

00017298: 9701 0000 1100 0500 c868 0180 0000 0000  .........h......

000172a8: 1000 0000 0000 0000                      ........

st_name: 0x197 (在符号表中的偏移量)
st_info: 0x11
st_other: 0x0
st_shndx:0x5
st_value: 0x800168c8
st_size: 0x10
说明符号名称在动态符号字符串表中的偏移量是0x197,大小为0x10。如下图从动态符号字符串表中根据索引查询到该符号名称是fdt_serial_uart8
$ riscv64-linux-gnu-readelf -S build/platform/generic/firmware/fw_payload.elf
There are 26 section headers, starting at offset 0xf66c0:
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000080000000  00000120
       0000000000012d20  0000000000000000 WAX       0     0     8
  [ 2] .rodata           PROGBITS         0000000080013000  00013120
       00000000000020a8  0000000000000000   A       0     0     8
  [ 3] .dynstr           STRTAB           00000000800150a8  000151c8
       000000000000028b  0000000000000000   A       0     0     1
...

符号表的偏移是0x151c8, 字符串在符号表的偏移是0x197,   0x151c8+0x197 = 0x1535f

$ xxd -s 0x1535f -l 0x10  build/platform/generic/firmware/fw_payload.elf

0001535f: 6664 745f 7365 7269 616c 5f75 6172 7438  fdt_serial_uart8

为什么这里长度是16字节,不对的呀,应该是19字节呢?
使用readelf查看.dynsym段内容
$ riscv64-linux-gnu-readelf --dyn-syms  build/platform/generic/firmware/fw_payload.elf


Symbol table '.dynsym' contains 37 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000080000000     0 SECTION LOCAL  DEFAULT    1
     2: 00000000800168c8    16 OBJECT  GLOBAL DEFAULT    5 fdt_serial_uart8250
     3: 00000000800165f0    48 OBJECT  GLOBAL DEFAULT    5 ecall_vendor
     4: 00000000800381e0  1024 OBJECT  GLOBAL DEFAULT   12 hartid_to_domain_table
     5: 0000000080002854    26 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_fence_i
     6: 00000000800165c0    48 OBJECT  GLOBAL DEFAULT    5 ecall_time
     7: 0000000080016cc0    32 OBJECT  GLOBAL DEFAULT    5 fdt_ipi_mswi
     8: 0000000080002948   134 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_hfence_vvma
     9: 0000000080016c58    32 OBJECT  GLOBAL DEFAULT    5 fdt_irqchip_plic
    10: 0000000080016530    48 OBJECT  GLOBAL DEFAULT    5 ecall_srst
    11: 000000008000a3a0     0 NOTYPE  GLOBAL DEFAULT    1 __sbi_expected_trap_hext
    12: 000000008000b448     0 NOTYPE  GLOBAL DEFAULT    1 __thead_pre_start_warm
    13: 0000000080016038     4 OBJECT  GLOBAL DEFAULT    5 last_hartid_having_scratc
    14: 0000000080016590    48 OBJECT  GLOBAL DEFAULT    5 ecall_rfence
    15: 0000000080016470    48 OBJECT  GLOBAL DEFAULT    5 ecall_base
    16: 00000000800027e2   114 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_sfence_vma_
    17: 000000008000b460     0 NOTYPE  GLOBAL DEFAULT    1 __reset_thead_csr_stub
    18: 0000000080037a50  1024 OBJECT  GLOBAL DEFAULT   12 hartid_to_scratch_table
    19: 000000008003bb70   512 OBJECT  GLOBAL DEFAULT   12 custom_csr
    20: 00000000800163d0   144 OBJECT  GLOBAL DEFAULT    5 root
    21: 0000000080016d60    24 OBJECT  GLOBAL DEFAULT    5 fdt_gpio_sifive
    22: 0000000080016d30    16 OBJECT  GLOBAL DEFAULT    5 fdt_i2c_adapter_sifive
    23: 0000000080016560    48 OBJECT  GLOBAL DEFAULT    5 ecall_ipi
    24: 00000000800164a0    48 OBJECT  GLOBAL DEFAULT    5 ecall_hsm
    25: 00000000800028e2   102 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_hfence_gvma
    26: 00000000800029ce   108 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_hfence_gvma
    27: 000000008000278e    84 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_sfence_vma
    28: 000000008000286e   116 FUNC    GLOBAL DEFAULT    1 sbi_tlb_local_hfence_vvma
    29: 0000000080016250    64 OBJECT  GLOBAL DEFAULT    5 sifive_fu540
    30: 000000008000b440     0 NOTYPE  GLOBAL DEFAULT    1 __fdt_reset_thead_csrr
    31: 0000000080016620     8 OBJECT  GLOBAL DEFAULT    5 sbi_hart_expected_trap
    32: 0000000080016330    64 OBJECT  GLOBAL DEFAULT    5 sifive_fu740
    33: 0000000080016ac8    16 OBJECT  GLOBAL DEFAULT    5 fdt_poweroff_gpio
    34: 0000000080016730    32 OBJECT  GLOBAL DEFAULT    5 fdt_timer_mtimer
    35: 00000000800164d0    48 OBJECT  GLOBAL DEFAULT    5 ecall_legacy
    36: 0000000080016500    48 OBJECT  GLOBAL DEFAULT    5 ecall_pmu

.symtab和.dynsym区别

.symtab和.dynsym两个不同的symbol table, 它们有什么区别?

.dynsym是.symtab的一个子集, 大家都有疑问, 为什么要两个信息重合的结构?

需要先了解allocable/non-allocable ELF section, ELF文件包含一些sections(如code和data)是在运行时需要的, 这些sections被称为allocable; 而其他一些sections仅仅是linker,debugger等工具需要, 在运行时并不需要, 这些sections被称为non-allocable的. 当linker构建ELF文件时, 它把allocable的数据放到一个地方, 将non-allocable的数据放到其他地方. 当OS加载ELF文件时, 仅仅allocable的数据被映射到内存, non-allocable的数据仍静静地呆在文件里不被处理. strip就是用来移除某些non-allocable sections的.

.symtab包含大量linker,debugger需要的数据, 但并不为runtime必需, 它是non-allocable的; .dynsym包含.symtab的一个子集, 比如共享库所需要在runtime加载的函数对应的symbols, 它是allocable的.

因此, 得到答案:

1. strip移除的应是.symtab.

2. nm读取的应是.symtab: 上面发现的libattr等nm结果为空, libpthread nm结果非空应是正常的. 3. 共享库包含的.dynsym是runtime必需的, 是allocable的.

OpenSBI中对.dynsym的操作

在fw_base.S中的第"3:"和"4:"标签是对.dynsym的操作

$ riscv64-linux-gnu-readelf -rW build/platform/generic/firmware/fw_payload.elf
Relocation section '.rela.dyn' at offset 0x175e0 contains 243 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
......
0000000080017120  0000000000000003 R_RISCV_RELATIVE                          80016590
0000000080016798  0000000200000002 R_RISCV_64             00000000800168c8 fdt_serial_uart8250 + 0
0000000080016a08  0000002100000002 R_RISCV_64             0000000080016ac8 fdt_poweroff_gpio + 0

操作的是下面两个条目,分别为第241和242条目

    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000080016798  0000000200000002 R_RISCV_64             00000000800168c8 fdt_serial_uart8250 + 0
0000000080016a08  0000002100000002 R_RISCV_64             0000000080016ac8 fdt_poweroff_gpio + 0

分析第一个条目的r_offset r_info和r_addend

$ riscv64-linux-gnu-readelf -S build/platform/generic/firmware/fw_payload.elf
There are 26 section headers, starting at offset 0xf66c0:


Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
.......
  [10] .dynsym           DYNSYM           0000000080017148  00017268
       0000000000000378  0000000000000018   A       3     2     8
  [11] .rela.dyn         RELA             00000000800174c0  000175e0
       00000000000016c8  0000000000000018   A      10     0     8
.......

.rela.dyn在整个文件中的偏移0x175e0, 第241个条目,地址为0x175e0+241*24 = 0x18c78

$ xxd -s 0x18c78 -l 24  build/platform/generic/firmware/fw_payload.elf
00018c78: 9867 0180 0000 0000 0200 0000 0200 0000  .g..............
00018c88: 0000 0000 0000 0000                      ........

r_offset: 0x80016798(需要链接的符号地址)

r_info: 0x200000002

r_addend: 0x0

以下是链接详细过程

3:

    /*读取__dyn_sym_start起始地址到t4*/

    lla t4, __dyn_sym_start

4:/*此时读取的.rely.dyn的type为R_RISCV_64

$ riscv64-linux-gnu-readelf -rW build/platform/generic/firmware/fw_payload.elf

    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend

0000000080017120  0000000000000003 R_RISCV_RELATIVE                          80016590

0000000080016798  0000000200000002 R_RISCV_64             00000000800168c8 fdt_serial_uart8250 + 0

0000000080016a08  0000002100000002 R_RISCV_64             0000000080016ac8 fdt_poweroff_gpio + 0

*/

    /*读取.rely.dyn中的r_info信息,r_info为0x200000002*/

    REG_L   t5, -(REGBYTES*2)(t0)   /* t5 <-- relocation info:type */

    /*将t5的值逻辑右移0x20位,t6则为0x2,即sym table index为2。在sym table中的第几个条目*/

    srli    t6, t5, SYM_INDEX   /* t6 <--- sym table index */

    /*t5为0x2,即relocation type为2*/

    andi    t5, t5, 0xFF        /* t5 <--- relocation type */

    /*RELOC_TYPE在64位系统里面为2,t3为2*/

    li  t3, RELOC_TYPE

    /*t3和t5不等才会跳转到5f*/

    bne t5, t3, 5f

    /* address R_RISCV_64 or R_RISCV_32 cases*/

    /*加载r_offset到t3,即t3=0x80016798*/

    REG_L   t3, -(REGBYTES*3)(t0)

    /*RV64中SYM_SIZE=24,t5=24*/

    li  t5, SYM_SIZE

    /*t6=t5*t6 = 24*2 = 48*/

    mul t6, t6, t5

    /*s5 = t4为.dynsym的起始地址 + t6 */

    add s5, t4, t6

    /*t0=.rely.dyn 中的r_addend 为0*/

    REG_L   t6, -(REGBYTES)(t0) /* t0 <-- addend */

    /*RV64中,REGBYTES=8,从s5+8地址处读取内存数据到t5中,t5为st_value: 0x800168c8*/

    REG_L   t5, REGBYTES(s5)

    /*t6(r_addend) + t5(st_value) = 0+0x800168c8*/

    add t5, t5, t6

    /*t2(加载地址-链接地址= offset) = 0*/

    add t5, t5, t2      /* t5 <-- location to fix up in RAM */

    add t3, t3, t2      /* t3 <-- location to fix up in RAM */

    /*将t5(st_value) 存储到t3(r_offset)地址中

    t5寄存器的值如下

    (gdb) x 0x800168c8

    0x800168c8 <fdt_serial_uart8250>:       0x800168d8

    t3寄存器的值如下

    (gdb) x 0x80016798

    0x80016798 <serial_drivers>:    0x00000000

    这里将0x800168c8存储到0x80016798对应的内存中,完成动态链接

    */

    REG_S   t5, 0(t3)       /* store runtime address to the variable */

    /*

    (gdb) x 0x80016798

    0x80016798 <serial_drivers>:    0x800168c8

    */

参考:

ELF的.rela.dyn节 - 编程和调试

简要剖析ELF文件动态链接会用到的段 - 知乎

Executable and Linkable Format (ELF)

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byd yes

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值