从现在开始就是分析最后的核心模块exec.c了,分析完这个文件后,就会和之前的所有分析形成一个环路,从创建进程、加载进程程序到进程调度、内存管理。

exec.c的核心do_execve函数很长,而且用到了很多其他的函数,copy_strings就是其中一个,我们这里就先来分析这个函数。

首先看调用处,在main.c中:

static char *argv_rc[] =
{
"/bin/sh", NULL};     // 调用执行程序时参数的字符串数组。
static char *envp_rc[] =
{
"HOME=/", NULL};     // 调用执行程序时的环境字符串数组。void init(void){
...execve ("/bin/sh", argv_rc, envp_rc);  // 替换成/bin/sh 程序并执行。
...
}

再看exec.c中:

/*
* MAX_ARG_PAGES 定义了新程序分配给参数和环境变量使用的内存最大页数。
* 32 页内存应该足够了,这使得环境和参数(env+arg)空间的总合达到128kB!
*/
#define MAX_ARG_PAGES 32do_execve (unsigned long *eip, long tmp, char *filename,char **argv, char **envp)
{unsigned long page[MAX_ARG_PAGES]; // 参数和环境字符串空间的页面指针数组。int i, argc, envc;// 参数和环境字符串空间中的偏移指针,初始化为指向该空间的最后一个长字处。unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4;
...// 计算参数个数和环境变量个数。argc = count (argv);envc = count (envp);// 若sh_bang 标志没有设置,则设置它,并复制指定个数的环境变量串和参数串到参数和环境空间中。if (sh_bang++ == 0){p = copy_strings (envc, envp, page, p, 0);p = copy_strings (--argc, argv + 1, page, p, 0);}
...
}

mm.h:

#define PAGE_SIZE 4096       // 定义内存页面的大小(字节数)。

exec.c和segment.h放在一起:

/*
* count()函数计算命令行参数/环境变量的个数。
*/计算参数个数。
// 参数:argv - 参数指针数组,最后一个指针项是NULL。
// 返回:参数个数。
static int
count (char **argv)
{int i = 0;char **tmp;if (tmp = argv)while (get_fs_long ((unsigned long *) (tmp++)))i++;return i;
}读取fs 段中指定地址处的长字(4 字节)。
// 参数:addr - 指定的内存地址。
// %0 - (返回的长字_v);%1 - (内存地址addr)。
// 返回:返回内存fs:[addr]处的长字。
extern inline unsigned long
get_fs_long (const unsigned long *addr)
{unsigned long _v;__asm__ ("movl %%fs:%1,%0": "=r" (_v):"m" (*addr));return _v;
}

先分析获取参数/环境变量的个数,首先声明了两个指针数组argv_rc和envp_rc并传入execve。

int* a[4]     指针数组

表示:数组a中的元素都为int型指针

注意do_execve的形参为char **argv, char **envp,指针的指针。所以也就是说在count函数中,tmp++是指针数组argv_rc的其中的元素的地址,那么在get_fs_long中*addr指的是argv_rc的元素的值(也就是"/bin/sh"这个char类型指针),因为使用的是fs:%1而不是fs:[%1],因此最终_v得到的是char类型的完整地址。所以count就是根据是不是有地址值来判断数量。

