摘要:本文主要为你解释一个C文件是如何被一步步处理成可执行的elf格式文件的。
本文来源: 从C文件到ELF
说明:所有本文的用例是以下hello.c程序:
#include<stdio.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
return 0;
}
1.预处理
我们来看看hello.c经过预处理以后的结果:gcc -E hello.c -o hello.i
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 940 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
int main(int argc, char *argv[])
{
printf("hello world\n");
return 0;
}
变化:预处理结果就是将stdio.h 文件中的内容插入到hello.c中了,文件变成了855行
2编译为汇编代码(Compilation)
编译:编译器的作用是预处理之后,可直接对生成的hello.i文件编译,生成汇编代码:
gcc -S hello.i -o hello.s
gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。我们同样可以用vim打开观看:
1 .file "hello.c"
2 .section .rodata
3 .LC0:
4 .string "hello world"
5 .text
6 .globl main
7 .type main, @function
8 main:
9 .LFB0:
10 .cfi_startproc
11 pushl %ebp
12 .cfi_def_cfa_offset 8
13 .cfi_offset 5, -8
14 movl %esp, %ebp
15 .cfi_def_cfa_register 5
16 andl $-16, %esp
17 subl $16, %esp
18 movl $.LC0, (%esp)
19 call puts
20 movl $0, %eax
21 leave
22 .cfi_restore 5
23 .cfi_def_cfa 4, 4
24 ret
25 .cfi_endproc
26 .LFE0:
27 .size main, .-main
28 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
29 .section .note.GNU-stack,"",@progbits
3汇编(Assembly)
汇编:汇编器对于上一小节中生成的汇编代码文件hello.s,gas汇编器负责将其编译为目标文件,如下:
gcc -c hello.s -o hello.o
说明,这一步将程序划分为若干段(数据段,代码段等),可以用objdump命令来查看这些目标文件的内容。
$ objdump -x hello.o
hello.o: file format elf32-i386
hello.o
architecture: i386, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001c 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000050 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000050 2**2
ALLOC
3 .rodata 0000000c 00000000 00000000 00000050 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002b 00000000 00000000 0000005c 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 00000000 00000000 00000087 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 00000000 00000000 00000088 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
4连接(Linking)
链接:连接器的目的主要是进行重定位和符号解析,gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test
gcc hello.o -o hello
可以用readelf命令查看elf文件的详细内容:
Histogram for `.gnu.hash' bucket list length (total of 2 buckets):
Length Number % of total Coverage
0 1 ( 50.0%)
1 1 ( 50.0%) 100.0%
Version symbols section '.gnu.version' contains 5 entries:
Addr: 0000000008048266 Offset: 0x000266 Link: 5 (.dynsym)
000: 0 (*local*) 2 (GLIBC_2.0) 0 (*local*) 2 (GLIBC_2.0)
004: 1 (*global*)
Version needs section '.gnu.version_r' contains 1 entries:
Addr: 0x0000000008048270 Offset: 0x000270 Link: 6 (.dynstr)
000000: Version: 1 File: libc.so.6 Cnt: 1
0x0010: Name: GLIBC_2.0 Flags: none Version: 2
Notes at offset 0x00000168 with length 0x00000020:
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 2.6.24
在命令行窗口中,执行./hello, 让它说HelloWorld吧!
总结一下:源文件name.c,经过-s处理,在编译之前停下,生成汇编代码,当然有intel 和AT&T等汇编格式可选;
然后经过-o,编译成目标代码,目标代码已经基本上是一些机器代码和重定位等信息了,典型的目标代码有text , data, bss等几个段构成
然后经过链接,生成可执行目标文件,这已经是完完全全的机器代码了(包含一些装载器所需要的定位信息)
本文详述了一个C源文件如何通过预处理、编译、汇编及链接过程,最终转换为ELF格式的可执行文件。该过程涉及预处理器对源代码的处理、编译器生成汇编代码、汇编器创建目标文件以及链接器整合各部分生成最终的可执行文件。
968

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



