函数栈调用解析复习

复习参考资料

1 共性

一个函数都可以分为三部分

  • prologue: 这部分负责相关栈和寄存器的初始化
  • body: 这部分负责函数运算主题部分
  • Epilogue: 这部分负责对函数栈的清理和恢复工作

2 x86的函数调用

uploading.4e448015.gif转存失败重新上传取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.486
.MODEL FLAT
.CODE
PUBLIC _myFunc
_myFunc PROC
  ; Subroutine Prologue
  push ebp     ; Save the old base pointer value.
  mov ebp, esp ; Set the new base pointer value.
  sub esp, 4   ; Make room for one 4-byte local variable.
  push edi     ; Save the values of registers that the function
  push esi     ; will modify. This function uses EDI and ESI.
  ; (no need to save EBX, EBP, or ESP)

  ; Subroutine Body
  mov eax, [ebp+8]   ; Move value of parameter 1 into EAX
  mov esi, [ebp+12]  ; Move value of parameter 2 into ESI
  mov edi, [ebp+16]  ; Move value of parameter 3 into EDI

  mov [ebp-4], edi   ; Move EDI into the local variable
  add [ebp-4], esi   ; Add ESI into the local variable
  add eax, [ebp-4]   ; Add the contents of the local variable
                     ; into EAX (final result)

  ; Subroutine Epilogue 
  pop esi      ; Recover register values
  pop edi
  mov esp, ebp ; Deallocate local variables
  pop ebp ; Restore the caller's base pointer value
  ret
_myFunc ENDP
END

注意:

  • ebp 保存栈帧

  • esp 保存栈底

  • 函数参数都在栈上,使用ebp作为基准进行调用

  • call func 等于 push eip + jump func

  • ret 等于pop eip

2 ARM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.global main

main:8  
	push   {r11, lr}    /* Start of the prologue. Saving Frame Pointer and LR onto the stack */
	add    r11, sp, #0  /* Setting up the bottom of the stack frame */
	sub    sp, sp, #16  /* End of the prologue. Allocating some buffer on the stack */
	mov    r0, #1       /* setting up local variables (a=1). This also serves as setting up the first parameter for the max function */
	mov    r1, #2       /* setting up local variables (b=2). This also serves as setting up the second parameter for the max function */
	bl     max          /* Calling/branching to function max */
	sub    sp, r11, #0  /* Start of the epilogue. Readjusting the Stack Pointer */
	pop    {r11, pc}    /* End of the epilogue. Restoring Frame pointer from the stack, jumping to previously saved LR via direct load into PC */

max:
	push   {r11}        /* Start of the prologue. Saving Frame Pointer onto the stack */
	add    r11, sp, #0  /* Setting up the bottom of the stack frame */
	sub    sp, sp, #12  /* End of the prologue. Allocating some buffer on the stack */
	cmp    r0, r1       /* Implementation of if(a<b) */
	movlt  r0, r1       /* if r0 was lower than r1, store r1 into r0 */
	add    sp, r11, #0  /* Start of the epilogue. Readjusting the Stack Pointer */
	pop    {r11}        /* restoring frame pointer */
	bx     lr           /* End of the epilogue. Jumping back to main via LR register */

注意:

  • r11(fp)保存栈帧
  • lr保存返回地址
  • sp保存栈底
  • 参数前四个存在r0-r3,后面存在栈
  • bl func等于mov lr,PC+4 b func
  • PC 指向当前正在运行的指令,编码,译码,执行导致pc的改变

3 动态加载

动态链接处理共享库的时候非常高效,当一个程序被加载进内存的时候,动态链接器会将需要的共享库,加载并绑定到进程的地址空间,在调用某个函数的时候,对函数地址进行解析,达到对函数调用的目的

3.1 两个表

3.1.1 PLT(Procedure Linkage Table)

过程连接表,在程序中以.plt节表示,表在代码段,每个表项表示了一个需要重定位的函数的相关若干条指令,每个表项长度为16字节,存储用于延迟绑定的代码