/*
* 'copy_string()'函数从用户内存空间拷贝参数和环境字符串到内核空闲页面内存中。
* 这些已具有直接放到新用户内存中的格式。
*
* 由TYT(Tytso)于1991.12.24 日修改,增加了from_kmem 参数,该参数指明了字符串或
* 字符串数组是来自用户段还是内核段。
*
* from_kmem        argv *      argv **
*          0                用户空间      用户空间
*          1                 内核空间      用户空间
*          2                 内核空间      内核空间
*
* 我们是通过巧妙处理fs 段寄存器来操作的。由于加载一个段寄存器代价太大,所以
* 我们尽量避免调用set_fs(),除非实在必要。
*/复制指定个数的参数字符串到参数和环境空间。
// 参数:argc - 欲添加的参数个数;argv - 参数指针数组;page - 参数和环境空间页面指针数组。
// p -在参数表空间中的偏移指针,始终指向已复制串的头部;from_kmem - 字符串来源标志。
// 在do_execve()函数中,p 初始化为指向参数表(128kB)空间的最后一个长字处,参数字符串
// 是以堆栈操作方式逆向往其中复制存放的,因此p 指针会始终指向参数字符串的头部。
// 返回:参数和环境空间当前头部指针。
static unsigned long
copy_strings (int argc, char **argv, unsigned long *page,unsigned long p, int from_kmem)
{char *tmp, *pag;int len, offset = 0;unsigned long old_fs, new_fs;if (!p)return 0;         /* bullet-proofing *//* 偏移指针验证 */
// 取ds 寄存器值到new_fs,并保存原fs 寄存器值到old_fs。new_fs = get_ds ();old_fs = get_fs ();
// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)。if (from_kmem == 2)set_fs (new_fs);
// 循环处理各个参数,从最后一个参数逆向开始复制,复制到指定偏移地址处。while (argc-- > 0){
// 如果字符串在用户空间而字符串数组在内核空间,则设置fs 段寄存器指向内核数据段(ds)。if (from_kmem == 1)set_fs (new_fs);
// 从最后一个参数开始逆向操作,取fs 段中最后一参数指针到tmp,如果为空,则出错死机。if (!(tmp = (char *) get_fs_long (((unsigned long *) argv) + argc)))panic ("argc is wrong");
// 如果字符串在用户空间而字符串数组在内核空间,则恢复fs 段寄存器原值。if (from_kmem == 1)set_fs (old_fs);
// 计算该参数字符串长度len,并使tmp 指向该参数字符串末端。len = 0;          /* remember zero-padding */do{          /* 我们知道串是以NULL 字节结尾的 */len++;}while (get_fs_byte (tmp++));
// 如果该字符串长度超过此时参数和环境空间中还剩余的空闲长度,则恢复fs 段寄存器并返回0。if (p - len < 0){          /* this shouldn't happen - 128kB */set_fs (old_fs);    /* 不会发生-因为有128kB 的空间 */return 0;}
// 复制fs 段中当前指定的参数字符串,是从该字符串尾逆向开始复制。while (len){--p;--tmp;--len;
// 函数刚开始执行时,偏移变量offset 被初始化为0,因此若offset-1<0,说明是首次复制字符串,
// 则令其等于p 指针在页面内的偏移值,并申请空闲页面。if (--offset < 0){offset = p % PAGE_SIZE;
// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值。if (from_kmem == 2)set_fs (old_fs);
// 如果当前偏移值p 所在的串空间页面指针数组项page[p/PAGE_SIZE]==0,表示相应页面还不存在,
// 则需申请新的内存空闲页面,将该页面指针填入指针数组,并且也使pag 指向该新页面,若申请不
// 到空闲页面则返回0。if (!(pag = (char *) page[p / PAGE_SIZE]) &&!(pag = (char *) page[p / PAGE_SIZE] =(unsigned long *) get_free_page ()))return 0;
// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)。if (from_kmem == 2)set_fs (new_fs);}
// 从fs 段中复制参数字符串中一字节到pag+offset 处。*(pag + offset) = get_fs_byte (tmp);}}
// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值。if (from_kmem == 2)set_fs (old_fs);
// 最后,返回参数和环境空间中已复制参数信息的头部偏移值。return p;
}

首先p是指向参数和环境空间的最后一个长字处,逻辑地址,如下图所示

首先从最后一个参数开始逆向操作,取fs段中最后一个参数指针到tmp。

然后取字符串长度,注意get_fs_byte的*addr为字符指针指向的值,也就是_v得到的是字符值一个字节。

最后是从字符串尾部开始逆向复制,注意page数组不是用来映射的,而是保存内存页的地址。而offset是每次循环都会变化。

最终返回p。

转载于:https://www.cnblogs.com/joey-hua/p/5638306.html

