- 1.背景介绍
- 1.1 C语言做了什么
- 1.2 什么是c语言标准库,c标准库、应用程序与内核的关系?
- 1.3.c库是保存何处,以及如何被用户调用?
- 2.应用程序的执行过程
- 3.通过汇编举例说明main的调用
首先我们的问题是,linux用户编程下main函数是第一个被执行的函数吗?
先给出结论,不是的,不是的,第一个不是main函数。
简单的main函数其实很复杂,我们只是站巨人的肩上(这个巨人就是内核、glibc、系统调用)。
在linux下进行c语言编程,我们首先明确几个概念,c语言、编译链、标准c库、应用程序、linux内核几个的关系。
1.1 C语言做了什么规定了c语言的语法,如关键字、运算符、表达式和函数等,它本身并不提供任何库函数供用户使用。
1.2 什么是c语言标准库,c标准库、应用程序与内核的关系?c库(linux下即为glibc库)实现了c语言编程所需要的常用函数,如:
stdio.h头文件的printf函数;
malloc.h头文件的malloc free函数 、
以及open read write等标准c库函数,调用内核函数进行实现(采用系统调用的方式),当然你也可以自己写c库,不过一般没什么必要;
c库对上承接应用程序,供应用程序调用,对下通过系统调用实现对内核的调用。例如malloc的实现。
我们知道在应用程序中调用malloc()函数即可以实现堆内存的申请,malloc并不是内核完成的,而是c标准库帮我们完成的,c标准库然后通过调用内核函数(通过系统调用)。
c库是通过在用户程序下使用#include即可以实现。
我们只包含了c库的头文件,那么c库的函数是怎么保存的以及如何与用户程序共同生成可执行文件的,要想清楚这个问题,就需要了解编译链的概念;
编译链或者交叉编译链是如何生成的,我们看到的gcc、arm-linux-gcc等编译链其是已经集成了预编译、编译、汇编、链接等过程,实际的实现主要由gcc、binutils、glibc库组合而成。
gcc主要实现预编译与编译阶段,gcc官网可以进行gcc源码下载https://gcc.gnu.org/
binutils源码下载,binutils主要实现汇编、链接等过程https://ftp.gnu.org/gnu/binutils/
glibc为linux下c的标准库,源码下载地址: http://www.gnu.org/software/libc/
glibc最终便保存在编译链中,形式为静态库和动态库 ,了解了c库、以及编译的概念后,我们再来分析main函数的执行过程。
对于用户来讲看到的是main函数,但是有没有考虑过main是执行时候的第一个函数入口吗?当然不是。函数的执行过程为: _start->_libc_start_main->main->exit
在我们执行a.out的时候,系统创建进程后首先执行的是_start,此函数实际位于
/lib/ld-linux.so.2(文件名可能会因版本平台有差异)库中,源码为glibc中的代码。
因此我们应用程序的执行过程为:
1.shell使用fork通过系统调用创建进程;
2.调用glibc库(静态库或者动态库)的_start函数,此库在制作编译链或者交叉编译链的时候已经生成好,在gcc编译的时候进行调用。
3.调用函数为 _start->_libc_start_main->mian->exit。
//test.c #includeint main(int argc,char *argv[]) { printf("%s hello word!!!n",__FUNCTION__); return 0; }
可以通过binutils工具进行分析ELF可执行文件的格式、反汇编等;
编译代码:gcc test.c
查看ELF执行文件格式:readelf -a a.out
可以看见入口地址为:Entry point address: 0x400470
//readelf -a a.out 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: 0x400470 Start of program headers: 64 (bytes into file) Start of section headers: 6712 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 28
3.查看反汇编代码
可以看见执行的入口地址为_start函数,这也进一步验证了我们思路;
//objdump -S a.out 0000000000400470 <_start>: 400470: 31 ed xor %ebp,%ebp 400472: 49 89 d1 mov %rdx,%r9 400475: 5e pop %rsi 400476: 48 89 e2 mov %rsp,%rdx 400479: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40047d: 50 push %rax 40047e: 54 push %rsp 40047f: 49 c7 c0 30 06 40 00 mov $0x400630,%r8 400486: 48 c7 c1 c0 05 40 00 mov $0x4005c0,%rcx 40048d: 48 c7 c7 66 05 40 00 mov $0x400566,%rdi 400494: e8 a7 ff ff ff callq 400440 <__libc_start_main@plt> 400499: f4 hlt 40049a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)



