Linux0.11内核源码解析-exec.c

这篇具有很好参考价值的文章主要介绍了Linux0.11内核源码解析-exec.c。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

主要实现对二进制可执行文件和shell文件的加载和执行,其中主要的函数是do_execve(),它是系统中断调用int 0x80的功能号__NR_execve()调用,是exec()函数的主要实现以下几点功能:

1.执行对参数和环境参数空间页面的初始化操作,初始化空间页面指针数组,根据执行文件名取执行对象的i节点,计算参数个数和环境变量个数,检查文件类型、执行权限

2.根据执行文件开始部分的头数据结构,对其中信息进行处理,根据被执行文件i节点读取文件头部信息,若是shell以#!开始,则分析shell程序名和其参数;根据幻数和段长度等信息判断是否可执行

3.对当前调用进程进行运行新文件前初始化操作,指向新执行文件的i节点,复位信号处理句柄,根据头结构信息设置局部描述符地址和段长;设置参数和环境参数页面指针;修改进程各执行字段内容

4.替换堆栈上原调用execve()程序的返回地址为新执行程序运行地址,运行新加载的程序

Linux0.11内核源码解析-exec.c,linux0.11内核源码,linux内核0.11

 create_tables函数用于根据给定的当前堆栈指针值p以及参数个数argc和环境变量个数envc,在新的程序堆栈中创建环境和参数变量指针表,并返回此时的堆栈指针值sp

Linux0.11内核源码解析-exec.c,linux0.11内核源码,linux内核0.11

 create_tables

用戶内存中解析环境变量和参数字符串,创建指针表,并将地址放到堆栈上,返回栈指针

/*
 * MAX_ARG_PAGES defines the number of pages allocated for arguments
 * and envelope for the new program. 32 should suffice, this gives
 * a maximum env+arg of 128kB !
 */
#define MAX_ARG_PAGES 32

/*
 * create_tables() parses the env- and arg-strings in new user
 * memory and creates the pointer tables from them, and puts their
 * addresses on the "stack", returning the new stack pointer value.
 */
static unsigned long * create_tables(char * p,int argc,int envc)
{
	unsigned long *argv,*envp;
	unsigned long * sp;
    //堆栈指针是以4字节为边界寻址
	sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
    //sp向下移动,空出环境参数占用空间个数,让环境参数指针envp指向该处
	sp -= envc+1;
	envp = sp;
    //sp向下移动,空出环境参数占用的空间个数,并让环境参数指针envp指向该处
	sp -= argc+1;
	argv = sp;

    //argc、argv、envp
	put_fs_long((unsigned long)envp,--sp);
	put_fs_long((unsigned long)argv,--sp);
	put_fs_long((unsigned long)argc,--sp);

    //argc
	while (argc-->0) {
		put_fs_long((unsigned long) p,argv++);
		while (get_fs_byte(p++)) /* nothing */ ;
	}
    //最后放置NULL
	put_fs_long(0,argv);

    //envc
	while (envc-->0) {
		put_fs_long((unsigned long) p,envp++);
		while (get_fs_byte(p++)) /* nothing */ ;
	}
    //最后放置NULL
	put_fs_long(0,envp);
	return sp;
}

count

计算参数个数

/*
 * count() counts the number of arguments/envelopes
 */
static int count(char ** argv)
{
	int i=0;
	char ** tmp;
    
	if ((tmp = argv))
        //判断为NULL就退出
		while (get_fs_long((unsigned long *) (tmp++)))
			i++;

	return i;
}

copy_string

函数从用户内存空间拷贝参数和环境字符串到内核空闲页面内存中,argc

/*
 * 'copy_string()' copies argument/envelope strings from user
 * memory to free pages in kernel mem. These are in a format ready
 * to be put directly into the top of new user memory.
 *
 * Modified by TYT, 11/24/91 to add the from_kmem argument, which specifies
 * whether the string and the string array are from user or kernel segments:
 * 
 * from_kmem     argv *        argv **
 *    0          user space    user space
 *    1          kernel space  user space
 *    2          kernel space  kernel space
 * 
 * We do this by playing games with the fs segment register.  Since it
 * it is expensive to load a segment register, we try to avoid calling
 * set_fs() unless we absolutely have to.
 */
