简要理解源程序到可执行文件

简要理解源程序到可执行文件

程序的生命周期从一个高级C语言程序开始,这种形式能够被人读懂,却不能被机器读懂,为了在系统上运行这个程序,该源程序需要被其他程序转化为一系列低级机器语言指令,然后将这些指令按照可执行目标程序的格式打包并以二进制磁盘文件形式存储起来。

在Linux系统下,可用以下指令完成源程序到目标程序的转化:

gcc -o hello hello.c main.c
     
     
gcc 编译器驱动程序读取源文件hello.c和main.c,经过预处理、编译、汇编、链接(分别使用预处理器、编译器、汇编器、链接器,这四个程序构成了编译系统)四个步骤,将其翻译成可执行目标程序hello。如下图所示:


运行以下命令:

# gcc --help
     
     

如下图所示,分别对应上图四个阶段:


     
     
  1. -E Preprocess only; do not compile, assemble or link
  2. -S Compile only; do not assemble or link
  3. -c Compile and assemble, but do not link
  4. -o < file> Place the output into < file>

1.示例程序


     
     
  1. //main.c
  2. #include<stdio.h>
  3. void hello();
  4. int main()
  5. {
  6. hello();
  7. return 0;
  8. }
  9. //hello.c
  10. #include<stdio.h>
  11. void hello()
  12. {
  13. printf( "Hello world\n");
  14. }

2.预处理

预处理器根据源程序中以字符”#”开头的命令,修改源程序,得到另一个源程序,常以.i作为文件扩展名。修改主要包括#include、#define和条件编译三个方面。

可执行以下命令查看程序变化:


     
     
  1. # gcc -o main .i -E main .c
  2. # gcc -o hello .i -E hello .c

查看hello.i(main.i类似),可以看到该文件是增加了很多变量,比如下面所示:


     
     
  1. extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
  2. extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
  3. extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
  4. # 943 "/usr/include/stdio.h" 3 4
  5. # 2 "test.c" 2
  6. void hello()
  7. {
  8. printf( "Hello world\n");
  9. }
从上图可以看出,预处理只是对源文件进行了扩展,得到的仍然是C语言源程序。

3. 编译

编译器将经过预处理器处理得到的文本文件hello.i和main.i翻译成hello.s与main.s,其中包含了汇编语言程序,汇编语言程序以一种标准的文本格式确切描述一条低级机器语言指令。
运行以下命令进行编译:

gcc -S main.c -o main.s
     
     

查看main.s:


     
     
  1. .file "main.c"
  2. .text
  3. .globl main
  4. .type main, @function
  5. main:
  6. .LFB 0:
  7. .cfi_startproc
  8. pushq %rbp
  9. .cfi_def_cfa_offset 16
  10. .cfi_offset 6, - 16
  11. movq %rsp, %rbp
  12. .cfi_def_cfa_register 6
  13. movl $0, %eax>
  14. call hello
  15. movl $0, %eax
  16. popq %rbp
  17. .cfi_def_cfa 7, 8
  18. ret
  19. .cfi_endproc
  20. .LFE 0:
  21. .size main, .-main
  22. .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
  23. .section .note.GNU-stack, "",@progbits

4.汇编

汇编器将hello.s和main.s翻译成机器语言指令,并打包成可重定位目标程序,一般以.o为文件扩展名。可重定位目标程序是二进制文件,它的字节编码是机器语言指令而不是字符。

运行以下指令可得到重定位目标程序main.o和hello.o:

gcc -c main.s hello.s
     
     

用文本编辑器打开main.o和hello.o发现文件是乱码,因为此时已经是二进制文件。

5.链接

链接程序将main.o和hello.o以及一些其他必要的目标文件组合起来,包含各函数库的入口,创建可执行目标文件。

gcc -o hello main.o hello.o
     
     
得到可执行程序hello.

在终端运行./hello,程序加载并运行。



作者:星爷
出处:http://lxWei.github.io/posts/262.html


