Linux内核分析课程7_execve()函数对应的系统调用处理过程

Linux内核课第七周作业。本文在云课堂中实验楼完成。 唐国泽 原创作品转载请注明出处.
《Linux内核分析》MOOC课程

昔者庄周梦为蝴蝶,栩栩然蝴蝶也,自喻适志与,不知周也。俄然觉,则蘧蘧然周也。不知周之梦为蝴蝶与,蝴蝶之梦为周与?周与蝴蝶,则必有分矣。此之谓物化。(《庄子·齐物论》)

在我们的操作系统中,也有如此浪漫情怀的庄生梦蝶—–exec()函数族.

庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)

一. exec函数族

(一) 介绍

fork()函数, 相当于是创建了一个新的进程, 但该子进程复制的确实父进程的内容, 如果让其执行下去,那么也是执行和父进程相同的内容呢, 但实际中,我们执行的是新的任务, 那么在这里是如何实现的呢?

exec函数族就实现了在一个进程中启动另外一个程序的方法. 它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。

那什么时候使用exec函数呢? 除了我们上面提到的fork()之后调用exec()函数来执行一个新进程外,还有当进程认为自己继续执行下去也没有什么实际的工作的时候,就可以调用exec函数来庄周梦蝶,化成蝶了.

(二) 函数族具体实现

在linux下,有六个exec开头的函数, 来实现:

这6个函数在函数名和使用语法的规则上都有细微的区别,下面就从可执行文件查找方式、参数传递方式和环境变量这几个方面进行比较。

  • 查找方式:表1中的前4个函数的查找方式都是完整的文件目录路径,而最后两个函数(也就是以 p 结尾的两个函数)可以只给出文件名,系统就会自动按照环境变量“$PATH” 所指定的路径进行查找。

  • 参数传递方式:exec函数族的参数传递有两种:一种是逐个列举的方式,而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第5位字母来区分的,字母为 “l”(list)的表示逐个列举参数的方式,其语法为:
    `const char *arg;字母为“v”(vector)的表示将所有参数整体构造指针数组传递,其语法为 char *const argv[]‘``。
    这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括该可执行程序命令本身)。要注意的是,这些参数必须以NULL结束。

  • 环境变量: exec函数族可以默认系统的环境变量,也可以传入指定的环境变量。这里以 “e”(environment)结尾的两个函数 execle()和 execve()就可以在 envp[]中指定当前进程所使用的环境变量。
    表2再对这6个函数中的函数名和对应语法做了一个小结,主要指出了函数名中每一位对应所表明的含义,以此表加以记住这6个函数。

   事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。在使用exec函数族时,一定要加上错误判断语句。exec 很容易执行失败,其中最常见的原因有:

  1. 找不到文件或路径,此时 errno 被设置为 ENOENT。
  2. 数组argv 和envp 忘记用NULL结束,此时,errno被设置为 EFAUL。
  3. 没有对应可执行文件的运行权限,此时 errno 被设置为EACCES。

二.实验分析

实践来检验理论, 才能让自己的知识学习的更加牢固。

进入gdb调试,设置断点:

  主要设置了以下几个断点。

b sys_execve
b do_execve
b do_open_exev
b do search_binary_handler
b load_elf_binary
b start_thread
b init_elf_binfmt

下面就主要分析这几个断点处的函数功能实现了.

1.首先分析函数实现

在函数中,我们可明显的看到,在fork执行完成之后,我们通过execlp()加载了可执行程序hello.

在这里调用的是execlp(), 最终调用的也是execve()这个系统调用。

   清晰的看到, 系统调用之后执行了do_execve()

2.do_execve()函数分析

1)在do_execve中限设置了相应的参数和环境变量,然后调用了do_execve_common()函数

2)do_execve_common()函数介绍

在do_execve_common()函数中,先打开对应文件,在这里是hello

接着将文件名,环境变量,命令行参数拷贝到新分配的页面中:

最后执行 exec_binprm来执行该可执行文件格式的处理函数:

接着详细分析exec_binprm中函数的执行过程, 分析如何来加载elf文件格式的.

   在该函数中, 可以看到调用了search_binary_handler(bprm)函数,该函数寻找符合文件格式对应的解析模块.
   其中的linux_binfmt *fmt结构体为:

   我们这里调用的是hello可执行文件,为elf格式,所有最后查找后调用为:

对应elf格式查找可得:

   对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读

对应elf文件的格式为:

整个ELF映像就是由文件头、区段头表、程序头表、一定数量的区段、以及一定数量的部构成,而ELF映像的装入/启动过程,则就是在各种头部信息的指引下将某些部或区段装入一个进程的用户空间,并为其运行做好准备(例如装入所需的共享库),最后(在目标进程首次受调度运行时)让CPU进入其程序入口的过程。接着是对elf_bss 、elf_brk、start_code、end_code等等变量的初始化。这些变量分别纪录着当前(到此刻为止)目标映像的bss段、代码段、数据段、以及动态分配“堆” 在用户空间的位置。除start_code的初始值为0xffffffff外,其余均为0。随着映像内容的装入,这些变量也会逐步得到调整。

load_elf_binary函数的作用就是读入了程序头表,并对start_code等变量进行初始化.

在load_elf_binary的最后调用

在这里就把新程序的ip和sp存入堆栈,覆盖掉了之前的ip,sp,之后,子进程返回的话,就从hello中的main开始执行了.

3.函数执行流程示意图:

execlp->hello
call *sys_execve
........do_execve
................. do_execve_common
......................... exec_binprm
...................................search_binary_handler(bprm)
.........................................linux_binfmt= elf_format
..............................................elf_format-> load_elf_binary
..............................................load_elf_binary
.....................................................start_thread
ret
Terry Tang
Terry Tang
Software Development Engineer

My research interests include distributed robotics, mobile computing and programmable matter.

comments powered by Disqus

Related