环境(均在linux下运行,虽然windows也兼容以下操作,但windows下正规的操作是与下面不同的)
Ubuntu 20.04
gcc
ldd
IDA Pro 6.8
函数调用
函数调用可以分为三类:
- 本地函数
- 静态链接库
- 动态链接
本地函数
最常见的函数调用。在C中表现为
int main () {...; func();}
int func() {...}
不作过多解释。
静态链接
在windows上的静态链接库为*.lib
;在linux上的静态链接库为 *.a
。
静态链接在编译时就被编译进二进制文件中,之后的运行无需依赖库文件。
缺点是增大了二进制文件的体积,相较于动态编译运行时速度更快。
示例:
// main.c
#include<stdio.h>
#include"test.h"
int main() {
int r = add1(1);
return 0;
}
// test.h
int add1(int n);
// test.c
int add1(int n) {
return n + 1;
}
先将test.c编译成静态函数库 test.a,再与main.c进行编译:
gcc -c test.c -o test.o # 生成.o目标文件
ar rcv libtest.a test.o # 打包成.a静态函数库,必须以lib*.a的方式命名,否则下面找不到库文件
gcc .\main.c -L./ -ltest # 在当前目录下查找test库,编译成功。 也可以直接制定库文件:gcc main.c libtest.a,这样是相同的效果。
gcc -c 表示只预处理、编译,不链接
gcc -L. 表示在当前目录.
下寻找库函数
gcc -lxxx 表示寻找xxx的库
用IDA查看libtest.a文件,找到了add1函数的定义:
// libtest.asm
.text:0000000000000000 public add1
.text:0000000000000000 add1 proc near
.text:0000000000000000 rep nop edx
.text:0000000000000004 push rbp
.text:0000000000000005 mov rbp, rsp
.text:0000000000000008 mov [rbp-4], edi
.text:000000000000000B mov eax, [rbp-4]
.text:000000000000000E add eax, 1
.text:0000000000000011 pop rbp
.text:0000000000000012
.text:0000000000000012 locret_12: ; DATA XREF: .eh_frame:0000000000000038o
.text:0000000000000012 retn
.text:0000000000000012 add1 endp
static_main中关于main函数和add1函数,都在文件中:
// static_main.asm
.text:0000000000001129 public main
.text:0000000000001129 main proc near ; DATA XREF: _start+21o
.text:0000000000001129 rep nop edx
.text:000000000000112D push rbp
.text:000000000000112E mov rbp, rsp
.text:0000000000001131 sub rsp, 10h
.text:0000000000001135 mov edi, 1
.text:000000000000113A call add1
.text:000000000000113F mov [rbp-4], eax
.text:0000000000001142 mov eax, 0
.text:0000000000001147 leave
.text:0000000000001148 retn
.text:0000000000001148 main endp
...
...
...
.text:0000000000001149 public add1
.text:0000000000001149 add1 proc near ; CODE XREF: main+11p
.text:0000000000001149 rep nop edx
.text:000000000000114D push rbp
.text:000000000000114E mov rbp, rsp
.text:0000000000001151 mov [rbp-4], edi
.text:0000000000001154 mov eax, [rbp-4]
.text:0000000000001157 add eax, 1
.text:000000000000115A pop rbp
.text:000000000000115B retn
.text:000000000000115B add1 endp
可以看到,通过静态链接得到的二进制可执行文件,里面是由add1函数的源汇编代码的。
动态链接
在windows上的动态链接库为*.dll
;在linux上的动态链接库为 *.so
。
gcc -fpic -c test.c // 生成.o文件
gcc -shared -o libhead.so test.o // 将.o文件生成为.so库文件,注意命名规则和上面一样
gcc -o dynamic_main main.c -L. -ltest // 编译成功
ldd查看引用文件:
$ ldd dynamic_main
linux-vdso.so.1 (0x00007ffe305d1000)
libtest.so => not found # 需要libtest.so,但是找不到此文件
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0aae9c5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0aaebcf000)
在上面可以看到,dynamic_main找不到我们自己的.so文件,只需要修改一下环境变量即可:
export LD_LIBRARY_PATH=/dir/of/so/file:$LD_LIBRARY_PATH
将/dir/of/so/file替换为.so文件所在目录。
ldd再次查看:
linux-vdso.so.1 (0x00007ffd9cfab000)
libtest.so => /home/***/Desktop/lf/libtest.so (0x00007f05800b6000) // 找到了.so文件
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f057feb3000)
/lib64/ld-linux-x86-64.so.2 (0x00007f05800c2000)
IDA查看libtest.so,可以看到文件中关于add1函数的定义:
.text:00000000000010F9 add1 proc near
.text:00000000000010F9 rep nop edx
.text:00000000000010FD push rbp
.text:00000000000010FE mov rbp, rsp
.text:0000000000001101 mov [rbp-4], edi
.text:0000000000001104 mov eax, [rbp-4]
.text:0000000000001107 add eax, 1
.text:000000000000110A pop rbp
.text:000000000000110B retn
.text:000000000000110B add1 endp
IDA查看dynamic_main:
// dynmic_main.asm
.text:0000000000001149 public main
.text:0000000000001149 main proc near ; DATA XREF: _start+21o
.text:0000000000001149 rep nop edx
.text:000000000000114D push rbp
.text:000000000000114E mov rbp, rsp
.text:0000000000001151 sub rsp, 10h
.text:0000000000001155 mov edi, 1
.text:000000000000115A call sub_1050 // 调用sub_1050,此函数代码在下
.text:000000000000115F mov [rbp-4], eax
.text:0000000000001162 mov eax, 0
.text:0000000000001167 leave
.text:0000000000001168 retn
.text:0000000000001168 main endp
...
...
.plt.sec:0000000000001050 sub_1050 proc near ; CODE XREF: main+11p // sub_1050函数
.plt.sec:0000000000001050 rep nop edx
.plt.sec:0000000000001054 repne jmp cs:add1_ptr
.plt.sec:0000000000001054 sub_1050 endp
...
...
.got:0000000000003FD0 add1_ptr dq offset add1 ; DATA XREF: sub_1050+4r
...
...
extern:000000000000401C extrn add1:near ; DATA XREF: .got:add1_ptro
具体的细节不甚了解,但是一步一步的引用到了extern段的代码,即调用了外部文件的函数,而add1函数本身的代码没出现在dynamic_main中。