本文的内容包括:

1. 用execve系统调用加载和执行一个可执行程序的代码演示

2. 用gdb跟踪系统调用execve的执行过程

3. execve系统调用处理过程分析

一、如何用execve系统调用加载一个可执行程序

下面的代码可以展示如何用execlp函数启动一个新的进程,execlp是对系统调用execve的一层封装。

其中第19行的输出是故意加上的。执行结果如下,可以看到第19行的输出根本没有显示出来,原因就是exec系列函数会用被加载的进程替换掉原来的进程。

在Linux帮助手册中关于exec系列函数的说明也说明了这一点:

execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded. (execve函数执行成功后不会返回,而且代码段,数据段,bss段和调用进程的栈会被被加载进来的程序覆盖掉)

二、 用gdb跟踪execve系统调用的实验方法

要用gdb调试execve系统调用,首先需要在我们的menuos中添加execve系统调用的入口,程序和上面的代码差不多,只是我们的menuos中还没有ls命令,所以我们需要做另外一个可执行程序让execve系统调用来加载。代码如下所示:

其中的载入的hello程序是我们准备的一个hello world程序,他做的事情就是简单地输出一行Hello Linux Kernel!的文字。我们用静态编译的方式构造这个hello程序,然后放到我们的根文件系统的根目录下,使用的命令和执行效果如下所示:

要调试我们的exec程序,只需要重新使用qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s启动,然后用另一个中断打开gdb远程调试就可以了。不需要使用-S参数,因为我们不需要调试启动过程,只需要等系统启动之后设置execve的断点,然后执行exec命令就可以追踪到了。

我们在下面几个函数中添加断点

1. do_execve

2. do_execve_common

3. exec_binprm

实验截图:

三、 execve系统调用处理过程分析

从上面的实验可以看到,主要的处理过程都在do_execve_common() 函数中,我们来分析这个函数的代码。 为了更清楚的看到这个函数的结构,下面代码删掉了一些错误处理的部分。

1430 static int do_execve_common(struct filename *filename,

1431                                 struct user_arg_ptr argv,

1432                                 struct user_arg_ptr envp)

1433 {

1434         struct linux_binprm *bprm;  // 用于解析ELF文件的结构

1435         struct file *file;

1436         struct files_struct *displaced;

1437         int retval;

1456         current->flags &= ~PF_NPROC_EXCEEDED;  // 标记程序已被执行

1458         retval = unshare_files(&displaced);  // 拷贝当前运行进程的fd到displaced中

1463         bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);

1467         retval = prepare_bprm_creds(bprm);   // 创建一个新的凭证

1471         check_unsafe_exec(bprm);             // 必要的安全检查

1472         current->in_execve = 1;

1474         file = do_open_exec(filename);       // 打开要执行的文件

1479         sched_exec(); // 下面是Linux代码中对这个函数的解释:

// execve() is a valuable balancing opportunity, because at

// this point the task has the smallest effective memory and cache footprint.

1481         bprm->file = file;

1482         bprm->filename = bprm->interp = filename->name;

1484         retval = bprm_mm_init(bprm);       // 为ELF文件分配内存,其中的一些值还是

// 默认值,需要在后面的函数中修正

1488         bprm->argc = count(argv, MAX_ARG_STRINGS);

1492         bprm->envc = count(envp, MAX_ARG_STRINGS);

1496         retval = prepare_binprm(bprm);     // 从打开的可执行文件中读取信息,填充bprm结构

// 下面的4句是将运行参数和环境变量都拷贝到bprm结构的内存空间中

1500         retval = copy_strings_kernel(1, &bprm->filename, bprm);

1504         bprm->exec = bprm->p;

1505         retval = copy_strings(bprm->envc, envp, bprm);

1509         retval = copy_strings(bprm->argc, argv, bprm);

     // 开始执行加载到内存中的ELF文件

1513         retval = exec_binprm(bprm);

/* 执行完成,清理并返回 */

1518         current->fs->in_exec = 0;

1519         current->in_execve = 0;

1520         acct_update_integrals(current);

1521         task_numa_free(current);

1522         free_bprm(bprm);

1523         putname(filename);

1524         if (displaced)

