当前位置: 首页 > news >正文

Linux-0.11 文件系统exec.c详解

Linux-0.11 文件系统exec.c详解

模块简介

该模块实现了二进制可执行文件和shell脚本文件的加载和执行。

函数详解

create_tables

static unsigned long * create_tables(char * p,int argc,int envc)

该函数的作用是建立参数和环境变量指针表。

create_table的作用就是建立指针表去指向copy_string拷贝的字符串。

create_tables

	unsigned long *argv,*envp;unsigned long * sp;sp = (unsigned long *) (0xfffffffc & (unsigned long) p);sp -= envc+1;envp = sp;sp -= argc+1;argv = sp;put_fs_long((unsigned long)envp,--sp);put_fs_long((unsigned long)argv,--sp);put_fs_long((unsigned long)argc,--sp);while (argc-->0) {put_fs_long((unsigned long) p,argv++);while (get_fs_byte(p++)) /* nothing */ ;}put_fs_long(0,argv);while (envc-->0) {put_fs_long((unsigned long) p,envp++);while (get_fs_byte(p++)) /* nothing */ ;}put_fs_long(0,envp);return sp;

count

static int count(char ** argv)

该函数用于计算参数的个数。

argv数组的最后一项是NULL,以此作为循环终止的条件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRmQGiRv-1685200650093)(null)]

	int i=0;char ** tmp;if ((tmp = argv))while (get_fs_long((unsigned long *) (tmp++)))i++;return i;

copy_strings

static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,unsigned long p, int from_kmem)

该函数的作用是从用户拷贝命令行参数和环境字符串拷贝到内核空间。

在do_execve函数中定义了一个page数组unsigned long page[MAX_ARG_PAGES],该数组定义在内核空间中,用于存储用户态传递过来的环境参数和命令行参数。初始时,程序定义了一个指向该空间末端(128k-4)处的空间偏移量p, 当随着环境参数和命令行参数的拷贝,p指针将向前进行移动, 如下图所示:

copy_strings

在函数的内部,考虑了参数不处于用户空间的场景,使用from_kmen,为了便于理解,可以默认from_kmen=0。

 * from_kmem     argv *        argv ***    0          user space    user space*    1          kernel space  user space*    2          kernel space  kernel space

该函数在开始处,定义了一些参数,并获取处ds寄存器和fs寄存器的值。

	char *tmp, *pag=NULL;int len, offset = 0;unsigned long old_fs, new_fs;if (!p)return 0;	/* bullet-proofing */new_fs = get_ds();old_fs = get_fs();if (from_kmem==2)set_fs(new_fs);

接下来,因此传入的参数的数量argc个,因此对其进行遍历,依次将其拷贝到page数组中。

	while (argc-- > 0) {if (from_kmem == 1)set_fs(new_fs);if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))panic("argc is wrong");if (from_kmem == 1)set_fs(old_fs);len=0;		/* remember zero-padding */do {len++;} while (get_fs_byte(tmp++));//将tmp指向字符串的末端if (p-len < 0) {	/* this shouldn't happen - 128kB */set_fs(old_fs);return 0;}while (len) {//逐字节拷贝--p; --tmp; --len;if (--offset < 0) {offset = p % PAGE_SIZE;if (from_kmem==2)set_fs(old_fs);if (!(pag = (char *) page[p/PAGE_SIZE]) &&!(pag = (char *) (page[p/PAGE_SIZE] =get_free_page()))) return 0;if (from_kmem==2)set_fs(new_fs);}*(pag + offset) = get_fs_byte(tmp);}}if (from_kmem==2)set_fs(old_fs);return p;

change_ldt

static unsigned long change_ldt(unsigned long text_size,unsigned long * page)

该函数的作用是修改LDT中段基址和段限长度, 同时将参数和环境空间放置在数据段的末端。

	unsigned long code_limit,data_limit,code_base,data_base;int i;code_limit = text_size+PAGE_SIZE -1;code_limit &= 0xFFFFF000;data_limit = 0x4000000;code_base = get_base(current->ldt[1]);data_base = code_base;set_base(current->ldt[1],code_base);set_limit(current->ldt[1],code_limit);set_base(current->ldt[2],data_base);set_limit(current->ldt[2],data_limit);
/* make sure fs points to the NEW data segment */__asm__("pushl $0x17\n\tpop %%fs"::);data_base += data_limit;for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {data_base -= PAGE_SIZE;if (page[i])put_page(page[i],data_base);}return data_limit;

do_execve

int do_execve(unsigned long * eip,long tmp,char * filename,char ** argv, char ** envp)

该函数的作用是用于加载并执行其他的程序。通常会跟着fork之后调用。

第一部分是变量的定义,没啥好说的。

	struct m_inode * inode;struct buffer_head * bh;struct exec ex;unsigned long page[MAX_ARG_PAGES];int i,argc,envc;int e_uid, e_gid;int retval;int sh_bang = 0;unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;

下面这里是对变量的check。eip[1]实际上就是源程序的cs寄存器值。对于用户态的程序, TI字段为1,RPL=3,代码段的序号为1,因此cs寄存器的值为0x000f,因此如果eip[1] & 0xffff的值不是0x000f,也就意味着execve是从内核进程调用的,但是内核进程是不能被替换掉的,因此这里进行检查。

         |   段描述符索引           |TI | RPL |
0x0017 = |0 0 0 0 0 0 0 0 0 0 0 1 0| 1 | 1 1 |
0x000f = |0 0 0 0 0 0 0 0 0 0 0 0 1| 1 | 1 1 |
	if ((0xffff & eip[1]) != 0x000f)panic("execve called from supervisor mode");

