曹朋辉
原创作品转载请注明出处
《Linux内核分析》MOOC课程
可执行文件的创建——预处理、编译和链接
shiyanlou:~/ $ cd Code [9:27:05]
shiyanlou:Code/ $ vi hello.c [9:27:14]
//预处理 -E
shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32 [9:34:55]
shiyanlou:Code/ $ vi hello.cpp [9:35:04]
//-x 指定文件类型
//将预处理的文件编译为汇编文件 -S
shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32 [9:35:21]
shiyanlou:Code/ $ vi hello.s [9:35:28]
//将汇编文件编译为目标文件(二进制的)
shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32 [9:35:58]
shiyanlou:Code/ $ vi hello.o [9:38:44]
//链接 hello为ELF格式的文件 以上语句调用共享库链接
shiyanlou:Code/ $ gcc -o hello hello.o -m32 [9:39:37]
shiyanlou:Code/ $ vi hello [9:39:44]
//调用静态库链接
shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static [9:40:21]
shiyanlou:Code/ $ ls -l [9:41:13]
-rwxrwxr-x 1 shiyanlou shiyanlou 7292 3\u6708 23 09:39 hello
-rw-rw-r-- 1 shiyanlou shiyanlou 64 3\u6708 23 09:30 hello.c
-rw-rw-r-- 1 shiyanlou shiyanlou 17302 3\u6708 23 09:35 hello.cpp
-rw-rw-r-- 1 shiyanlou shiyanlou 1020 3\u6708 23 09:38 hello.o
-rw-rw-r-- 1 shiyanlou shiyanlou 470 3\u6708 23 09:35 hello.s
-rwxrwxr-x 1 shiyanlou shiyanlou 733254 3\u6708 23 09:41 hello.static
静态链接和动态链接是怎么回事?
可执行文件的内部是怎样的?
ABI和目标文件什么关系?
命令行参数和环境变量是如何保存和传递的?
命令行参数和环境变量是如何进入新程序的堆栈的?
![Upload Paste_Image.png failed. Please try again.]
目标文件及链接
ELF目标文件格式
ELF文件格式 -- (中文翻译版)
PE格式大多用在wondows上
ELF格式大多用在Linux上
ELF (Excutable and Linkble Format)
.o 可重定位
a.out
.so
查看ELF文件的头部
shiyanlou:Code/ $ readelf -h hello
查看该ELF文件依赖的共享库
shiyanlou:sharelib/ $ ldd main [21:25:56]
linux-gate.so.1 => (0xf774e000) # 这个是vdso - virtual DSO:dynamically shared object,并不存在这个共享库文件,它是内核的一部分,为了解决libc与新版本内核的系统调用不同步的问题,linux-gate.so.1里封装的系统调用与内核支持的系统调用完全匹配,因为它就是内核的一部分嘛。而libc里封装的系统调用与内核并不完全一致,因为它们各自都在版本更新。
libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)
libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)
libc.so.6 => /lib32/libc.so.6 (0xf7588000)
/lib/ld-linux.so.2 (0xf774f000)
shiyanlou:sharelib/ $ ldd /lib32/libc.so.6 [21:37:00]
/lib/ld-linux.so.2 (0xf779e000)
linux-gate.so.1 => (0xf779d000)
# readelf -d 也可以看依赖的so文件
shiyanlou:sharelib/ $ readelf -d main [21:28:04]
Dynamic section at offset 0xf04 contains 26 entries:
0x00000001 (NEEDED) 共享库:[libshlibexample.so]
0x00000001 (NEEDED) 共享库:[libdl.so.2]
0x00000001 (NEEDED) 共享库:[libc.so.6]
0x0000000c (INIT) 0x80484f0
0x0000000d (FINI) 0x8048804
0x00000019 (INIT_ARRAY) 0x8049ef8
命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
$ ls -l /usr/bin 列出/usr/bin下的目录信息
Shell本身不限制命令行参数的个数,�命令行参数的个数受限于命令自身
例如,int main(int argc, char *argv[])
又如, int main(int argc, char *argv[], char envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
库函数exec都是execve的封装例程
#include
#include
#include
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid<0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid==0)
{
/* child process */
execlp("/bin/ls","ls",NULL);
}
else
{
/* parent process */
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!");
exit(0);
}
}
命令行参数和环境串都放在用户态堆栈中
在linux中一个程序是如何加载运行的
在shell中输入一个程序的名称时,shell会先fork一个子进程,然后在子进程中调用execlp函数来拉起我们执行的程序,
exec系统调用
首先,exec会调用sys_execve,然后调用do_execve,再调用do_execve_common,这个函数会把函数参数和系统环境传进来进行相应的处理。然后调用exec_binprm来执行相应的程序。而exec_binprm又会调用search_binary_handler,这个函数会调用各种不同的格式来识别相应的文件,直到识别为止,比如linux中可执行文件为ELF,它就会识别出elf文件。
在sys_execve处设置断点
retval = fmt->load_binary(bprm)把对应的文件以对应的格式加载到内存里面。由于Linux是elf格式,故会执行对应的load_elf_binary,然后在这个函数里面有一个函数start_thread,这个函数会复制内核堆栈,同时会设置新的进程的执行位置,即会设置新的eip,使eip指向新程序的入口位置。