1.对内核中do_execve()函数的理解
Linux提供了execle,execlp, execv, execvp和execve等六个用以执行一个可执行文件的函数(其之间的差异在于对命令行参数和环境变量的传递方式不同)。第一个参数是要被执行的程序的路径,第二个参数则向程序传递了命令行参数,第三个参数则向程序传递环境变量。但其本质都是调用sys_execve()中的do_execve()函数。
1.1.do_execve()函数的主要流程:
说明1:do_execve()的参数
int do_execve(char * filename,
char __user *__user *argv,
char __user *__user *envp,
struct pt_regs * regs)
filename:要执行的文件名,argv:参数,envp:指的是环境变量, struct pt_regs:模拟cpu寄存器的地址,其定义如下:
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
int xfs;
int xgs;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
在这个结构体中,记录了用户态下的cpu寄存器在核心态的栈中的保存情况。通过这个参数,sys_execve能获得保存在用户空间的一下信息:课执行文件路径的指针(ebx中),命令行参数的指针(ecx中)和环境变量的指针(edx中)。
1.2. do_execve()函数的准备工作
//不使用shell程序打开文件
retval = unshare_files(&displaced);
if (retval)
goto out_ret;
retval = -ENOMEM;
//分配 struct linux_binprm结构体
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_files;
retval = prepare_bprm_creds(bprm);
if (retval)
goto out_free;
retval = check_unsafe_exec(bprm);
if (retval < 0)
goto out_free;
clear_in_exec = retval;
current->in_execve = 1;
这里面最重要的是这个叫struct linux_binpm的结构体,在这个结构体中会保存要执行文件的相关信息。然后会做一些准备工作:prepare_bprm_creds(), 还要进行安全检测:check_unsafe_exec():对要执行的文件进行安全检测,其传入参数是一个linux_binprm的结构体。
struct linux_binprm{
char buf[BINPRM_BUF_SIZE]; //保存课执行文件的头128个字节
#ifdef CONFIG_MMU
struct vm_area_struct *vma;
unsigned long vma_pages; //当前内存页的最高地址
#else
# define MAX_ARG_PAGES 32
struct page *page[MAX_ARG_PAGES];
#endif
struct mm_struct *mm;
unsigned long p; /* current top of mem */
unsigned int
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent's caps anyway */
#ifdef __alpha__
unsigned int taso:1;
#endif
unsigned int recursion_depth;
struct file * file; //要执行的文件
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc; //命令行参数和环境变量参数
char * filename; /* Name of binary as seen by procps */ 要被执行的文件的名的二进制
char * interp; /* Name of the binary really executed. Most
of the time same as filename, but could be
different for binfmt_{misc,script} */ 要被执行的文件的真是名,通常和filename相同
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};
1.3. open_exec()
struct file *open_exec(const char *name)
{
struct file *file;
int err;
file = do_filp_open(AT_FDCWD, name,
O_LARGEFILE | O_RDONLY | FMODE_EXEC, 0,
MAY_EXEC | MAY_OPEN);
if (IS_ERR(file))
goto out;
err = -EACCES;
//检查是否是普通文件
if (!S_ISREG(file->f_path.dentry->d_inode->i_mode))
goto exit;
//检查该文件系统上是否禁止执行可执行文件
if (file->f_path.mnt->mnt_flags & MNT_NOEXEC)
goto exit;
fsnotify_open(file->f_path.dentry);
//检查索引节点(即file->f_path.dentry->d_inode)的i_writecount字段是否大于0,
err = deny_write_access(file);
if (err)
goto exit;
out:
return file;
exit:
fput(file);
return ERR_PTR(err);
}}
这个函数传入参数为filename,即文件名的字符串,返回值为struct file类型的指针,即返回file结构体,那么这个函数的作用就很清楚了:它会调用do_filp_open()函数,返回这个文件对应的文件对象。(即struct file结构体)。
1.4. shced_exec
得到了struct file结构体,那么该程序执行前,需要先被cpu调度,在这里会调用sched_exec()函数,在这个函数中使用调度类对当前的进程进行调度。
struct task_struct *p = current;
struct rq *rq;
dest_cpu = p->sched_class->select_task_rq(rq, p, SD_BALANCE_EXEC, 0);//调度类
1.5.接着是对bprm结构体的一个填充:bprm_mm_init()函数
bprm->file = file;
bprm->filename = filename;
bprm->interp = filename;
接着会调用bprm_mm_init():对bprm结构体进行初始化,这个函数中主要是初始化了mm_struct
int bprm_mm_init(struct linux_binprm *bprm)
{
int err;
struct mm_struct *mm = NULL;
//为mm_struct分配内存
bprm->mm = mm = mm_alloc();
err = -ENOMEM;
if (!mm)
goto err;
//初始化进程上下文
err = init_new_context(current, mm);
if (err)
goto err;
//初始化mm_struct
err = __bprm_mm_init(bprm);
if (err)
goto err;
return 0;
err:
if (mm) {
bprm->mm = NULL;
mmdrop(mm);
}
return err;
}
下面是继续对bprm结构体填充的一个过程
bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out;
bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out;
紧接着会调用prepare_binprm()函数,填充Linux_binprm结构中的e_uid项和e_gid项。并且使用可执行文件的前128个字节来填充linux_binprm结构中的Buf项。
int prepare_binprm(struct linux_binprm *bprm)
{
//.............................
bprm->cred->euid = current_euid();
bprm->cred->egid = current_egid();
//..................................
memset(bprm->buf, 0, BINPRM_BUF_SIZE);
return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}
接着将文件名,环境变量和命令行参数等拷贝到新分配的页面中
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)
goto out;
1.6.将可执行的二进制文件load进来,search_binary_handler()函数
在这个函数中涉及到一个结构体struct linux_binfmt,在这个结构体中有一个函数指针,负责将要执行的二进制程序load进来。
struct linux_binfmt {
struct list_head lh;
struct module *module;
//这个函数完成二进制文件的装载和启动,
int (*load_binary)(struct linux_binprm *, struct pt_regs * regs);
//动态加载共享库
int (*load_shlib)(struct file *);
//用于程序出错时,输出内存转储
int (*core_dump)(long signr, struct pt_regs *regs, struct file *file, unsigned long limit);
unsigned long min_coredump; /* minimal dump size */
int hasvdso;
};
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
/................/
for (try=0; try<2; try++) {
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
if (!fn)
continue;
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
//具体的执行的函数
retval = fn(bprm, regs);
/*
* Restore the depth counter to its starting value
* in this call, so we don't have to rely on every
* load_binary function to restore it on return.
*/
bprm->recursion_depth = depth;
if (retval >= 0) {
if (depth == 0)
tracehook_report_exec(fmt, bprm, regs);
put_binfmt(fmt);
allow_write_access(bprm->file);
if (bprm->file)
fput(bprm->file);
bprm->file = NULL;
current->did_exec = 1;
proc_exec_connector(current);
return retval;
}
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (retval != -ENOEXEC || bprm->mm == NULL)
break;
if (!bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (retval != -ENOEXEC || bprm->mm == NULL) {
break;
}
}
return retval;
}
}
1、外层是一个循环,for (try=0; try<2; try++) 貌似是重试两次的意思,具体为什么是两次,暂时还没搞明白.
2、里层通过list_for_each_entry(fmt, &formats, lh)遍历二进制格式列表,寻找合适的handler,找到之后,让fn函数去执行。
1.7.收尾工作
当执行完之后,会有一些收尾工作:释放掉bprm结构体等。
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
acct_update_integrals(current);
free_bprm(bprm);
if (displaced)
put_files_struct(displaced);
return retval;