接下来根据文件的路径获取i节点。

if (!(inode=namei(filename)))		/* get executables inode */return -ENOENT;

然后统计参数和环境变量的个数。

argc = count(argv);
envc = count(envp);

如果inode不是常规文件,则返回错误。

restart_interp:if (!S_ISREG(inode->i_mode)) {	/* must be regular file */retval = -EACCES;goto exec_error2;}

下面的代码用于判断进程是否有权限执行。

	i = inode->i_mode;e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;if (current->euid == inode->i_uid)i >>= 6;else if (current->egid == inode->i_gid)i >>= 3;if (!(i & 1) &&!((inode->i_mode & 0111) && suser())) {retval = -ENOEXEC;goto exec_error2;}

执行到这里说明进程有权限运行程序,接着便是去磁盘中读取程序的第一个数据块。

if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {retval = -EACCES;goto exec_error2;
}
ex = *((struct exec *) bh->b_data);	/* read exec-header */

下面这里便是判断文件是否是一个脚本文件。

if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {

此时bh块中的数据内容已经被拷贝到了ex结构体中,因此可以释放bh块。

接下来因为Linux-0.11只支持ZMAGIC格式可执行文件,因此如果格式不等于ZMAGIC将直接返回错误。

此外,如果可执行文件太大或者文件缺失不全,那么也不能运行。首先如果程序的代码段+数据段+bss段的长度超过了50M,则返回错误。 其次如果执行文件的长度小于(代码段+数据段+符号表长度+执行头)的长度,也会返回错误。

brelse(bh);
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {retval = -ENOEXEC;goto exec_error2;
}

如果执行文件代码开始处没有位于1024字节边界处,则也不能执行。

if (N_TXTOFF(ex) != BLOCK_SIZE) {printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);retval = -ENOEXEC;goto exec_error2;
}

如果sh_bang标志没有设置,则复制指定个数的命令行参数和环境字符串到参数和环境空间。如果sh_bang为true,代表是运行的脚本程序,此时命令行参数和环境字符串已经进行了复制。

	if (!sh_bang) {p = copy_strings(envc,envp,page,p,0);//拷贝环境字符串p = copy_strings(argc,argv,page,p,0);//拷贝命令行参数if (!p) {retval = -ENOMEM;goto exec_error2;}}

这里放回进程原来所需要执行的程序的i节点,并让进程executable指向新执行文件的i节点。然后复位原进程的所有信号处理句柄。再根据执行时关闭文件句柄close_on_exec位图标志,关闭指定的打开文件,并复位该标志。

if (current->executable)iput(current->executable);
current->executable = inode;
for (i=0 ; i<32 ; i++)current->sigaction[i].sa_handler = NULL;
for (i=0 ; i<NR_OPEN ; i++)if ((current->close_on_exec>>i)&1)sys_close(i);
current->close_on_exec = 0;
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
if (last_task_used_math == current)last_task_used_math = NULL;
current->used_math = 0;
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
p = (unsigned long) create_tables((char *)p,argc,envc);

接下来重新设置进程个字段的值。例如brk=a_text+a_data+a_bss

current->brk = ex.a_bss +(current->end_data = ex.a_data +(current->end_code = ex.a_text));
current->start_stack = p & 0xfffff000;
current->euid = e_uid;
current->egid = e_gid;

如果执行文件的代码段加上数据段长度不在页面的边界上,则将剩余部分置为0。

i = ex.a_text+ex.a_data;
while (i&0xfff)put_fs_byte(0,(char *) (i++));
eip[0] = ex.a_entry;		/* eip, magic happens :-) */
eip[3] = p;			/* stack pointer */
return 0;

Q & A

http://www.lryc.cn/news/90849.html

相关文章:

  • NET框架程序设计-第1章.NET框架开发平台体系架构
  • (哈希表 ) 349. 两个数组的交集 ——【Leetcode每日一题】
  • JavaScript基本语法(二)
  • ChatGPT3.5-4资源汇总,直连无梯子
  • 【Netty】使用 SSL/TLS 加密 Netty 程序(二十)
  • runway gen2
  • Day2:Windows网络编程-TCP
  • leetcode1985. 找出数组中的第 K 大整数
  • 基于深度学习的高精度野生动物检测识别系统(PyTorch+Pyside6+YOLOv5模型)
  • 从零开始 Spring Boot 35:Lombok
  • 深入解析Spring源码系列:Day 6 - Spring MVC原理
  • Cmake中message函数 如何优雅地输出
  • 人工智能基础部分20-生成对抗网络(GAN)的实现应用
  • JavaScript表单事件(上篇)
  • vb6 Webview2微软Edge Chromium内核执行JS取网页数据测速
  • 编码,Part 1:ASCII、汉字及 Unicode 标准
  • C++ Eigen库矩阵操作
  • Linux-0.11 boot目录bootsect.s详解
  • django组件552
  • 【枚举算法的Java实现及其应用】
  • linux led 驱动
  • 平面最近点对(分治算法)
  • 【基于前后端分离的博客系统】Servlet版本
  • 在线Excel绝配:SpreadJS 16.1.1+GcExcel 6.1.1 Crack
  • 一个轻量的登录鉴权工具Sa-Token 集成SpringBoot简要步骤
  • day 44 完全背包:518. 零钱兑换 II;377. 组合总和 Ⅳ
  • K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods
  • 牛客网DAY2(编程题)
  • Java经典笔试题—day14
  • 一个帮助写autoprefixer配置的网站