可执行文件的生成过程:
hello.c ----预处理---> hello.i ----编译----> hello.s -----汇编-----> hello.o -----链接----->hello -----加载---->hello进程
其中预处理器根据hello.c中的#开头的命令解析, 如将include 头文件放在此处,选择条件编译等等; 编译阶段 就是将.i 文件翻译为更低级的汇编指令; 而后这些汇编指令通过汇编器汇编为目标文件; 最后在由连接器将目标文件与库文件链接为可执行文件; 程序要运行时 再由加载器将之加载到内存运行。
对于链接的阶段又可以分为静态链接和动态链接,接下来主要讲解它们各自的特点和原理。
符号表和Linux目标文件格式ELF
首先这里先说明一下符号表和Linux目标文件格式ELF:
如代码:
//main.c
void swap(); int buf[2] = {1,2}; int main() { swap(); return 0; }
//swap.c
extern int buf[];
int *bufp0 = &buf[0];
int *bufp1;
void swap()
{
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
先将文件main.c变为汇编:
.file "main.c"
#表示全局的数据段
.globl buf
.data
.align 4
.type buf, @object
.size buf, 8
buf: .long 1 .long 2
#表示全局的代码段 .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $0, %eax call swap movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret
这里的section标记buff,main就是用来生成符号表的,.c 文件经过语法分析后变成汇编语言,再由汇编中的.text .data 表示目标文件.o 的代码段和数据段,各个段的不同内容再用符号表来表示。目标文件可以是可重定位文件或者可执行文件, 那么为什么要是elf格式呢? 这里就牵扯到可执行文件加载的问题,加载器将可执行文件加载到到内存,其实质就是解析可执行文件的elf, 将按照其格式将代码段 数据段 只读数据段 bss段等加载到虚拟地址空间。我们可以简单看一下linux 0.12 exec.c 源代码:
//a.out.h 早期的elf struct exec { unsigned long a_magic; /* Use macros N_MAGIC, etc for access */ unsigned a_text; /* length of text, in bytes */ unsigned a_data; /* length of data, in bytes */ unsigned a_bss; /* length of uninitialized data area for file, in bytes */ unsigned a_syms; /* length of symbol table data in file, in bytes */ unsigned a_entry; /* start address */ unsigned a_trsize; /* length of relocation info for text, in bytes */ unsigned a_drsize; /* length of relocation info for data, in bytes */ }; 再看exec.c 的exec函数 int do_execve(unsigned long * eip,long tmp,char * filename, char ** argv, char ** envp) { struct m_inode * inode; struct buffer_head * bh; struct exec ex;//elf 格式struct unsigned long page[MAX_ARG_PAGES]; int i,argc,envc; int e_uid, e_gid; int retval; int sh_bang = 0; unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4; if ((0xffff & eip[1]) != 0x000f) panic("execve called from supervisor mode"); for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */ page[i]=0; if (!(inode=namei(filename))) /* get executables inode */ return -ENOENT; argc = count(argv); envc = count(envp); restart_interp: if (!S_ISREG(inode->i_mode)) { /* must be regular file */ retval = -EACCES; goto exec_error2; } i = inode->i_mode; e_uid = (i & S_ISUID) ? inode->i_uid : current->euid; e_gid = (i & S_ISGID) ? inode->i_gid : current->egid; if (current->euid == inode->i_uid) i >>= 6; else if (in_group_p(inode->i_gid)) i >>= 3; if (!(i & 1) && !((inode->i_mode & 0111) && suser())) { retval = -ENOEXEC; goto exec_error2; } if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) { retval = -EACCES; goto exec_error2; } ex = *((struct exec *) bh->b_data); /* read exec-header 读取elf 信息*/ if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) { /* * This section does the #! interpretation. * Sorta complicated, but hopefully it will work. -TYT */ char buf[128], *cp, *interp, *i_name, *i_arg; unsigned long old_fs; strncpy(buf, bh->b_data+2, 127); brelse(bh); iput(inode); buf[127] = '