GCC编译可重定位文件简单示例

GCC在编译C语言程序时,经历四个过程,即:预处理->汇编->编译->链接。下面以前三个过程为例,展示将一个c文件编译到可重定位文件的过程中,各个步骤所得目标文件的内容。

首先,编写两个简单函数,用于两个浮点数的加法和减法。

obj_0.c

/**
 * @{fun} relocatable obj file 0
 *        addition and subtraction
 * @{author} YanWen
 */

// addition function
double add(double x1, double x2) { return x1 + x2; }

// subtraction function
double subtract(double x1, double x2) { return x1 - x2; }

第一步,进行预处理,执行一下命令:

$ gcc -o obj_0.i -E obj_0.c

得到预处理文件如下:

obj_0.c的预处理文件

# 1 "obj_0.c"
# 1 ""
# 1 ""
# 31 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "" 2
# 1 "obj_0.c"

double add(double x1, double x2) { return x1 + x2; }

double subtract(double x1, double x2) { return x1 - x2; }

从预处理结果可以看出,因为程序较为简单,所以预处理内容较少,没有体现宏展开等内容。但是大体变化还是可以看出来的。在原obj_0.c的基础上,预处理去除了注释,添加了行号和文件标识。函数定义的代码仍然存在。

第二步,执行以下命令,进行汇编:

$ gcc -o obj_0.s -S obj_0.i

得到AT&T汇编代码如下:

obj_0.s

	.file	"obj_0.c"
	.text
	.globl	add
	.type	add, @function
add:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movsd	%xmm0, -8(%rbp)
	movsd	%xmm1, -16(%rbp)
	movsd	-8(%rbp), %xmm0
	addsd	-16(%rbp), %xmm0
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	add, .-add
	.globl	subtract
	.type	subtract, @function
subtract:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movsd	%xmm0, -8(%rbp)
	movsd	%xmm1, -16(%rbp)
	movsd	-8(%rbp), %xmm0
	subsd	-16(%rbp), %xmm0
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	subtract, .-subtract
	.ident	"GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
	.section	.note.GNU-stack,"",@progbits

在汇编文件obj_0.s里,我们不用关注汇编的实现细节,当然,经过对照也可以很容易明白各个指令的含义。

第三步,编译:

$ gcc -o obj_0.o -c obj_0.s

编译后,得到目标文件obj_0.o。可以使用利用binutils工具包的readelf查看目标文件头,如下图:

简单函数目标文件程序elf头
简单函数目标文件程序elf头

可从上图看出,得到的obj_0.o属于REL可重定位文件。在该文件里,代码段和数据段还未确定,经链接后生成可执行文件或共享库。

最后,出于好奇,采用objdump反汇编obj_0.o,执行以下指令:

$ objdump -S obj_0.o > obj_0.sx

最后得到目标文件反汇编代码:

obj_0.sx:

obj_0.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <add>;:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	f2 0f 11 45 f8       	movsd  %xmm0,-0x8(%rbp)
   9:	f2 0f 11 4d f0       	movsd  %xmm1,-0x10(%rbp)
   e:	f2 0f 10 45 f8       	movsd  -0x8(%rbp),%xmm0
  13:	f2 0f 58 45 f0       	addsd  -0x10(%rbp),%xmm0
  18:	5d                   	pop    %rbp
  19:	c3                   	retq   

000000000000001a <subtract>:
  1a:	55                   	push   %rbp
  1b:	48 89 e5             	mov    %rsp,%rbp
  1e:	f2 0f 11 45 f8       	movsd  %xmm0,-0x8(%rbp)
  23:	f2 0f 11 4d f0       	movsd  %xmm1,-0x10(%rbp)
  28:	f2 0f 10 45 f8       	movsd  -0x8(%rbp),%xmm0
  2d:	f2 0f 5c 45 f0       	subsd  -0x10(%rbp),%xmm0
  32:	5d                   	pop    %rbp
  33:	c3                   	retq   

我们得到的是可重定位文件obj_0.o的.text段的内容。与obj_o.s比较发现,.c源文件编译出的obj_o.s与obj_o.o反汇编得到的代码大致一样,在函数功能实现的汇编代码里,各个指令是一致的,包括采用xmm寄存器传参和返回参数。

好了,短暂的介绍就到这里,网上还有很多资料,感兴趣的同学可以去找找。

参考资料:en-wiki-x86 calling convention

作者: V

Web Dev