static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
		unsigned long p, int from_kmem)
{
	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);
	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++));
        //字符串长度判断
		if (p-len < 0) {	/* this shouldn't happen - 128kB */
			set_fs(old_fs);
			return 0;
		}
		while (len) {
			--p; --tmp; --len;
            //首次复制字符串,offset复制为0
			if (--offset < 0) {
                //p指针在页内的偏移
				offset = p % PAGE_SIZE;
                //恢复fs段寄存器
				if (from_kmem==2)
					set_fs(old_fs);
                //如果pag不为NULL,说明页面在之前的拷贝过程得到分配
                //pag指针为NULL,说明该页面还没有分配内存空间
				if (!(pag = (char *) page[p/PAGE_SIZE]) &&
				    !(pag = (char *) (page[p/PAGE_SIZE] =
				      (unsigned long *) get_free_page()))) 
					return 0;
				if (from_kmem==2)
					set_fs(new_fs);

			}
			*(pag + offset) = get_fs_byte(tmp);
		}
	}
    //还原fs
	if (from_kmem==2)
		set_fs(old_fs);
	return p;
}

修改局部描述符表中的描述符基址和段限长,并将参数和环境页面放置在数据段末端

参数:text_size 执行文件头部中a_text字段给出的代码段长度值,page参数和环境空间页面指针数组

static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
	unsigned long code_limit,data_limit,code_base,data_base;
	int i;
//根据执行文件头部a_text的值,计算以页面长度为边界代码段限长,并设置数据段长度为64MB
	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;
}

execve系统中断调用函数,加载并执行子进程

函数系统中断调用int 0x80功能号__NR_execve,

参数:eip指向堆栈中调用系统中断的程序代码指针eip,

tmp:_sys_execve时返回地址

filename 被执行程序文件名

argv命令行参数指针数组

envp环境变量指针数组文章来源地址https://www.toymoban.com/news/detail-737092.html

/*
 * 'do_execve()' executes a new program.
 */
int do_execve(unsigned long * eip,long tmp,char * filename,
	char ** argv, char ** envp)
{
	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;

	if ((0xffff & eip[1]) != 0x000f)
		panic("execve called from supervisor mode");
	for (i=0 ; i<MAX_ARG_PAGES ; i++)	/* clear page-table */
		page[i]=0;
	if (!(inode=namei(filename)))		/* get executables inode */
		return -ENOENT;
	argc = count(argv);
	envc = count(envp);
	
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)) {
		/*
		 * This section does the #! interpretation.
		 * Sorta complicated, but hopefully it will work.  -TYT
		 */

		char buf[1023], *cp, *interp, *i_name, *i_arg;
		unsigned long old_fs;

		strncpy(buf, bh->b_data+2, 1022);
		brelse(bh);
		iput(inode);
		buf[1022] = '\0';
		if ((cp = strchr(buf, '\n'))) {
			*cp = '\0';
			for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
		}
		if (!cp || *cp == '\0') {
			retval = -ENOEXEC; /* No interpreter name found */
			goto exec_error1;
		}
		interp = i_name = cp;
		i_arg = 0;
		for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
 			if (*cp == '/')
				i_name = cp+1;
		}
		if (*cp) {
			*cp++ = '\0';
			i_arg = cp;
		}
		/*
		 * OK, we've parsed out the interpreter name and
		 * (optional) argument.
		 */
		if (sh_bang++ == 0) {
			p = copy_strings(envc, envp, page, p, 0);
			p = copy_strings(--argc, argv+1, page, p, 0);
		}
		/*
		 * Splice in (1) the interpreter's name for argv[0]
		 *           (2) (optional) argument to interpreter
		 *           (3) filename of shell script
		 *
		 * This is done in reverse order, because of how the
		 * user environment and arguments are stored.
		 */
		p = copy_strings(1, &filename, page, p, 1);
		argc++;
		if (i_arg) {
			p = copy_strings(1, &i_arg, page, p, 2);
			argc++;
		}
		p = copy_strings(1, &i_name, page, p, 2);
		argc++;
		if (!p) {
			retval = -ENOMEM;
			goto exec_error1;
		}
		/*
		 * OK, now restart the process with the interpreter's inode.
		 */
		old_fs = get_fs();
		set_fs(get_ds());
		if (!(inode=namei(interp))) { /* get executables inode */
			set_fs(old_fs);
			retval = -ENOENT;
			goto exec_error1;
		}
		set_fs(old_fs);
		goto restart_interp;
	}
	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;
	}
	if (N_TXTOFF(ex) != BLOCK_SIZE) {
		printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
		retval = -ENOEXEC;
		goto exec_error2;
	}
	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;
		}
	}
