一、 预处理、编译、链接
gcc hello.c -o hello.
-
gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。
预处理 → 编译
→
汇编
→
链接
-
预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作。
gcc -E hello.c -o hello.i
编译:gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
gcc –S hello.i –o hello.s
-S:该选项只进行编译而不进行汇编,生成汇编代码。
汇编:把编译阶段生成的.s文件
转成二进制目标代码.
gcc –c hello.s –o hello.o
链接:将编译输出.o文件
链接成最终的可执行文件。
gcc hello.o –o hello
运行:若链接没有-o指明,则生成可执行文件默认为a.out
./hello
二、可执行文件
1、在windows环境下,只要双击一个.exe的文件就可以执行一个程序,这个以.exe结尾的文件就是一个可执行文件。在andriod系统下,一个.apk的文件就是一个可执行文件,在linux系统下,可执行文件在linux环境下并没有什么特殊的后缀标记,只是在生成该文件时,它的属性设置了可执行(就是‘x’),那么他就是属于可执行文件。
2、linux系统中,可执行文件的格式为elf(Executable and Linking Format)格式
ELF文件有三种类型:
- 可重定位文件 :也就是通常称的目标文件,后缀为.o。链接器将它作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件。
- 共享文件 :这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。
- 可执行文件
- 第一个是连接编辑器,可以和其他的可重定位和共享object文件来创建其他的object。
- 第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
readelf -h
typedef struct {
Elf32_Word p_type; /* 段类型 */
Elf32_Off p_offset; /* 段位置相对于文件开始处的偏移量 */
Elf32_Addr p_vaddr; /* 段在内存中的地址 */
Elf32_Addr p_paddr; /* 段的物理地址 */
Elf32_Word p_filesz; /* 段在文件中的长度 */
Elf32_Word p_memsz; /* 段在内存中的长度 */
Elf32_Word p_flags; /* 段的标记 */
Elf32_Word p_align; /* 段在内存中对齐标记 */
}Elf32_Phdr;
4、可执行程序动态链接
(1)动态链接
load_elf_binary(...)
{
...
kernel_read();//其实就是文件解析
...
//映射到进程空间 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依赖动态库的话
{
...
//装载ld的起点 #获得动态连接器的程序起点
elf_entry=load_elf_interp(...);
...
}
else //静态链接
{
...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);
}
-
- 实际上,装载过程是一个广度遍历,遍历的对象是“依赖树”。
-
主要过程是动态链接器完成、用户态完成。
(2)装载时动态链接
/*准备.so文件*/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example/*编译成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32/*使用库文件(因为已经包含了头文件所以可以直接调用函数)*/
SharedLibApi();
(3)运行时动态链接
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example/*编译成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32/*使用库文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加载进来
int (*func)(void);//声明一个函数指针
func = dlsym(handle,"DynamicalLoadingLibApi");//根据名称找到函数指针
func(); //调用已声明函数
(4)运行
$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/*将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。*/
三、可执行程序的装载
1、sys_execve内核处理过程
(1)新的可执行程序
- 一般是地址空间为0x8048000或0x8048300
(2)execve与fork
- execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。
- fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户
- execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。
(3)execve
- 执行到可执行程序 -> 陷入内核
- 构造新的可执行文件 -> 覆盖掉原可执行程序
- 返回到新的可执行程序,作为起点(也就是main函数)
- 需要构造其执行环境;
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,先函数调用参数传递,再系统调用参数传递。
(4)静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时不同
- 静态链接:elf_entry指向可执行文件的头部,一般是main函数,是新程序执行的起点。
- 动态链接:elf_entry指向ld(动态链接器)的起点,加载load_elf_interp
1、在实验楼虚拟机下,键入以下指令更新 MenuOS
cd LinuxKernel
rm menu -rf
git config --global user.name "Scott Chacon"
git config --global user.mail "schacon@gmail.com"
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs
3、以跟踪模式启动MenuOS
关闭 MenuOS,以如下指令重启 MenuOS:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
水平分割终端模拟器窗口,运行gdb,让 MenuOS 完全启动,处于接收命令状态:
cd ~/LinuxKernel
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
(gdb) c # 让 MenuOS 完全启动,处于接收命令状态
按“Ctrl +C", 让 gdb 处于 gdb 命令行模式,设置断点