exec*函数对应的系统调用处理过程

“casualet + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

exec*函数对应的系统调用会加载可执行程序到内存进行执行。本文将分析可执行程序加载的过程,包括可执行程序的个是ELF,动态链接以及静态链接相关内容,并通过gdb调试的方式展示该系统调用的执行过程。

具体的实验环境设置流程可以参考之前的系列文章。 首先我们设置一些断点,然后依然使用qemu命令运行内核,开始调试。我们输入命令exec,可以运行到第一个断点sys_execve:

在这一部,我们通过系统调用函数陷入内核,走过了int 0x80 =>sys_call_table=>中断处理函数sys_execve的流程。我们输入c,可以执行到下一个短点do_execve,这是中断处理函数执行的结果。

进入do_execve以后执行,可以进入do_execve_common函数,我们在这里也设置了断点,所以执行c以后,可以看到这样的结果:

这个函数的参数是文件名,参数以及环境变量。这些参数是在原始的函数调用时通过栈的方式调用的。在执行系统调用函数的时候,又通过寄存器的方式拷贝了这个参数。我们在do_execve_common函数

中单步执行进行跟踪,可以看到:

这个函数持续执行,可以看到进入了1474行的函数do_open_exec,这里会打开文件,获得一个file 变量。然后继续执行,我们可以看到其部分的代码:

bprm->file = file; 

....

在bprm变量中收入了我们打开的文件,以及一些相关的参数。这个bprm变量是我们在do_execve_common函数中定义的一个linux_binprm类型的结构体指针。最后在1513行会执行

retval = exec_binprm(bprm),进入一个新的函数。在这个函数里面,会继续调用ret=search_binary_handler(bprm); 这是我们的下一个断点。

在这个函数里面,有list_for_each_entry(fmt,&formats,1h),会寻找能够解析当前可执行文件的代码模块。然后继续执行retval=fmt-》load_binary(bprm),这里实际上调用了load_elf_binary函数,开始对

可执行文件进行解析。load_elf_binary的代码可以参考linux源码arch/x86/kernel/process_32.c. 在这个函数里面,处理了动态链接和静态链接的两种情况。对于动态链接,设置了elf_entry为动态链接器ld的起始地址,

对于静态链接,则是设置成main函数的入口地址。然后在后面执行start_thread(regs,elf_entry,bprm->p), 来对内核栈进行设置,其中就用到上面的elf_entry的入口地址。此外,该函数还会对用户态的栈空间进行设置。

这样就完成了程序的加载过程。

 

总结:

1.可执行程序的elf格式

  我们编译获得一个可执行程序,会被加载到内存进行执行。这个可执行程序是有一定的格式的,其中现在用的比较多的就是elf格式。对于一个可执行文件,必须保护程序运行必要的相关信息,比如说这个程序依赖那些动态链接库,程序的入口地址等,我们通过这样一种格式化的方式,保存了这些信息共系统进行解析,从而可以加载我们的代码段和数据段进入内存。关于这个格式具体细节,可以查看相关的说明文档,在linux内核中也通过load_elf_binary对这个文件进行了解析,是通过参考文档来做的。

2.程序的加载以及执行过程的关键行为分析

  我们在shell中使用./main 来运行一个程序的时候,shell其实是通过创建子进程,然后使用execve函数来完成程序的加载和执行的。execve函数会获得一些shell通过函数调用的机制传递的参数进行执行,经历上面调试过程讲解的一系列步骤:

execve=>do_execve=>do_execve_common=>(do_open_exec/exec_binprm)=>search_binary_handler=>list_for_each_entry=>load_elf_binary=>start_thread...总体来讲,就是通过构造一些结构体,首先对文件进行打开操作,并且在结构体中保

存一些必要的信息,然后根据文件的类型,使用不同的模块对文件进行解析,解析的过程中知道了这是一个动态链接的程序还是一个静态链接的程序,根据这个设置内核栈中的ip,这样在进程调度的时候,就有了合适的入口。如果是静态链接的程序,这个入口就是main

函数,所以我们在学习C语言的时候,说main函数是程序的入口,是函数调用的起点,而现在我们知道,main函数本身是操作系统通过进程的方式来调用的,调用的方式就是设置内核栈中的ip的值,通过进程调度来实现main函数的调用。在上述的过程中,内核还会创建

用户态的栈空间,这样main函数执行的时候就有栈可以用了,就可以进入函数调用的机制。 另一方面,如果是一个动态链接的程序,那 么这个入口被设置成链接器ld的入口,执行函数的时候,先进入链接程序,这个链接程序会分析用户程序以来的动态链接库,把相应的

库文件加载到内存中来,然后设置ip为main函数的入口,开始执行。

 

posted @ 2016-04-08 17:25  Casualet  阅读(2044)  评论(0编辑  收藏  举报