/* OK, This is the point of no return */
	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);
	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;
	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;
exec_error2:
	iput(inode);
exec_error1:
	for (i=0 ; i<MAX_ARG_PAGES ; i++)
		free_page(page[i]);
	return(retval);
}

到了这里,关于Linux0.11内核源码解析-exec.c的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux内核解析-linux-5.14.10-内核源码注释】内核启动kernel_init解释

    static int __ref kernel_init(void *unused) : 声明一个静态整型函数 kernel_init() ,该函数不会被其他文件访问,使用 __ref 标记表示该函数是可重定位的,并且该函数不需要任何参数。 wait_for_completion(kthreadd_done); : 等待 kthreadd 线程完成初始化, wait_for_completion() 函数会阻塞当前进程,直到

    2024年02月02日
    浏览(70)
  • 【Linux内核解析-linux-5.14.10-内核源码注释】关于Linux同步机制知识点整理

    在Linux系统中,同步机制是操作系统中非常重要的一部分,以下是一些基本要点: 什么是同步机制?同步机制是一种操作系统提供的机制,用于协调多个进程或线程之间的访问共享资源,防止出现竞态条件和死锁等问题。 Linux中常用的同步机制有哪些?Linux中常用的同步机制

    2024年02月04日
    浏览(50)
  • 【Linux0.11代码分析】04 之 head.s 启动流程

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《【Linux0.11代码分析】03 之 setup.s 启动流程》 4.《【Linux0.11代码分析】04 之 head.s 启动流程

    2024年02月03日
    浏览(38)
  • 【Linux0.11代码分析】07 之 kernel execve() 函数 实现原理

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《

    2024年02月03日
    浏览(48)
  • Linux内核源码分析 1:Linux内核体系架构和学习路线

    好久没有动笔写文章了,这段时间经历了蛮多事情的。这段时间自己写了一两个基于不同指令集的 Linux 内核, x86 和 RISC-V 。期间也去做了一些嵌入式相关的工作,研究了一下 ARM 指令集架构。 虽然今年九月份我就要申请了,具体申请 AI 方向还是机器人、嵌入式、操作系统、

    2024年02月07日
    浏览(54)
  • Linux内核源码分析 (A)常见内核面试题

    系统调用 do_fork() : copy_process() 定时中断 do_timer() 唤醒进程 wake_up_process() :进程由睡眠状态转为 RUNNING 状态 系统调用 sys_sched_yield() 改变进程的调度策略 sched_setscheduler() : 什么情况下会发生调度时机:阻塞操作、中断返回之前(系统调用返回用户空间时)、被唤醒的进程会

    2024年02月10日
    浏览(60)
  • Linux内核源码下载

    github:https://github.com/torvalds/linux linux源码官网:https://www.kernel.org/ linux源码官网:https://www.kernel.org/ 左侧不同分支分别对应,主线,稳定版,长期支持版 和不同的版本 选择要下载的分支,点击右侧的[browse] 然后点击上方的summary列表, 在最下侧就可以看到git下载链接 github下载

    2024年02月12日
    浏览(58)
  • 获取Linux内核源码

    在嵌入式平台上做Linux开发的时候,我们用的kernel都是芯片厂家移植到自家平台上的,但是最初的原生Linux内核的源码是从哪里来的呢?下面我们介绍一下怎么获取原生的Linux源码。 Linux社区的官方网站是 https://kernel.org/ ,页面如下: 可以看到当前Linux内核的主线的版本是6

    2024年02月10日
    浏览(32)
  • Linux内核源码下载方式

    Linux内核源码下载页面 页面介绍: HTTP地址(https://www.kernel.org/pub/)提供了HTTP协议的下载方式,您可以使用浏览器或wget等工具下载内核源码。 GIT地址(https://git.kernel.org/)提供了Git协议的方式,您可以使用Git客户端工具克隆内核源码仓库。 RSYNC地址(rsync://rsync.kernel.org/pub/)

    2023年04月17日
    浏览(52)
  • 1、怎么阅读linux内核源码

    : 了解C语言和操作系统的基本概念对于理解内核代码至关重要。如果对C语言和操作系统的基础知识感到陌生,可以先学习相关的教材或课程。 Linux内核非常庞大,涵盖了多个子系统和功能。因此,选择您感兴趣的子系统或功能模块,有助于更有针对性地学习和研究相关的代

    2024年02月15日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包