1525                 put_files_struct(displaced);

1526         return retval;

1547 }

上面的过程中,最重要的莫过于1513行的exec_binprm()函数。下面来看exec_binprm的实现:

1405 static int exec_binprm(struct linux_binprm *bprm)

1406 {

1407         pid_t old_pid, old_vpid;

1408         int ret;

1409

1410         /* Need to fetch pid before load_binary changes it */

1411         old_pid = current->pid;

1412         rcu_read_lock();

1413         old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent))     ;

1414         rcu_read_unlock();

1415

1416         ret = search_binary_handler(bprm);

1417         if (ret >= 0) {

1418                 audit_bprm(bprm);

1419                 trace_sched_process_exec(current, old_pid, bprm);

1420                 ptrace_event(PTRACE_EVENT_EXEC, old_vpid);

1421                 proc_exec_connector(current);

1422         }

1423

1424         return ret;

1425 }

其中,需要理解的就是search_binary_handler()函数,代码如下:

1349 /*

1350  * cycle the list of binary formats handler, until one recognizes the image

1351  */

1352 int search_binary_handler(struct linux_binprm *bprm)

1353 {

1354         bool need_retry = IS_ENABLED(CONFIG_MODULES);

1355         struct linux_binfmt *fmt;

1356         int retval;

1357

1358         /* This allows 4 levels of binfmt rewrites before failing hard. */

1359         if (bprm->recursion_depth > 5)

1360                 return -ELOOP;

1361

1362         retval = security_bprm_check(bprm);   // 检查用户是否有权限运行该文件

1363         if (retval)

1364                 return retval;

1365

1366         retval = -ENOENT;

1367  retry:

1368         read_lock(&binfmt_lock);

1369         list_for_each_entry(fmt, &formats, lh) { // 尝试每一种格式的解析函数,

// 支持的格式由__register_binfmt() 函数注册进来

1370                 if (!try_module_get(fmt->module))

1371                         continue;

1372                 read_unlock(&binfmt_lock);

1373                 bprm->recursion_depth++;

1374                 retval = fmt->load_binary(bprm); // 关键步骤,调用合适格式的处理函数加载该可执行文件

// 对ELF文件来说,这个处理函数是 load_elf_binary

1375                 read_lock(&binfmt_lock);

1376                 put_binfmt(fmt);

1377                 bprm->recursion_depth--;

1378                 if (retval < 0 && !bprm->mm) {

1379                         /* we got to flush_old_exec() and failed after it */

1380                         read_unlock(&binfmt_lock);

1381                         force_sigsegv(SIGSEGV, current);

1382                         return retval;

1383                 }

1384                 if (retval != -ENOEXEC || !bprm->file) {

1385                         read_unlock(&binfmt_lock);

1386                         return retval;

1387                 }

1388         }

1389         read_unlock(&binfmt_lock);

1390

1391         if (need_retry) {

1392                 if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&

1393                     printable(bprm->buf[2]) && printable(bprm->buf[3]))

1394                         return retval;

1395                 if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) <      0)

1396                         return retval;

1397                 need_retry = false;

1398                 goto retry;

1399         }

1400

1401         return retval;

1402 }

在load_elf_binary()函数中,加载进来的可执行文件将把当前正在执行的进程的内存空间完全覆盖掉,如果可执行文件是静态链接的文件,进程的IP寄存器值将被设置为main函数的入口地址,从而开始新的进程;而如果可执行文件是动态链接的,IP的值将被设置为加载器ld的入口地址,是程序的运行由该加载器接管,ld会处理一些依赖的动态链接库相关的处理工作,使程序继续往下执行,而不管哪种执行方式,当前的进程都会被新加载进来的程序完全替换掉,这也是我们最早的那个程序中第19行的信息没有在终端上显示的原因。

四、总结

简单总结一下execve系统调用的执行过程:

1. 陷入内核

2. 加载新的可执行文件并进行可执行性检查

3. 将新的可执行文件映射到当前运行进程的进程空间中,并覆盖原来的进程数据

