1. 基础知识
1.1 栈帧
栈帧的定义:栈上保持多个栈帧实体,每个栈帧对应于一个函数执行的环境,这个执行环境主要用于存储函数参数、函数的局部变量、函数执行结束后的返回地址、临时变量和寄存器等。下面将以一个 add 函数为例介绍栈帧,函数源码如下所示:
#include<stdio.h>
int add(int a,int b){
int c;
c = a + b;
printf("%d\n",c);
return c;
}
int main(){
int a=0,b=1;
add(a,b);
return 0;
}
函数调用过程:(1)main 函数将调用参数 b,a 从右向左依次压入栈中;(2)main 函数中下一条指令的地址压入返回地址;(3)跳转到 add 函数起始地址执行;(4)add 函数先压入 main函数栈帧的 ebp,保存 main 函数的基址;(5)当前 esp 的值保存到 ebp 寄存器,保存 add 函数的基址。
函数返回过程:(1)add 函数的返回值保存在 eax 寄存器中;(2)将 ebp 中 add 函数的基址传递给 esp;(3)将压入的 main 函数栈帧的 ebp 弹出,赋值给ebp;(4)跳转到返回地址,执行main 函数。
1.2 GOT与PLT
内容概述:PLT 与 GOT 是 Linux 的 ELF 格式文件定位全局变量和过程的表。ELF 格式的共享库使用 PIC 技术使代码和数据的引用与地址无关。
全局偏移表(GOT,Global Offset Table):GOT 是定位全局变量和函数的表,表中每一项都是程序要引用的全局变量或函数的地址。对于模块外部引用的全局变量和全局函数,用 GOT 表的表项内容作为地址来间接寻址。对于本模块内的静态变量和静态函数,由于与 GOT 的距离是固定的,用 GOT 表的首地址作为一个基准,用相对于该基准的偏移量来引用。
模块指的是一个程序的编译单元,通常是指一个单独的源文件或者可执行程序或动态链接库(如 .so、.dll 文件)。现代程序通常由多个模块组成,每个模块负责不同的功能或实现不同的部分代码。
过程链接表(PLT,Procedure Linkage Table):PLT 是 Linux ELF 文件中将动态链接的程序进行延迟绑定的表,即函数第一次被调用的时候才进行绑定。每个动态链接的程序和共享库都有一个 PLT 表,表中每一项都是一段代码,对应于程序要引用的全局函数。
程序对某个函数的访问都被调整为对 PLT 入口的访问。每个 PLT 入口项都对应一个 GOT 项,执行函数的过程就是跳转到相应 GOT 项存储的地址的过程。函数第一次调用时,通过 PLT 生成 GOT。函数第二次及后续调用 ,直接使用 GOT。示例:
1.3 程序调试
1.3.1 源码编译
以 print.c 程序作为示例,介绍 GOT 与 PLT 的原理,程序源码如下所示:
void print(char *s)
{
printf("%s",s);
}
接下来使用-fPIC和-shared设置来编译这个文件,生成动态链接库。其中,-fPIC 是产生地址无关
代码,-shared 是生成共享库,具体指令如下所示:
gcc print.c -o libprint.so -fPIC -shared
编写一个头文件 print.h,来声明这个函数,源代码如下所示:
#ifndef dynamiclink_H
#define dynamiclink_H
void print(char *s);
#endif
在主程序 dynamic.c 中

最低0.47元/天 解锁文章
1656

被折叠的 条评论
为什么被折叠?