Linux0.11内核--加载可执行二进制文件之1.copy_strings相关推荐

  1. ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载,程序的动态加载和执行...

    ★PART1:32位保护模式下内核简易模型 1. 内核的结构,功能和加载 每个内核的主引导程序都会有所不同,因为内核都会有不同的结构.有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后 ...

  2. Linux0.11内核源码解析-setup.s

    学习资料: Linux内核完全注释 操作系统真像还原 极客时间-Linux内核源码趣读 Linux0.11内核源码 ->setup程序将system模块从0x10000~0x8ffff整块向下移 ...

  3. 一站式linux0.11内核head.s代码段图表详解

    阅读本文章需要的基础: 计算机组成原理:针对8086,80386CPU架构的计算机硬件体系要有清楚的认知,我们都知道操作系统是用来管理硬件的,那我们就要对本版本的操作系统所依赖的硬件体系有系统的了解, ...

  4. Linux0.11内核源码解析-bootsect.s

    学习资料: Linux内核完全注释 操作系统真像还原 极客时间-Linux内核源码趣读 Linux0.11内核源码 ->上电 ->80x86架构CPU会自动进入实模式 ->从地址0x ...

  5. LINUX0.11内核阅读笔记

    我是通过阅读赵炯老师编的厚厚的linux内核完全剖析看完LINUX0.11的代码,不得不发自内心的说Linus真的是个天才.虽然我觉得很多OS设计的思想他是从UNIX学来的,但是他自己很周全很漂亮很巧 ...

  6. Windows内核加载器概念学习

    最近看ReactOS源码分析相关,看到内核加载器概念相关的:原文如下: ReactOS源码分析--内核加载器(一) 计算机BIOS读取硬盘第一个扇区的数据到内存0x7C00位置,将控制权交给主引导记录 ...

  7. 程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24

    程序的加载和执行(四)--<x86汇编语言:从实模式到保护模式>读书笔记24 通过本文能学到什么? 怎样跳转到用户程序 用户程序通过调用内核过程完成自己的功能 怎样从用户程序返回到内核 接 ...

  8. 程序的加载和执行(一)——《x86汇编语言:从实模式到保护模式》读书笔记21

    程序的加载和执行(一) 本文及之后的几篇博文是原书第13章的学习笔记. 本章主要是学习一个例子,对应的代码分为3个文件: ;代码清单13-1;文件名:c13_mbr.asm;文件说明:硬盘主引导扇区代 ...

  9. X86汇编语言从实模式到保护模式13:保护模式程序的动态加载和执行

    目录 1. 引入保护模式对程序加载与执行的影响 1.1 对应用程序的影响 1.2 对操作系统的影响 1.3 本章程序总体结构 2. MBR加载内核过程分析 2.1 内核头部段分析 2.1.1 内核总长 ...

最新文章

  1. 【青少年编程】【三级】 魔术表演“开花”
  2. Keras vs PyTorch:谁是第一深度学习框架?
  3. 12无法使用otg_ios设备该如何选择U盘,以及U盘日常使用技巧
  4. 软件工程的七条基本原理:经典
  5. 校验值的计算----移位算法
  6. jquery获取select选择的显示值
  7. C语言和C++语言在语法上面的部分区别
  8. 随想录(linux下的pv操作)
  9. Tcl/tk glob nocomplain source
  10. 一个校园互联网实验室的纳新试题(我的服务端开发笔试题)
  11. 航空公司客户价值数据的分析
  12. 2018北京网络赛B题 Tomb Raider
  13. android来电自定义显示图片,安卓手机如何自定义设置通话背景
  14. 关于Qt bindValue函数出错问题
  15. BUUCTF WEB Easy Calc
  16. 限制p元素之显示2行文字,同时出现省略号。
  17. Android开发-Notification通知栏通知最基础运用
  18. 2004-6-6 0:03:43 死得其所
  19. 百度奖学金获得者徐立恒:执着创造价值
  20. 7.项目成本管理+信息系统项目管理+野马合集

热门文章

  1. 2.12日递推专题第一题
  2. 用python阐释工作量证明(proof of work)
  3. Lis(bzoj 3532)
  4. selenium IE 浏览器驱动下载地方
  5. C#-修改图书借阅管理系统-错误与SQL server 2008错误、复制数据库
  6. Farbic区块链PHP SDK
  7. springCloud分布式事务实战(三)分布式事务处理器的编译和运行之注册中心编写与测试...
  8. CentOs7安装apache以及遇到的问题
  9. 利用Zabbix ODBC monitoring监控MySQL
  10. iOS 文字转化成图片