可执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PLT[0]  --> 与每个函数第一次链接相关指令
例:
0x4004c0:
0x4004c0:  ff 35 42 0b 20 00        push   QWORD PTR [rip+0x200b42]      // push [GOT[1]]
0x4004c6:  ff 25 44 0b 20 00        jmp    QWORD PTR [rip+0x200b44]      // jmp  [GOT[2]]
0x4004cc:  0f 1f 40 00              nop    DWORD PTR [rax+0x0]
即:
    第一条指令为 push 一个值,该值为 GOT[1] 处存放的地址,
    第二条指令为 jmp 到一个地址执行,该值为 GOT[2] 处存放的地址

PLT[1]  --> 某个函数链接时所需要的指令,与 got 表一一对应
例:
0x4004d0 <__stack_chk_fail@plt>:
0x4004d0:  ff 25 42 0b 20 00        jmp    QWORD PTR [rip+0x200b42]  // jmp GOT[3]  
0x4004d6:  68 00 00 00 00           push   0x0                       // push reloc_arg
0x4004db:  e9 e0 ff ff ff           jmp    0x4004c0 <_init+0x20>     // jmp PLT[0]
即:
    第一条指令为: jmp 到一个地址执行,该地址为对应 GOT 表项处存放的地址,在下文中会具体讨论这种结构
    第二条指令为: push 一个值,该值作用在下文提到
    第三个指令为: jmp 一个地址执行,其实该地址就是上边提到的 PLT[0] 的地址,
                也就是说接下来要执行 PLT[0] 中保存的两条指令

3.1.2 GOT(Global Offset Table)

全局偏移表,在程序中以.gpt.got.plt节区表示,存放于数据段,可读可写,每一个表项存储的都是一个地址,每个表项的长度都是当前程序对应的需要寻址的长度(32位程序:4个字节,64位程序8个字节)

1
2
3
4
5
6
7
8
9
10
GOT[0]  --> 此处存放的是 .dynamic 的地址;该节(段)的作用会在下文讨论
GOT[1]  --> 此处存放的是 link_map 的地址;该结构也会在下文讨论
GOT[2]  --> 此处存放的是 dl_runtime_resolve 函数的地址
GOT[3]  --> 与 PLT[1] 对应,存放的是与该表项 (PLT[1]) 要解析的函数相关地址,
            由于延迟绑定的原因,开始未调用对应函数时该项存的是 PLT[1] 中第二条指令的地址,
            当进行完一次延迟绑定之后存放的才是所要解析的函数的真实地址
GOT[4]  --> 与 PLT[2] 对应,所以存放的是与 PLT[2] 所解析的函数相关的地址
 .
 .
 .

3.1.3 表与表之间的关系

1
2
3
4
5
6
7
8
9
10
GOT[0]: .dynamic 地址                    PLT[0]: 与每个函数第一次链接相关指令
GOT[1]: link_map 地址
GOT[2]: dl_runtime_resolve 函数地址      
GOT[3]  -->  PLT[1]    // 一一对应
GOT[4]  -->  PLT[2]    // 相互协同,作用于一个函数
GOT[5]  -->  PLT[3]    // 一个保存的是该函数所需要的延迟绑定的指令
GOT[6]  -->  PLT[4]    // 一个是保存个该函数链接所需要的地址
 .             .
 .             .
 .             .

3.2 三个节

3.2.1 .dynamic

加载的时候.dynamic节区也是.dynamic段,这部分主要与动态链接的整个过程有关,保存的是与动态链接相关的信息。

通过.dynamic能够找到与动态链接相关的其他节区(.dynsym,.dynstr,.rel.plt等节区)

结构:

1
2
3
4
5
6
7
8
9
struct Elf64_Dyn {
    Elf64_Sxword d_tag;		// type of dynamic table entry
    						// 识别该结构体表示哪一节,通过这个字段能够寻找不同节区
    union {
        Elf64_Xword d_val;	// Integer value of entry
        					// 对应表在文件中的偏移地址
        Elf64_Addr  d_ptr;	// Pointer value of entry
    } d_un;
}