4. 将EIP的值设置为新的可执行程序的入口地址。如果可执行程序是静态链接的程序,或不需要其他的动态链接库,则新的入口地址就是新的可执行文件的main函数地址;如果可执行程序还需要其他的动态链接库,则入口地址是加载器ld的入口地址

5. 返回用户态,程序从新的EIP出开始继续往下执行。至此,老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。从这个角度来看,新的运行进程中已经找不到原来的对execve调用的代码了,所以execve函数的一个特别之处是他从来不会成功返回,而总是实现了一次完全的变身。

Linux内核分析(七)系统调用execve处理过程相关推荐

  1. linux内核分析——扒开系统调用的三层皮(上)

    20135125陈智威 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 系统调用 ...

  2. 跟踪分析Linux内核5.0系统调用处理过程

    跟踪分析Linux内核5.0系统调用处理过程 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.com/mengning/linuxk ...

  3. Linux内核分析 第七周 可执行程序的装载

    张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...

  4. 第七周linux内核分析

    可执行程序的装载 作者 黎静+ 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  5. Linux内核深入理解系统调用(2):vsyscall 和 vDSO 以及程序是如何运行的(execve)

    Linux内核深入理解系统调用(2) vsyscall 和 vDSO 以及程序是如何运行的(execve) rtoax 2021年3月 1. vsyscalls 和 vDSO 这是讲解 Linux 内 ...

  6. Linux内核分析 笔记七 可执行程序的装载 ——by王玥

    一.预处理.编译.链接和目标文件的格式 (一)可执行程序是怎么得来的? 1. 2.可执行文件的创建--预处理.编译和链接 shiyanlou:~/ $ cd Code                  ...

  7. 《Linux内核分析》 第八节 进程的切换和一般的执行过程

    张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第八 ...

  8. LINUX内核分析第四周——扒开系统调用的三层皮

    LINUX内核分析第四周--扒开系统调用的三层皮 李雪琦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  9. linux swi 内核sp,Linux内核分析课程8_进程调度与进程切换过程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? Linux内核课第八周作业.本文在云课堂中实验楼完成. 原创作品转载请注明出处 <Linux内核分析>MOO ...

  10. 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)

    黄胤凯   原创作品转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.视频学习 1 ...

最新文章

  1. luogu P3455 [POI2007]ZAP-Queries (莫比乌斯反演 + 整除分块)
  2. perl dancer + net::ssh2监控服务器性能
  3. 12 Django cooking与session
  4. 整理iOS9适配中出现的坑
  5. 计算机 电工学简明教程,电工学简明教程复习要点
  6. python template_python的Template使用指南
  7. HDU 6170 2017 多校训练:Two strings(DP)
  8. 最新语言表示方法XLNet
  9. javascript基础知识系列:eval()
  10. 手机热点总是正在连接服务器,电脑连接手机热点无法上网的三种解决方法
  11. 许晓斌_Maven实战(六)——Gradle,构建工具的未来?
  12. Maven学习之路(五)maven的灵活构建--属性、profile和资源过滤
  13. Go语言使用谷歌浏览器打开指定网址
  14. 范数(Norm)和谱半径(Spectral Radii)
  15. 基于PYNQ的AD采集系统
  16. Echarts 开源,免费商用图表控件使用整理
  17. 服务器主板开关电源维修,个人经验:开关电源不通电的修复
  18. 【修真院web小课堂】请描述 BFC(Block Formatting Context) 及其如何工作
  19. 工控通讯经历1:(C#)三菱FX5U-32M与上位机通讯(超详细!)
  20. 电商项目实战第六节: CSS3+HTML5+JS 设计案例【考拉海购网站】之【页底信息,网站备案信息】

热门文章

  1. 从零开始一起学习SLAM-ICP原理及应用
  2. 惯性导航原理(二)-平台式+捷联式+INS精度
  3. 小米4未显示4g连接服务器,小米4wifi连接上但打不开网页怎么办?
  4. 2022年汽车配件市场分析
  5. 《卷积网络》深度卷积网络实例
  6. 液晶显示器原理和应用
  7. 腾讯云服务器的购买、注册和登录
  8. Mini LED,显示技术的春天?!
  9. 力扣 面试题 10.11. 峰与谷
  10. 教育机构如何给视频加密防止下载和传播?