#include<stdio.h>
int main(void)
{
printf("hello world ");
return 0;
}
gcc -g -wall helloworld.c -o hello_world 生成可执行文件,其过程 涉及预处理,编译,汇编,链接等多个步骤
预处理:用于处理预处理命令,上面helloworld代码的预处理就是#include,该头文件所有源码将在第一行展开,可使用 gcc -E helloworld.c > helloworld.i ,生成预处理文件。理解了预处理,在出现一些常见的错误时,才能明白其中的原因。比如,为什么不能在头文件中定义全局变量?这是因为定义全局变量的代码会存在于所有以#include包含该头文件的文件中,也就是说所有的这些文件,都会定义一个同样的全局变量,这样就不可避免地造成了冲突
编译环节指的是对源代码进行语法分析,并优化产生汇编代码(而不是二进制代码)
gcc -S helloworld.c -o helloworld.s
接下来汇编阶段,就是将汇编代码翻译成可执行的指令 gcc -c helloworld.c -o hellowrold.o
链接阶段是生成可执行文件的最后一个步骤,其工作是将各个目标文件--包括库文件,链接生成一可执行文件。这个过程中,涉及的概念比较多,比如地址和空间分配,符号解析,重定位等在Linux环境下由GNU的连接器ld完成的
gcc -g -Wall -v helloworld.c -o helloworld
--------------------------------------------------程序的构成----------------------------------
Linux下可执行文件的格式为elf格式,下面使用readelf查看helloworld格式
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4003c0
Start of program headers: 64 (bytes into file)
Start of section headers: 2560 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 26
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .hash HASH 0000000000400240 00000240
0000000000000024 0000000000000004 A 4 0 8
[ 4] .dynsym DYNSYM 0000000000400268 00000268
0000000000000060 0000000000000018 A 5 1 8
[ 5] .dynstr STRTAB 00000000004002c8 000002c8
000000000000003d 0000000000000000 A 0 0 1
[ 6] .gnu.version VERSYM 0000000000400306 00000306
0000000000000008 0000000000000002 A 4 0 2
[ 7] .gnu.version_r VERNEED 0000000000400310 00000310
0000000000000020 0000000000000000 A 5 1 8
[ 8] .rela.dyn RELA 0000000000400330 00000330
0000000000000018 0000000000000018 A 4 0 8
[ 9] .rela.plt RELA 0000000000400348 00000348
0000000000000030 0000000000000018 A 4 11 8
[10] .init PROGBITS 0000000000400378 00000378
0000000000000018 0000000000000000 AX 0 0 4
[11] .plt PROGBITS 0000000000400390 00000390
0000000000000030 0000000000000010 AX 0 0 4
[12] .text PROGBITS 00000000004003c0 000003c0
0000000000000258 0000000000000000 AX 0 0 16
[13] .fini PROGBITS 0000000000400618 00000618
000000000000000e 0000000000000000 AX 0 0 4
[14] .rodata PROGBITS 0000000000400628 00000628
0000000000000010 0000000000000000 A 0 0 4
[15] .eh_frame_hdr PROGBITS 0000000000400638 00000638
0000000000000024 0000000000000000 A 0 0 4
[16] .eh_frame PROGBITS 0000000000400660 00000660
000000000000007c 0000000000000000 A 0 0 8
[17] .ctors PROGBITS 00000000006006e0 000006e0
0000000000000010 0000000000000000 WA 0 0 8
[18] .dtors PROGBITS 00000000006006f0 000006f0
0000000000000010 0000000000000000 WA 0 0 8
[19] .jcr PROGBITS 0000000000600700 00000700
0000000000000008 0000000000000000 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000600708 00000708
0000000000000190 0000000000000010 WA 5 0 8
[21] .got PROGBITS 0000000000600898 00000898
0000000000000008 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 00000000006008a0 000008a0
0000000000000028 0000000000000008 WA 0 0 8
[23] .data PROGBITS 00000000006008c8 000008c8
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 00000000006008d8 000008d8
0000000000000010 0000000000000000 WA 0 0 8
[25] .comment PROGBITS 0000000000000000 000008d8
000000000000003e 0000000000000001 MS 0 0 1
[26] .shstrtab STRTAB 0000000000000000 00000916
00000000000000e7 0000000000000000 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001140
0000000000000660 0000000000000018 28 47 8
[28] .strtab STRTAB 0000000000000000 000017a0
000000000000025b 0000000000000000 0 0 1
由于输出过多,后面的结果并没有完全展示出来。ELF文件的主要内容就是由各个section及symbol表组成的。在上面的section列表中,大家最熟悉的应该是text段、data段和bss段。text段为代码段,用于保存可执行指令。data段为数据段,用于保存有非0初始值的全局变量和静态变量。bss段用于保存没有初始值或初值为0的全局变量和静态变量,当程序加载时,bss段中的变量会被初始化为0。这个段并不占用物理空间——因为完全没有必要,这些变量的值固定初始化为0,因此何必占用宝贵的物理空间?
其他段没有这三个段有名,下面来介绍一下其中一些比较常见的段:
·debug段:顾名思义,用于保存调试信息。
·dynamic段:用于保存动态链接信息。
·fini段:用于保存进程退出时的执行程序。当进程结束时,系统会自动执行这部分代码。
·init段:用于保存进程启动时的执行程序。当进程启动时,系统会自动执行这部分代码。
·rodata段:用于保存只读数据,如const修饰的全局变量、字符串常量。
·symtab段:用于保存符号表。
-------------------------------------程序是如何跑起来的--------------------
在Linux环境下,可以使用strace跟踪系统调用,此处以helloworld为例
execve("./hello", ["./hello"], [/* 41 vars */]) = 0
brk(0) = 0x151b000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f30733ef000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=62458, ...}) = 0
mmap(NULL, 62458, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f30733df000
close(3)= 0
open("/lib64/libc.so.6", O_RDONLY) = 3 //加载c语言库
read(3, "177ELF2113 3 >