RISC-V动态链接实验

准备

实验环境

qemu linux启动环境: https://www.laumy.tech/1186.html

代码

动态库

 cat swap.c
#include <stdio.h>
int shared = 1;
int z;
void swap(int *a, int *b)
{
        printf("
        *a ^= *b ^= *a ^= *b;
}

主程序

#include <stdio.h>
extern  int shared;
int s;
int main(void)
{
        int a = 100;
        int b = 200;
        int c = 300;

        swap(&a, &shared);
        printf("a:
        sleep(10);
        swap(&a, &b);
        printf("a:
        return 0;
}

编译

生成动态库:riscv64-unknown-linux-gnu-gcc -g -fPIC -shared swap.c -o libswap.so
生成可执行程序:riscv64-unknown-linux-gnu-gcc -g main.c ./libswap.so

将生成的可执行程序通过通过mount方式挂载到qemu linux中。

qemu gdb

启动gdb: riscv64-unknown-linux-gnu-gdb
连接: target remote:1234
加载符号:file xxx/a.out
打断点:b main
执行显示信息:(可实现在gdbinit中写好)
    display/z ra
    display/zsp
    display/z s0
    display/zs1
    display/z s2
    display/zs3
    display/z s4
    display/zs5
    display/z a0
    display/za1
    display/z a2
    display/za3
    display/z a4
    display/za5
    display/z a6
    display/za7
    display/z t0
    display/zt1
    display/z t2
    display/zt3
    display/z t4
    display/zt5
    set disassemble-next-line on
在qemu linux中:./a.out

第一次跳转到PLT表

准备跳转到swap@plt表

0x00000000000105d0      7               swap(&a, &shared);
   0x00000000000105c6 <main+16>:        fec40713        addi    a4,s0,-20
   0x00000000000105ca <main+20>:        83818593        addi    a1,gp,-1992
   0x00000000000105ce <main+24>:        853a            mv      a0,a4
=> 0x00000000000105d0 <main+26>:        f11ff0ef        jal     ra,0x104e0 <swap@plt>

寄存器信息如下:
10: /z ra = 0x00000000000105d4
11: /zsp = 0x0000003fd23d4bf0
12: /z s0 = 0x0000003fd23d4c10
13: /zs1 = 0x0000000000000000
14: /z s2 = 0x0000000000145f60
15: /zs3 = 0x0000000000000000
16: /z s4 = 0x00000000001a2828
17: /zs5 = 0x0000000000000000
18: /z a0 = 0x0000003fd23d4bfc
19: /za1 = 0x0000000000012038
20: /z a2 = 0x0000003fd23d4d98
21: /za3 = 0x0000000000000000
22: /z a4 = 0x0000003fd23d4bfc
23: /za5 = 0x0000000000000064
24: /z a6 = 0x0000003f81c60d90
25: /za7 = 0x7a2f5b5a40014e00
26: /z t0 = 0x0000003f81b61e18
27: /zt1 = 0x0000003f81b7e6d2
28: /z t2 = 0xffffffffffffffff
29: /zt3 = 0x00000000000206d2
30: /z t4 = 0x0000003f81c86a8c
31: /zt5 = 0x0000000000000004

下面是.plt的代码,先关注swap@plt

Disassembly of section .plt:

00000000000104a0 <_PROCEDURE_LINKAGE_TABLE_>:
   104a0:   00002397            auipc   t2,0x2
   104a4:   41c30333            sub t1,t1,t3
   104a8:   b683be03            ld  t3,-1176(t2) # 12008 <__TMC_END__>
   104ac:   fd430313            addi    t1,t1,-44
   104b0:   b6838293            addi    t0,t2,-1176
   104b4:   00135313            srli    t1,t1,0x1
   104b8:   0082b283            ld  t0,8(t0)
   104bc:   000e0067            jr  t3 # 1a000 <__global_pointer$+0x7800>

00000000000104c0 <__libc_start_main@plt>:
   104c0:   00002e17            auipc   t3,0x2
   104c4:   b58e3e03            ld  t3,-1192(t3) # 12018 <__libc_start_main@GLIBC_2.27>
   104c8:   000e0367            jalr    t1,t3
   104cc:   00000013            nop

00000000000104d0 <sleep@plt>:
   104d0:   00002e17            auipc   t3,0x2
   104d4:   b50e3e03            ld  t3,-1200(t3) # 12020 <sleep@GLIBC_2.27>
   104d8:   000e0367            jalr    t1,t3
   104dc:   00000013            nop

00000000000104e0 <swap@plt>:
   104e0:   00002e17            auipc   t3,0x2
   104e4:   b48e3e03            ld  t3,-1208(t3) # 12028 <swap>
   104e8:   000e0367            jalr    t1,t3
   104ec:   00000013            nop

从上面发现,swap@plt,是从12028处加载一个地址,然后跳转运行。下面则是12028的地址,发现是.got表。注意12028: 04a0 addi s0,sp,584,不要被这个误导,正好反汇编翻译2字节对应是addi s0,sp,584。实际上0x12028处存储的地址是:0x104a0,因此上面加载的t3值就是0x104a0,即jalr将跳转到该值运行。而0x104a0正好是PROCEDURE_LINKAGE_TABLE

Disassembly of section .got:

0000000000012008 <__TMC_END__>:
   12008:   ffffffff            0xffffffff
   1200c:   ffffffff            0xffffffff
    ...
   12018:   04a0                    addi    s0,sp,584
   1201a:   0001                    nop
   1201c:   0000                    unimp
   1201e:   0000                    unimp
   12020:   04a0                    addi    s0,sp,584
   12022:   0001                    nop
   12024:   0000                    unimp
   12026:   0000                    unimp
   12028:   04a0                    addi    s0,sp,584
   1202a:   0001                    nop
   1202c:   0000                    unimp
    ...

从上面分析可知,swap函数地址存储在.got表0x12028处,而该值初值是PROCEDURE_LINKAGE_TABLE,这是因为在编译完还没有运行前,swap在动态库中地址并没有确定,实际的地址需要程序运行加载才会确定,所以了默认先填PROCEDURE_LINKAGE_TABLE,而该标签中的代码,正好就是处理寻址swap地址的功能。

下面是gdb单步调试的信息,下面的跳转跟我们分析的一致。

也可以通过x命令查询.got表的信息

小结:跳转到.plt表,第一次执行对应的.got表中没有存储swap的地址,将先跳转到PROCEDURE_LINKAGE_TABLE,即.plt的第一表项。

procedure linkage table

.plt的地址一表项存储的是链接器表。

Disassembly of section .plt:

00000000000104a0 <_PROCEDURE_LINKAGE_TABLE_>:
   104a0:   00002397            auipc   t2,0x2
   104a4:   41c30333            sub t1,t1,t3
   104a8:   b683be03            ld  t3,-1176(t2) # 12008 <__TMC_END__>
   104ac:   fd430313            addi    t1,t1,-44
   104b0:   b6838293            addi    t0,t2,-1176
   104b4:   00135313            srli    t1,t1,0x1
   104b8:   0082b283            ld  t0,8(t0)
   104bc:   000e0067            jr  t3 # 1a000 <__global_pointer$+0x7800>

上面的汇编最终会跳转到链接器里面,所做的工作就是通过链接器查询swap的地址,然后填充到.got中,然后进行跳转swap,这里就不再分析,直接看最后的结果。当执行完链接器的处理后,会更新.got表。

从上面可知swap函数在got表中,地址被更新为0x3f9a69f4c2。

再次调用swap函数

当再次调用swap函数的时候,ld t3,-1208(t3),这里的t3就变成了0x3f9a69f4c2,这样就不会再执行PROCEDURE_LINKAGE_TABLE,直接跳转到swap的地址。

还可以使用cat /proc/xx/maps确定so落在的范围,下面的地址不一定匹配了, 重新运行了一次a.out,只是给个示例。