您的位置:首页 > 运维架构 > Linux

Linux内核设计第七周学习总结 Linux内核如何装载和启动一个可执行程序

2016-04-04 20:23 816 查看
陈巧然原创作品 转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-100002900

实验目的

使用gdb跟踪sys_execve内核函数的处理过程,分析exec*函数对应的系统调用处理过程,理解Linux内核如何装载和启动一个可执行程序。

实验过程

登陆实验楼虚拟机http://www.shiyanlou.com/courses/195

打开shell终端,执行以下命令:


cd LinuxKernel

rm -rf menu

git clone https://github.com/mengning/menu.git
cd menu

mv test_exec.c test.c

make rootfs





可以看到启动后的MenuOS已经包含了exec命令。

可以通过增加-s -S启动参数打开调试模式


qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S


打开gdb进行远程调试


gdb

file ../linux-3.18.6/vmlinux

target remote:1234





设置断点


b sys_execve

b do_execve

b do_execve_common

b exec_binprm

b search_binary_handler

b load_elf_binary

b start_thread


实验分析

通过查看源码可以看出执行exec命令是通过调用execlp函数实现的,属于库函数exec*都是execve的封装例程。



装载和启动一个可执行程序依次调用以下函数:

sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

实验总结

当linux内核或程序(例如shell)用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。

当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。

因为调用exec并不创建新进程,所以前后的进程ID并未改变。

exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段。

参考资料:《UNIX环境高级编程第二版》

学习笔记

程序编译链接过程

预处理:(.c -> .cpp)


gcc -E -o hello.cpp hello.c -m32


编译:(.cpp -> .s 汇编)


gcc -x cpp-output -S -o hello.s hello.cpp -m32


编译:(.s -> .o 二进制目标代码)


gcc -x assembler -c hello.s -o hello.o -m32


链接:(.o -> a.out)共享库


gcc -o hello hello.o -m32


静态编译:


gcc -o hello.static hello.o -m32 -static


c语言main函数格式:


int main(int argc, char *argv[])

int main(int argc, char *argv[], char *envp[])


库函数exec*都是execve的封装例程

execve函数格式:


int execve(const char * filename,char * const argv[ ],char * const envp[ ])


sys_execve -> do_execve -> do_execve_common -> exec_binprm

说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:


int execl(const char *path, const char *arg, ...);

int execv(const char *path, char *const argv[]);

int execle(const char *path, const char *arg, ..., char * const envp[]);

int execve(const char *path, char *const argv[], char *const envp[]);

int execlp(const char *file, const char *arg, ...);

int execvp(const char *file, char *const argv[]);


其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

区别一:前4个取路径名作为参数,后2个取文件名作为参数;

区别二:参数表的传递(l表示list,v表示vector);

区别三:向新程序传递环境表。

动态链接:

装载时动态链接


gcc -shared shlibexample.c -o libshlibexample.so -m32


运行时动态链接


gcc -shared dllibexample.c -o libdllibexample.so -m32


主调程序


gcc main.c -o main -L /path/to/your/dir-l shlibexample -ldl -m32

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: