实验七:Linux 内核如何装载和启动一个可执行程序 实验要求
理解编译链接的过程和 ELF 可执行文件格式 编程使用 exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式 使用 gdb 跟踪分析一个 execve 系统调用内核处理函数 sys_execve ,验证对 Linux 系统加载可执行程序所需处理过程的理解 特别关注新的可执行程序是从哪里开始执行的?为什么 execve 系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序 execve 系统调用返回时会有什么不同? 根据本周所学知识分析 exec* 函数对应的系统调用处理过程 实验基础知识总结
ELF文件:
ELF(Excutable and Linking Format)是一个文件格式的标准。通过readelf-h hello查看可执行文件hello的头部(-a查看全部信息,-h只查看头部信息),头部里面注明了目标文件类型ELF32。Entry point address是程序入口,地址为0x400440 ELF文件的三种类型:
可重定位文件:属于中间文件,需要继续处理。由编译器和汇编器创建。一个源代码会生成一个可重定位文件。用来和其他目标文件一起来创建一个可执行文件、静态库文件或者共享目标文件。可重定位文件后缀为.o ,最后所有.o文件会链接为一个文件 可执行文件:由多个可重定位文件结合生成,完成了所有重定位工作和符号解析的文件。文件中保存着一个用来执行的程序 共享目标文件:共享库,是指被可执行文件或其他库文件使用的目标文件。其后缀为.so ELF文件的作用:
ELF文件参与程序链接(建立一个程序)和程序的执行(运行一个文件) 如果用于编译和链接(可重定位文件),则编译器和链接器将把elf文件看作是节头表描述的节的集合,程序头表可选 如果用于加载执行(可执行文件),则加载器则将把elf文件看作是程序头表描述的段的集合,一个段可能包含多个节,节头表可选 如果是共享文件,则两者都含有 实验过程
首先把menu文件夹初始化一下,然后把test_exec.c改名为test.c文件,然后make一下 然后进入到menuos中,用help命令能看到exec 测试完毕后让menuos stop来方便调试 依次打开gdb,初始化,并且打上断点 然后退出gdb调试界面,使用readelf -h hello命令查看ELF头 能看到魔数、类别、数据等ELF头详细信息 实验分析
int do_execve ( struct filename * filename,
const char __user * const __user * __argv,
const char __user * const __user * __envp)
{
return do_execve_common ( filename, argv, envp) ;
}
static int do_execve_common ( struct filename * filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
sched_exec ( ) ;
retval = prepare_binprm ( bprm) ;
retval = copy_strings_kernel ( 1 , & bprm-> filename, bprm) ;
retval = copy_strings ( bprm-> envc, envp, bprm) ;
retval = copy_strings ( bprm-> argc, argv, bprm) ;
retval = exec_binprm ( bprm) ;
}
static int exec_binprm ( struct linux_binprm * bprm)
{
ret = search_binary_handler ( bprm) ;
return ret;
}
由以上代码可知,do_ execve调用了do_ execve_ common,而do_ execve_ common又主要依靠了exec_ binprm,在exec_ binprm中又有一个至关重要的函数,叫做search_binary_ handler。这就是sys_execve的内部处理过程 实验总结
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件,如果不是可以执行的文件,那么就解释成为一个shell文件,shell执行。当Linux内核或程序使用fork函数创建子进程后,子进程往往要调用一种exec函数(exec家族的一种)以执行另一个程序;在调用一种exec函数时,该进程执行的程序完全被替换为新程序,而新程序则从其main函数处开始执行,因为调用exec函数并不创建新进程,所以前后的进程ID并未改变,或者说exec函数只是用了一个全新的程序替换了当前进程的正文、数据段和堆栈段 本次实验较为容易,操作过程中未遇到问题