RISC-V动态链接实验
- RISC-V
- 2024-05-18
- 148热度
- 0评论
准备
实验环境
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单步调试的信息,下面的跳转跟我们分析的一致。
小结:跳转到.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,只是给个示例。