Linux0.11内核--加载可执行二进制文件之1.copy_strings
从现在开始就是分析最后的核心模块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 32 do_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。
Linux0.11内核--加载可执行二进制文件之1.copy_strings的更多相关文章
- Linux0.11内核--加载可执行二进制文件之3.exec
最后剩下最核心的函数do_execve了,由于这里为了简单起见我不分析shell命令的情况, /* * 'do_execve()'函数执行一个新程序. */ //// execve()系统中断调用函数 ...
- Linux0.11内核--加载可执行二进制文件之2.change_ldt
前面分析完了copy_strings函数,这里来分析另一个注意的函数change_ldt. 先来看调用处: // 根据a_text 修改局部表中描述符基址和段限长,并将参数和环境空间页面放置在数据段末 ...
- ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载,程序的动态加载和执行
★PART1:32位保护模式下内核简易模型 1. 内核的结构,功能和加载 每个内核的主引导程序都会有所不同,因为内核都会有不同的结构.有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后 ...
- JavaScript 的性能优化:加载和执行
随着 Web2.0 技术的不断推广,越来越多的应用使用 javascript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 ...
- [转]JavaScript 的性能优化:加载和执行
原文链接:http://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/index.html?ca=drs- JavaScript 的性能优化: ...
- bootm命令中地址参数,内核加载地址以及内核入口地址
bootm命令只能用来引导经过mkimage构建了镜像头的内核镜像文件以及根文件镜像,对于没有用mkimage对内核进行处理的话,那直接把内核下载到连接脚本中指定的加载地址0x30008000再运行就 ...
- Linux-0.11内核源代码分析系列:内存管理get_free_page()函数分析
Linux-0.11内存管理模块是源码中比較难以理解的部分,如今把笔者个人的理解发表 先发Linux-0.11内核内存管理get_free_page()函数分析 有时间再写其它函数或者文件的:) /* ...
- 内核加载与linux的grub
计算机系统的启动是一个复杂的过程,启动过程大致可以分为以下几个阶段: +------计算机系统启动流程----------------------------- ------------------- ...
- JavaScript的性能优化:加载和执行
随着 Web2.0 技术的不断推广,越来越多的应用使用 javascript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 ...
随机推荐
- Unity基础知识学习笔记二
1,object Instantiate(object original,Vector3 position,Quaternion rotation) 克隆原始物体,并返回克隆物体. ...
- IOS遍历网页获取网页中<img>标签中的图片url
前言: 项目中遇见一个需求遍历网页中所有的<img>标签并且去处图片的url 第一步:编写获取<img >标签的正则表达式,代码如下: -(NSArray*)getImgTag ...
- 通过监控线程状态来保证socket服务器的稳定运行
云平台中使用的socket服务器是我们自己定义一套通信协议,并通过C#实现的一个socket服务. 该服务目前是和web服务一起运行在IIS容器中,通过启动一个永不退出的新线程来监听端口. 在开发的初 ...
- MyCAT常用分片规则之分片枚举
MyCAT支持多种分片规则,下面测试的这种是分片枚举.适用场景,列值的个数是固定的,譬如省份,月份等. 在这里,需定义三个值,规则均是在rule.xml中定义. 1. tableRule 2. fun ...
- struts2学习笔记--OGNL表达式1
struts2标签库主要使用的是OGNL语言,类似于El表达式,但是强大得多,它是一种操作对象属性的表达式语言,OGNL有自己的优点: 能够访问对象的方法,如list.size(); 能够访问静态属性 ...
- 原创:微信小程序源码解说:石头剪刀布(附源码下载)
我的博客:来源链接 昨天看有个石头剪刀布的练习,就拿出来做了一下,布局的代码浪费了很多时间,果然CSS这块的还不是很熟练,下面直接上图上代码了. JS: var numAi = 0 var timer ...
- SQL Server 数据缓存
引言 SQL Server通过一些工具来监控数据,其中之一的方法就是动态管理管理视图(DMV). 常规动态服务器管理对象 dm_db_*:数据库和数据库对象 dm_exec_*:执行用户代码和关联的连 ...
- wordpress上传图片时重命名--修改插件时遇到的一些问题
wordpress是用php语言开发的博客平台,它扩展性强,容易扩展,很适合拿来做二次开发. 1,问题由来 本周五,我在浏览公司的网站(基于wordpress开发)时发现,网站首页上有两篇文章的缩略图 ...
- 微信公众账号 token 验证失败 解决办法
问题:微信公众账号 开发过程中配置 token 提示 验证失败 如下图: 点击修改配置: 填写相关url与token(自定义):点击提交,会出现 出现这种情况,主要是对相关参数不熟悉,要了解url与 ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——总结与性能分析
Sizzle引擎的主体部分已经分析完毕了,今天为这部分划一个句号. a. Sizzle解析流程总结 是时候该做一个总结了.Sizzle解析的流程已经一目了然了. 1.选择器进入Sizzle( sele ...