- int do_execve(const char * filename,
-
const char __user *const __user *argv,
-
const char __user *const __user *envp,
- struct pt_regs * regs)
其中filename是可执行文件名,argv和envp分别是传入的参数和环境变量,regs是寄存器集合
其中的argv喝envp是指针数组,并且它们指向的两个数组自身的指针,以及数组中的指针都是位于虚拟地址空间的用户空间部分。
__user这个宏是供代码分析工具来检查该变量是否指向用户空间的
- # define __user __attribute__((noderef, address_space(1)))
当编译内核代码的时候,使用make C=1或C=2的时候,会调用一个叫Sparse的工具,这个工具对内核代码进行检查,怎么检查呢,就是靠对那些声明过Sparse这个工具所能识别的特性的内核函数或是变量进行检查。在调用Sparse这个工具的同时,在Sparse代码里,会加上#define __CHECKER__ 1的字样。换句话说,就是,如果使用Sparse对代码进行检查,那么内核代码就会定义__CHECKER__宏,否则就不定义。具体解释请访问:
例如:
# define __user __attribute__((noderef, address_space(1)))
这个宏是重点,用来检查是否属于用户空间!这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
其他的那些个属性是用来检查什么的呢,我一个个地做介绍。
__user 这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(__attribute__((noderef))——no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1(__attribute__((address_space(1)))),即用户程序空间的。这里Sparse工具把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。
例如:
# define __user __attribute__((noderef, address_space(1)))
这个宏是重点,用来检查是否属于用户空间!这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
其他的那些个属性是用来检查什么的呢,我一个个地做介绍。
__user 这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(__attribute__((noderef))——no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1(__attribute__((address_space(1)))),即用户程序空间的。这里Sparse工具把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。
——引用自
接着讲解do_execve
do_execve完成的工作主要如下:
1、打开可执行文件
- file = open_exec(filename);
-
retval = PTR_ERR(file);
-
if (IS_ERR(file))
- goto out_unmark;
2、bprm_init
1) mm_alloc:生成一个新的mm_struct实例来管理进程地址空间
- bprm->mm = mm = mm_alloc();
2)init_new_context:初始化mm_struct实例
- err = init_new_context(current, mm);
3)__bprm_mm_init:建立初始的栈
- err = __bprm_mm_init(bprm);
要仔细了解这部分的工作,就需要了解struct linux_binprm和struct mm_struct的结构和具体作用,这个后面会进行讲解。
3、prepare_binprm:新进程的很多参数,例如euid,egid,参数列表等,会被后面的函数用到,未来方便起见,会将这些信息打包在linux_binprm里,因此prepare_binprm的作用是将父进程的一些相关的值(特别是euid和gid)复制到linux_binprm这个结构里。
- bprm->cred->euid = current_euid();
-
bprm->cred->egid = current_egid();
-
-
if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
-
/* Set-uid? */
-
if (mode & S_ISUID) {
-
bprm->per_clear |= PER_CLEAR_ON_SETID;
-
bprm->cred->euid = inode->i_uid;
-
}
-
-
/* Set-gid? */
-
/*
-
* If setgid is set but no group execute bit then this
-
* is a candidate for mandatory locking, not a setgid
-
* executable.
-
*/
-
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
-
bprm->per_clear |= PER_CLEAR_ON_SETID;
-
bprm->cred->egid = inode->i_gid;
-
}
- }
4、复制环境变量和参数数组内容
- retval = copy_strings_kernel(1, &bprm->filename, bprm);
-
if (retval < 0)
-
goto out;
-
-
bprm->exec = bprm->p;
-
retval = copy_strings(bprm->envc, envp, bprm);
-
if (retval < 0)
-
goto out;
-
-
retval = copy_strings(bprm->argc, argv, bprm);
- if (retval < 0)
5、search_binary_handler:遍历所有的二进制格式,查找合适的handler。
这个函数比较复制后面会仔细讲解。