从汇编的角度来看编译时的函数链接

环境(均在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中。


参考链接

  1. Linux-静态链接库和动态链接库
  2. linux静态链接库
  3. linux下动态库的编写和调用
  4. 动态链接库和静态链接库 - 知乎
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值