3.2.2 .dynsym

动态符号表,存储着在动态链接中需要的每个函数的符号信息,每个结构体对应一个符号,结构体数组,d_tag = DT_SYMTAB(0x06)

  • .symtab在运行时会将运行时需要的符号表复制一份到.dynsym,而不需要的并不需要加载进内存,因此可以优化
  • 同理strtab也可以优化

结构:

1
2
3
4
5
6
7
8
9
10
typedef struct {
    Elf64_Word st_name;	// Symbol name(String table index)
    					// 保存了函数名在.dynstr中的偏移,结合.dynstr可以找到准确的函数名
    unsigned char st_info;	// Synbol type and binding
    unsigned char st_other; // Symbol visibility
    Elf64_Section st_shndx;	// Section index
    Elf64_Addr	  st_value; // Symbol value
    						// 如果符号被导出,则存在这个导出函数的虚拟地址,否则为null
    Elf64_Xword	  st_size;	// Symbol size
} Elf64_Sym;

3.2.3 .dynstr

动态字符串表,存放了一系列的字符串,表示了符号的名称,是一组字符串数组,d_tag = DT_STRTAB(0x05)

  • strtab在运行的时候会将运行时需要的字符串赋值到.dynstr,所以strtab可以优化

3.2.4 .rel.plt(.rela.plt)

重定位表,保存了重定位相关的信息,描述了如何在链接或者运行时,对ELF目标文件的某部分内容或者进程镜像进行补充和修改

每个结构体也对应一个需要重定位的函数,d_tag = DT_REL(0x11)/DT_RELA(0x7)

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct {
    Elf64_Addr	r_offset;	// Address
    						// 解析完的函数的真是地址存放的位置
    						// 对应解析函数的GOT表项地址
    Elf64_Xword r_info;		// Relocation type and symbol index
    						// 高位表示索引,低位表示信息
} Elf64_Rel;

typedef struct {
   Elf32_Addr    r_offset;  /* Address */
   Elf32_Word    r_info;    /* Relocation type and symbol index */
   Elf32_Sword   r_addend;  /* Addend */
 } Elf32_Rela;

3.3 链接过程

1586185898823

主要函数是 dl_runtime__resolve(link_map_obj, reloc_arg)

第一个参数是一个link_map

第二个参数是一个重定位参数,即在运行PLT代码时push进去的n

这个函数主要是调用了一个dl_fixup(link_map_obj, reloc_arg)结构完成主要功能

第一个参数的作用是获得重定位函数所在的library的基址地址,以及或许在library中寻找重定位函数时所需要的Section。

第二个函数主要是确定需要解析的函数名,以及解析完之后写回的地址。

因此整个过程大概可以理解为,dl_fixup,通过reloc_arg参数确定当前正在解析的函数名

然后拿着函数名,利用link_map找到.dynsym ,然后进行函数名匹配,如果成功,则从.dynsym中获取对应符号的函数地址

1
2
3
4
5
reloc_arg : 函数名A
linkmap --> .dynsym
--> 遍历匹配函数名称 A
若 某一个 Elf64_Sym(符号) 的 st_name + .dynstr ==  A
则 该 Elf64_Sym 表示的符号即为函数 A

如果存在一个函数put,在第一次执行的时候绑定过程如下:

  1. 进入puts@plt,执行2中的case2:

  2. 1
    2
    3
    4
    5
    6
    
    case1:
    jump GOT[x] # address
    
    case2:
    push n
    jump PLT[0] # dynmic
    
  3. 1
    2
    3
    
    PLT[0]:
    push GOT[1] # link_map
    jump GOT[2] # call dl_runtime_resolve(栈上的参数为 n 以及 link_map)
    
  4. 上面dl_runtime_resolve函数得到的puts函数的真实地址写入GOT[x]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值