本篇文章全部摘抄自學長博客供以后學習:
http://efraim.me/2015/12/05/tech-linux-2015-12-05/
排版因與博客園編輯器不同而稍作修改。
輸出hello world!系統發生了什么?
經典的hello world!
1 #include <stdio.h> 2 3 int main () 4 { 5 printf("hello world!"); 6 }
該段程序,在hello world過程中,系統發生了什么?
0X00 新建hello.c
hello.c文件,是文件由0/1的位(bit)序列,8位組成一組,稱為字節,一個個字節表示文件中的一個個字符。
由此引出,系統中的所有信息(磁盤文件,系統程序,網絡傳輸的數據等等),都是一些0/1序列,當我們打開hello.c文件時,系統會按某種規則文件進行解析,最后呈現出人類能看懂的字符,而不是二進制0/1。
區分不同數據對象的唯一方法,就是系統讀取這個文件時上下文(可以理解為當時的環境),比如,在不同的上下文中,一個同樣的字節序列,有可能表示一個整數,浮點數,字符數或者機器指令。
0x01 解析hello.c
C語言是高級語言,所以這個形式的代碼能讓人讀懂。但系統不認識,為了讓系統能認識,需要將hello.c轉化成一系機器能認識的語言指令,然后將這些指令按照一種稱為可執行目標程序的格式進行打包,並以二進制磁盤文件形式存放,目標程序也可以稱為可執行文件。
這里,我用GCC編譯器,編譯解析hello.c
- 預處理階段:-E
預處理器(cpp)主要處理根據以字符#開頭的命令,修改原始的C程序。比如hello.c中第一行的#include <stdio.h> 命令會告訴預處理其讀區系統頭文件stdio.h的內容.並把它直接插入到程序文本中。此過程,會得到以.i作為擴展名。
`$ gcc -E hello.c -o hello.i`//預編譯hello.c文件 輸出hello.i文件
hello.i文件內容都有些什么呢?
1 //hello.i 2 # 1 "hello.c" 3 # 1 "<built-in>" 1 4 # 1 "<built-in>" 3 5 # 325 "<built-in>" 3 6 # 1 "<command line>" 1 7 # 1 "<built-in>" 2 8 # 1 "hello.c" 2 9 # 1 "/usr/include/stdio.h" 1 3 4 10 # 64 "/usr/include/stdio.h" 3 4 11 # 1 "/usr/include/sys/cdefs.h" 1 3 4 12 # 533 "/usr/include/sys/cdefs.h" 3 4 13 # 1 "/usr/include/sys/_symbol_aliasing.h" 1 3 4 14 # 534 "/usr/include/sys/cdefs.h" 2 3 4 15 # 599 "/usr/include/sys/cdefs.h" 3 4 16 # 1 "/usr/include/sys/_posix_availability.h" 1 3 4 17 # 600 "/usr/include/sys/cdefs.h" 2 3 4 18 # 65 "/usr/include/stdio.h" 2 3 4 19 # 1 "/usr/include/Availability.h" 1 3 4 20 # 162 "/usr/include/Availability.h" 3 4 21 # 1 "/usr/include/AvailabilityInternal.h" 1 3 4 22 # 163 "/usr/include/Availability.h" 2 3 4 23 # 66 "/usr/include/stdio.h" 2 3 4 24 # 1 "/usr/include/_types.h" 1 3 4 25 # 27 "/usr/include/_types.h" 3 4 26 # 1 "/usr/include/sys/_types.h" 1 3 4 27 # 33 "/usr/include/sys/_types.h" 3 4 28 # 1 "/usr/include/machine/_types.h" 1 3 4 29 # 32 "/usr/include/machine/_types.h" 3 4 30 # 1 "/usr/include/i386/_types.h" 1 3 4 31 # 37 "/usr/include/i386/_types.h" 3 4 32 typedef signed char __int8_t; 33 typedef unsigned char __uint8_t; 34 typedef short __int16_t; 35 typedef unsigned short __uint16_t; 36 typedef int __int32_t; 37 typedef unsigned int __uint32_t; 38 typedef long long __int64_t; 39 typedef unsigned long long __uint64_t; 40 typedef long __darwin_intptr_t; 41 typedef unsigned int __darwin_natural_t; 42 # 70 "/usr/include/i386/_types.h" 3 4 43 typedef int __darwin_ct_rune_t;
- 編譯階段:-S
編譯器(ccl)將hello.i翻譯成文件文件hello.s文件,它包含一個匯編語言程序,匯編程序中的每條語句都以一種標准的文本格式確切地描述了一條條低級機器語言指令.所以該過程會檢查代碼規范,語法,詞法分析,具體如下圖.只有編譯成功之后,才能生成具體的匯編代碼。
$ gcc -S hello.i -o hello.s
1 //hello.s 2 .section __TEXT,__text,regular,pure_instructions 3 .macosx_version_min 10, 11 4 .globl _main 5 .align 4, 0x90 6 _main: ## @main 7 .cfi_startproc 8 ## BB#0: 9 pushq %rbp 10 Ltmp0: 11 .cfi_def_cfa_offset 16 12 Ltmp1: 13 .cfi_offset %rbp, -16 14 movq %rsp, %rbp 15 Ltmp2: 16 .cfi_def_cfa_register %rbp 17 subq $16, %rsp 18 leaq L_.str(%rip), %rdi 19 movl $0, -4(%rbp) 20 movb $0, %al 21 callq _printf 22 xorl %ecx, %ecx 23 movl %eax, -8(%rbp) ## 4-byte Spill 24 movl %ecx, %eax 25 addq $16, %rsp 26 popq %rbp 27 retq 28 .cfi_endproc 29 30 .section __TEXT,__cstring,cstring_literals 31 L_.str: ## @.str 32 .asciz "hello world!" 33 .subsections_via_symbols
- 匯編階段:-c
匯編器(as)將hello.s 文件翻譯成機器語言指令,,把這些指令打包成一種叫做可重定向目標程序的格式,並且保存在hello.o文件中.該文件是一個二進制文件,他的字節編碼是機器語言指令而不是字符,如果用編輯器打開將是一段亂碼。
- 鏈接階段:
注意,hello程序調用了printf函數,他是每個C編譯器都會提供的標准C庫的一個函數.該函數存在於一個名為printf.o的單獨的預編譯好的文件中,必須將該文件以某種方式合並到我們的hello.o的文件中。連接器(ld),就復制處理這種合並。結果就得到一個hello文件,是一個可執行文件。