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日
    浏览(53)
  • 【Linux内核解析-linux-5.14.10-内核源码注释】关于Linux同步机制知识点整理

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

    2024年02月04日
    浏览(37)
  • 【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日
    浏览(29)
  • 【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日
    浏览(37)
  • Linux内核源码分析 1:Linux内核体系架构和学习路线

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

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

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

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

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

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

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

    2024年02月10日
    浏览(22)
  • 1、怎么阅读linux内核源码

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

    2024年02月15日
    浏览(30)
  • linux内核网络源码学习(二)

    skb_reserve 函数通常用于网络编程中的数据包处理,特别是在构建自定义协议栈或数据包处理模块时。它的作用是为数据包的头部预留额外的空间,以确保数据包的头部数据在内存中是对齐的。 边界对齐的概念是因为许多硬件平台和网络协议要求数据包头的字节对齐。如果数据

    2024年02月08日
    浏览(41)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包