程序的生命周期从一个高级C语言程序开始,这种形式能够被人读懂,却不能被机器读懂,为了在系统上运行这个程序,该源程序需要被其他程序转化为一系列低级机器语言指令,然后将这些指令按照可执行目标程序的格式打包并以二进制磁盘文件形式存储起来。

在Linux系统下,可用以下指令完成源程序到目标程序的转化:

gcc -o hello hello.c main.c
  
  
gcc 编译器驱动程序读取源文件hello.c和main.c,经过预处理、编译、汇编、链接(分别使用预处理器、编译器、汇编器、链接器,这四个程序构成了编译系统)四个步骤,将其翻译成可执行目标程序hello。如下图所示:


运行以下命令:

# gcc --help
  
  

如下图所示,分别对应上图四个阶段:


  
  
  1. -E Preprocess only; do not compile, assemble or link
  2. -S Compile only; do not assemble or link
  3. -c Compile and assemble, but do not link
  4. -o < file> Place the output into < file>

1.示例程序


  
  
  1. //main.c
  2. #include<stdio.h>
  3. void hello();
  4. int main()
  5. {
  6. hello();
  7. return 0;
  8. }
  9. //hello.c
  10. #include<stdio.h>
  11. void hello()
  12. {
  13. printf( "Hello world\n");
  14. }

2.预处理

预处理器根据源程序中以字符”#”开头的命令,修改源程序,得到另一个源程序,常以.i作为文件扩展名。修改主要包括#include、#define和条件编译三个方面。

可执行以下命令查看程序变化:


  
  
  1. # gcc -o main .i -E main .c
  2. # gcc -o hello .i -E hello .c

查看hello.i(main.i类似),可以看到该文件是增加了很多变量,比如下面所示:


  
  
  1. extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
  2. extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
  3. extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
  4. # 943 "/usr/include/stdio.h" 3 4
  5. # 2 "test.c" 2
  6. void hello()
  7. {
  8. printf( "Hello world\n");
  9. }
从上图可以看出,预处理只是对源文件进行了扩展,得到的仍然是C语言源程序。

3. 编译

编译器将经过预处理器处理得到的文本文件hello.i和main.i翻译成hello.s与main.s,其中包含了汇编语言程序,汇编语言程序以一种标准的文本格式确切描述一条低级机器语言指令。
运行以下命令进行编译:

gcc -S main.c -o main.s
  
  

查看main.s:


  
  
  1. .file "main.c"
  2. .text
  3. .globl main
  4. .type main, @function
  5. main:
  6. .LFB 0:
  7. .cfi_startproc
  8. pushq %rbp
  9. .cfi_def_cfa_offset 16
  10. .cfi_offset 6, - 16
  11. movq %rsp, %rbp
  12. .cfi_def_cfa_register 6
  13. movl $0, %eax>
  14. call hello
  15. movl $0, %eax
  16. popq %rbp
  17. .cfi_def_cfa 7, 8
  18. ret
  19. .cfi_endproc
  20. .LFE 0:
  21. .size main, .-main
  22. .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
  23. .section .note.GNU-stack, "",@progbits

4.汇编

汇编器将hello.s和main.s翻译成机器语言指令,并打包成可重定位目标程序,一般以.o为文件扩展名。可重定位目标程序是二进制文件,它的字节编码是机器语言指令而不是字符。

运行以下指令可得到重定位目标程序main.o和hello.o:

gcc -c main.s hello.s
  
  

用文本编辑器打开main.o和hello.o发现文件是乱码,因为此时已经是二进制文件。

5.链接

链接程序将main.o和hello.o以及一些其他必要的目标文件组合起来,包含各函数库的入口,创建可执行目标文件。

gcc -o hello main.o hello.o
  
  
得到可执行程序hello.

在终端运行./hello,程序加载并运行。



作者:星爷
出处:http://lxWei.github.io/posts/262.html


猜你喜欢

转载自blog.csdn.net/qq_28788687/article/details/82502191