最近一段时间有点忙,好久没有阅读内核代码了。坚持下去,自勉!
进程的创建execve
execve
asmlinkage int sys_execve(struct pt_regs regs) { int error; char * filename; filename = getname((char *) regs.ebx); error = PTR_ERR(filename); if (IS_ERR(filename)) goto out; error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s); if (error == 0) current->ptrace &= ~PT_DTRACE; putname(filename); out: return error; }
1) regs.ebx 是系统调用的第一个参数。要执行的二进制文件的路径。 2) filename = getname((char *) regs.ebx); 把这个字符串拷贝到系统空间。 3) error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s); 开始执行do_execve
getname拷贝用户空间的路径名到内核空间
char * getname(const char * filename) { char *tmp, *result; result = ERR_PTR(-ENOMEM); tmp = __getname(); if (tmp) { int retval = do_getname(filename, tmp); result = tmp; if (retval < 0) { putname(tmp); result = ERR_PTR(retval); } } return result; }
1) tmp = __getname(); #define __getname() kmem_cache_alloc(names_cachep, SLAB_KERNEL) 从slab中申请一个页面存放用户空间的路径字符串。 为什么要申请一个页面呢?因为路径字符串可能很长。而内核栈的大小是7K,不合适在栈上开辟一段4K的临时空间。char tmp[4096]。 2) do_getname(filename, tmp); 如果申请到了一个页面,则开始拷贝。
do_getname
static inline int do_getname(const char *filename, char *page) { int retval; unsigned long len = PATH_MAX + 1; if ((unsigned long) filename >= TASK_SIZE) { if (!segment_eq(get_fs(), KERNEL_DS)) return -EFAULT; } else if (TASK_SIZE - (unsigned long) filename < PAGE_SIZE) len = TASK_SIZE - (unsigned long) filename; retval = strncpy_from_user((char *)page, filename, len); if (retval > 0) { if (retval < len) return 0; return -ENAMETOOLONG; } else if (!retval) retval = -ENOENT; return retval; }
1) if ((unsigned long) filename >= TASK_SIZE) 如果filename指针越过了 TASK_SIZE,说明这个指针到达了内核的地址空间,是非法的。 2) else if (TASK_SIZE - (unsigned long) filename < PAGE_SIZE) 这个判断可以简单的得出路径字符串长度小于一个页面。更新长度len。其他的情况使用路径的最大长度默认值。 3) retval = strncpy_from_user((char *)page, filename, len); 从用户态拷贝字符串。
strncpy_from_user
long __strncpy_from_user(char *dst, const char *src, long count) { long res; __do_strncpy_from_user(dst, src, count, res); return res; } long strncpy_from_user(char *dst, const char *src, long count) { long res = -EFAULT; if (access_ok(VERIFY_READ, src, 1)) __do_strncpy_from_user(dst, src, count, res); return res; }
#define __do_strncpy_from_user(dst,src,count,res) \ do { \ int __d0, __d1, __d2; \ __asm__ __volatile__( \ " testl %1,%1\n" \ " jz 2f\n" \ "0: lodsb\n" \ " stosb\n" \ " testb %%al,%%al\n" \ " jz 1f\n" \ " decl %1\n" \ " jnz 0b\n" \ "1: subl %1,%0\n" \ "2:\n" \ ".section .fixup,\"ax\"\n" \ "3: movl %5,%0\n" \ " jmp 2b\n" \ ".previous\n" \ ".section __ex_table,\"a\"\n" \ " .align 4\n" \ " .long 0b,3b\n" \ ".previous" \ : "=d"(res), "=c"(count), "=&a" (__d0), "=&S" (__d1), \ "=&D" (__d2) \ : "i"(-EFAULT), "0"(count), "1"(count), "3"(src), "4"(dst) \ : "memory"); \ } while (0)
1) strncpy_from_user 最终调用的是宏 __do_strncpy_from_user 2) lodsb 把esi指向的字节加载到eax中。 3) stosb 把eax中的内容存储到edi指向的内存中。 4) testb %%al,%%al\n" 如果eax为0,则说明拷贝到了字符串的结尾NULL。 5) "1: subl %1,%0\n" res-count做为返回值。 res是字符串的原始长度,count是已经拷贝了的字符数。 如果res-count为0,说明还没有拷贝到NULL,说明用户空间传过来的字符串太长了!。 6) ".section __ex_table,\"a\"\n" __ex_table是异常修复表。异常发生的地方page_falut二分的查找这个表,找到修复异常的入口。 如果符号0处发生了异常,就用3处的代码修复。 7) "3: movl %5,%0\n" 异常发生时的修复逻辑是 res = _EFAULT。
do_execve
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs) { struct linux_binprm bprm; struct file *file; int retval; int i; file = open_exec(filename); retval = PTR_ERR(file); if (IS_ERR(file)) return retval; bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *); memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); bprm.file = file; bprm.filename = filename; bprm.sh_bang = 0; bprm.loader = 0; bprm.exec = 0; if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) { allow_write_access(file); fput(file); return bprm.argc; } if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) { allow_write_access(file); fput(file); return bprm.envc; } retval = prepare_binprm(&bprm); if (retval < 0) goto out; 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; retval = search_binary_handler(&bprm,regs); if (retval >= 0) return retval; out: allow_write_access(bprm.file); if (bprm.file) fput(bprm.file); for (i = 0 ; i < MAX_ARG_PAGES ; i++) { struct page * page = bprm.page[i]; if (page) __free_page(page); } return retval; }
1) file = open_exec(filename); 打开可执行文件,临时设置文件为只读deny_write_access(file);。 2) struct linux_binprm bprm; 这个结构体用来装载可执行文件时,收集可执行文件的相关信息。 struct linux_binprm{ char buf[BINPRM_BUF_SIZE]; struct page *page[MAX_ARG_PAGES]; unsigned long p; /* current top of mem */ int sh_bang; struct file * file; int e_uid, e_gid; kernel_cap_t cap_inheritable, cap_permitted, cap_effective; int argc, envc; char * filename; /* Name of binary */ unsigned long loader, exec; }; 3) bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *); memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); 内核要把环境变量个数全部拷贝到内核空间。所以分配了最大参数个数的页面数。 bprm.page 是一个页面数组。 bprm.p 是在准备可执行环境过程中用到的内存的大小,正好是参数个数减去第一个参数的指针的大小,因为第一个参数已经被内核拷贝过来了,bprm需要再拷贝了。 4) retval = prepare_binprm(&bprm); 准备从可执行文件中读取128字节到 binprm.buf中。 这128字节包含了可执行文件的属性信息。用以决定是a.out, ELF, sh脚本等。 5) retval = copy_strings_kernel(1, &bprm.filename, &bprm); retval = copy_strings(bprm.envc, envp, &bprm); 把用户空间的参数和环境变量拷贝到内核空间的bprm。 6) int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs) 环境准备好之后,开始装载可执行文件。
search_binary_handler 装载可执行文件
内核中有一个formats队列,初始化了所有可执行文件的负责人。
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs) { int try,retval=0; struct linux_binfmt *fmt; for (try=0; try<2; try++) { read_lock(&binfmt_lock); for (fmt = formats ; fmt ; fmt = fmt->next) { int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; if (!fn) continue; if (!try_inc_mod_count(fmt->module)) continue; read_unlock(&binfmt_lock); retval = fn(bprm, regs); if (retval >= 0) { put_binfmt(fmt); allow_write_access(bprm->file); if (bprm->file) fput(bprm->file); bprm->file = NULL; current->did_exec = 1; return retval; } read_lock(&binfmt_lock); put_binfmt(fmt); if (retval != -ENOEXEC) break; if (!bprm->file) { read_unlock(&binfmt_lock); return retval; } } read_unlock(&binfmt_lock); if (retval != -ENOEXEC) { break; #ifdef CONFIG_KMOD }else{ #define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e)) char modname[20]; if (printable(bprm->buf[0]) && printable(bprm->buf[1]) && printable(bprm->buf[2]) && printable(bprm->buf[3])) break; /* -ENOEXEC */ sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2])); request_module(modname); #endif } } return retval; }
1) for循环第一遍 从formats队列中查找一个可以为bprm我服务的loader。 2) int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; 获取负责一种文件格式模块的函数指针。 3) retval = fn(bprm, regs); 尝试者处理。 4) for循环第二遍 安装模块。 模块的字符串规则是: "binfmt-buf[2]x"。 然后再从头尝试解码一遍。
二进制文件的装载
各种loader以内核模块的形式加载进来:
static int __init init_aout_binfmt(void) { return register_binfmt(&aout_format); } static void __exit exit_aout_binfmt(void) { unregister_binfmt(&aout_format); } EXPORT_NO_SYMBOLS; module_init(init_aout_binfmt); module_exit(exit_aout_binfmt);
装载器的formats队列定义。 static struct linux_binfmt *formats; 其中, struct linux_binfmt { struct linux_binfmt * next; 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 min_coredump; /* minimal dump size */ }; 1) load_binary 装载二进制的函数指针。 2) load_shlib 装载动态脚本 #!/bin/bash 3) core_dump 产生core_dump的函数指针。
a.out的装载
static struct linux_binfmt aout_format = { NULL, THIS_MODULE, load_aout_binary, load_aout_library, aout_core_dump, PAGE_SIZE }; 可以看到二进制文件的加载函数是 load_aout_binary。
load_aout_binary第1段
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs) { struct exec ex; unsigned long error; unsigned long fd_offset; unsigned long rlim; int retval; ex = *((struct exec *) bprm->buf); if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC && N_MAGIC(ex) != QMAGIC && N_MAGIC(ex) != NMAGIC) || N_TRSIZE(ex) || N_DRSIZE(ex) || bprm->file->f_dentry->d_inode->i_size < ex.a_text+ex.a_data+N_SYMSIZE(ex)+N_TXTOFF(ex)) { return -ENOEXEC; } fd_offset = N_TXTOFF(ex); rlim = current->rlim[RLIMIT_DATA].rlim_cur; if (rlim >= RLIM_INFINITY) rlim = ~0; if (ex.a_data + ex.a_bss > rlim) return -ENOMEM; retval = flush_old_exec(bprm); if (retval) return retval;
1) ex = *((struct exec *) bprm->buf); 从bprm中取出exec结构体,再做一次检查。 2) fd_offset = N_TXTOFF(ex); 获取二进制文件的代码开始的地方。 3) if (ex.a_data + ex.a_bss > rlim) 检查二进制文件中的data+bss是否超限 4) flush_old_exec 当前的子进程的内存,信号处理函数等资源抛掉。
flush_old_exec
int flush_old_exec(struct linux_binprm * bprm) { char * name; int i, ch, retval; struct signal_struct * oldsig; oldsig = current->sig; retval = make_private_signals(); if (retval) goto flush_failed; retval = exec_mmap(); if (retval) goto mmap_failed; release_old_signals(oldsig); current->sas_ss_sp = current->sas_ss_size = 0; if (current->euid == current->uid && current->egid == current->gid) current->dumpable = 1; name = bprm->filename; for (i=0; (ch = *(name++)) != '\0';) { if (ch == '/') i = 0; else if (i < 15) current->comm[i++] = ch; } current->comm[i] = '\0'; flush_thread(); de_thread(current); if (bprm->e_uid != current->euid || bprm->e_gid != current->egid || permission(bprm->file->f_dentry->d_inode,MAY_READ)) current->dumpable = 0; current->self_exec_id++; flush_signal_handlers(current); flush_old_files(current->files); return 0; mmap_failed: flush_failed: spin_lock_irq(¤t->sigmask_lock); if (current->sig != oldsig) kfree(current->sig); current->sig = oldsig; spin_unlock_irq(¤t->sigmask_lock); return retval; }
1) retval = make_private_signals(); 当前的子进程的信号处理函数可能是和父进程共享的,所以要做一次复制。 2) retval = exec_mmap(); 丢掉用户空间的内存,给子进程分配属于自己的mm_struct。此时用户空间的内存大小是0,页面表项也是空的。 3) current->comm[i++] = ch; 拷贝执行程序的路径名。 4) de_thread(current); 新生成的子进程可能是父进程在同一个线程组里。 de_thread解除这种关系。 5) flush_signal_handlers(current); 复制一份信号处理函数,并且把所有的信号处理函数都指向SIG_DFL。 6) flush_old_files(current->files); 每个进程task_struct结构中有file_struct结构,指向已经打开文件的信息。 在file_struct中的有个位图close_on_exec,指示哪些文件需要在执行一个新目标程序时候关闭。 这个函数就是关闭这些文件,然后将close_on_exec清空。 close_on_exec可以通过ioctl控制。
# exec_mmap
丢掉用户空间的内存,给子进程分配属于自己的mm_struct。
static int exec_mmap(void) { struct mm_struct * mm, * old_mm; old_mm = current->mm; if (old_mm && atomic_read(&old_mm->mm_users) == 1) { flush_cache_mm(old_mm); mm_release(); exit_mmap(old_mm); flush_tlb_mm(old_mm); return 0; } mm = mm_alloc(); if (mm) { struct mm_struct *active_mm = current->active_mm; if (init_new_context(current, mm)) { mmdrop(mm); return -ENOMEM; } spin_lock(&mmlist_lock); list_add(&mm->mmlist, &init_mm.mmlist); spin_unlock(&mmlist_lock); task_lock(current); current->mm = mm; current->active_mm = mm; task_unlock(current); activate_mm(active_mm, mm); mm_release(); if (old_mm) { if (active_mm != old_mm) BUG(); mmput(old_mm); return 0; } mmdrop(active_mm); return 0; } return -ENOMEM; }
1) 当前的子进程的内存和父进程关系有两种: 一是通过vfork调用产生的进程,内存和父进程完全共享mm_struct。 二是通过fork调用产生的进程, 内存和父进程的关系是copy-on-write,页面表项都是独立的。 2) if (old_mm && atomic_read(&old_mm->mm_users) == 1) 如果是通过fork,子进程拥有独立mm_struct,独立的页面表项,只是这些页面表项指向的物理内存和父进程是共享的。 3) mm_release if (tsk->flags & PF_VFORK) { tsk->flags &= ~PF_VFORK; up(tsk->p_opptr->vfork_sem); } 释放父进程的锁。因为此时父进程此时正在等待子进程释放mm_struct。 4) exit_mmap(old_mm); 释放当前进程拥有的独立的页面表项。 5) mm = mm_alloc(); 如果子进程和父进程是共享mm_struct的,说明子进程还没有自己的mm_struct。需要为此分配一个并且激活。 6) activate_mm(active_mm, mm); 激活新分配给进程的mm。 7) mmput(old_mm);;mmdrop 和内核线程相关。
load_aout_binary第2段
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs) { ... ... ... current->mm->end_code = ex.a_text + (current->mm->start_code = N_TXTADDR(ex)); current->mm->end_data = ex.a_data + (current->mm->start_data = N_DATADDR(ex)); current->mm->brk = ex.a_bss + (current->mm->start_brk = N_BSSADDR(ex)); current->mm->rss = 0; current->mm->mmap = NULL; compute_creds(bprm); current->flags &= ~PF_FORKNOEXEC; ... ... ... }
1) current->mm->end_code = ex.a_text + (current->mm->start_code = N_TXTADDR(ex)); current->mm->start_code = N_TXTADDR(ex) 设置新进程的start_code为二进制文件ex中代码段开始的地方。 设置新进程的end_code为二进制文件ex中代码段开始的地方,加上代码段的大小。 其中, #define N_TXTADDR(x) (N_MAGIC(x) == QMAGIC ? PAGE_SIZE : 0)。 2) 同样的end_data设置数据段。 3) current->mm->brk = ex.a_bss + (current->mm->start_brk = N_BSSADDR(ex)); 设置start_brk为bss的开始。 设置brk为bss的开始,加上bss大小。 4) current->mm->rss = 0; 设置rss物理内存使用量为0
load_aout_binary第3段 脚本代码的载入
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs) { ... ... ... if (N_MAGIC(ex) == OMAGIC) { unsigned long text_addr, map_size; loff_t pos; text_addr = N_TXTADDR(ex); pos = 32; map_size = ex.a_text+ex.a_data; error = do_brk(text_addr & PAGE_MASK, map_size); if (error != (text_addr & PAGE_MASK)) { send_sig(SIGKILL, current, 0); return error; } error = bprm->file->f_op->read(bprm->file, (char *)text_addr, ex.a_text+ex.a_data, &pos); if (error < 0) { send_sig(SIGKILL, current, 0); return error; } ... ... ... }
1) if (N_MAGIC(ex) == OMAGIC) 如果可执行文件是OMAGIC类型,是纯的二进制。 2) map_size = ex.a_text+ex.a_data; 需要加载可执行文件的部分是text段和data段。 3) error = do_brk(text_addr & PAGE_MASK, map_size); 调用brk分配内存。 4) error = bprm->file->f_op->read(bprm->file, (char *)text_addr, ex.a_text+ex.a_data, &pos); 读入到内存中。
load_aout_binary第3段 纯二进制代码的载入
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs) { ... ... ... } else { static unsigned long error_time, error_time2; if ((ex.a_text & 0xfff || ex.a_data & 0xfff) && (N_MAGIC(ex) != NMAGIC) && (jiffies-error_time2) > 5*HZ) { printk(KERN_NOTICE "executable not page aligned\n"); error_time2 = jiffies; } if ((fd_offset & ~PAGE_MASK) != 0 && (jiffies-error_time) > 5*HZ) { printk(KERN_WARNING "fd_offset is not page aligned. Please convert program: %s\n", bprm->file->f_dentry->d_name.name); error_time = jiffies; } if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) { loff_t pos = fd_offset; do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data); bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex), ex.a_text+ex.a_data, &pos); flush_icache_range((unsigned long) N_TXTADDR(ex), (unsigned long) N_TXTADDR(ex) + ex.a_text+ex.a_data); goto beyond_if; } down(¤t->mm->mmap_sem); error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE, fd_offset); up(¤t->mm->mmap_sem); if (error != N_TXTADDR(ex)) { send_sig(SIGKILL, current, 0); return error; } down(¤t->mm->mmap_sem); error = do_mmap(bprm->file, N_DATADDR(ex), ex.a_data, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE, fd_offset + ex.a_text); up(¤t->mm->mmap_sem); if (error != N_DATADDR(ex)) { send_sig(SIGKILL, current, 0); return error; } } ... ... ... }
1) if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) 如果二进制文件所在的文件系统不支持mmap,则调用read。 2) error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text, 对于纯的二进制,优先使用mmap,这样还能节省swap空间。
load_aout_binary第3段 bss段的初始化
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs) { ... ... ... beyond_if: set_binfmt(&aout_format); set_brk(current->mm->start_brk, current->mm->brk); retval = setup_arg_pages(bprm); if (retval < 0) { send_sig(SIGKILL, current, 0); return retval; } current->mm->start_stack = (unsigned long) create_aout_tables((char *) bprm->p, bprm); start_thread(regs, ex.a_entry, current->mm->start_stack); if (current->ptrace & PT_PTRACED) send_sig(SIGTRAP, current, 0); return 0; }
1) set_brk(current->mm->start_brk, current->mm->brk); 此时区间[start_brk, brk)之间是bss段。建立bss段的虚拟地址。 2) retval = setup_arg_pages(bprm); 此时从用户空间拷贝过来的参数和环境变量在内核数据结构bprm中, 这个函数要把这些参数对应的页面再反向映射给用户空间的地址。 3) create_aout_tables((char *) bprm->p, bprm); 构造argv指针数组。 4) start_thread(regs, ex.a_entry, current->mm->start_stack); ex.a_entry是二进制指令入口的地方。 start_stack用户空间栈的开端。
setup_arg_pages(bprm);
int setup_arg_pages(struct linux_binprm *bprm) { unsigned long stack_base; struct vm_area_struct *mpnt; int i; stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE; bprm->p += stack_base; if (bprm->loader) bprm->loader += stack_base; bprm->exec += stack_base; mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); if (!mpnt) return -ENOMEM; down(¤t->mm->mmap_sem); { mpnt->vm_mm = current->mm; mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p; mpnt->vm_end = STACK_TOP; mpnt->vm_page_prot = PAGE_COPY; mpnt->vm_flags = VM_STACK_FLAGS; mpnt->vm_ops = NULL; mpnt->vm_pgoff = 0; mpnt->vm_file = NULL; mpnt->vm_private_data = (void *) 0; insert_vm_struct(current->mm, mpnt); current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT; } for (i = 0 ; i < MAX_ARG_PAGES ; i++) { struct page *page = bprm->page[i]; if (page) { bprm->page[i] = NULL; current->mm->rss++; put_dirty_page(current,page,stack_base); } stack_base += PAGE_SIZE; } up(¤t->mm->mmap_sem); return 0; }
1) STACK_TOP 是用户空间栈的顶端大小是3G. 2) mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); 给argv和envp所需要的页面分配虚拟地址空间。 3) mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p; 4) for (i = 0 ; i < MAX_ARG_PAGES ; i++) 遍历所有的参数,把每个参数和用户空间专门给参数分配的虚拟地址stack_base建立起映射。
start_thread
#define start_thread(regs, new_eip, new_esp) do { \ __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0)); \ set_fs(USER_DS); \ regs->xds = __USER_DS; \ regs->xes = __USER_DS; \ regs->xss = __USER_DS; \ regs->xcs = __USER_CS; \ regs->eip = new_eip; \ regs->esp = new_esp; \ } while (0)
1) 关键是对regs的理解, regs进入系统调用保存的现场,在系统调用返回的时候,这个些值都会被恢复到CPU的各个寄存器中。 2) regs->eip = new_eip; 返回到用户空间从new_eip开始执行。 3) regs->esp = new_esp; 返回到用户空间后新的栈。
脚本的装载
脚本装载的module
struct linux_binfmt script_format = { NULL, THIS_MODULE, load_script, NULL, NULL, 0 }; static int __init init_script_binfmt(void) { return register_binfmt(&script_format); } static void __exit exit_script_binfmt(void) { unregister_binfmt(&script_format); } module_init(init_script_binfmt) module_exit(exit_script_binfmt)
装载函数
static int load_script(struct linux_binprm *bprm,struct pt_regs *regs) { char *cp, *i_name, *i_arg; struct file *file; char interp[BINPRM_BUF_SIZE]; int retval; if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') || (bprm->sh_bang)) return -ENOEXEC; bprm->sh_bang++; allow_write_access(bprm->file); fput(bprm->file); bprm->file = NULL; bprm->buf[BINPRM_BUF_SIZE - 1] = '\0'; if ((cp = strchr(bprm->buf, '\n')) == NULL) cp = bprm->buf+BINPRM_BUF_SIZE-1; *cp = '\0'; while (cp > bprm->buf) { cp--; if ((*cp == ' ') || (*cp == '\t')) *cp = '\0'; else break; } for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++); if (*cp == '\0') return -ENOEXEC; /* No interpreter name found */ i_name = cp; i_arg = 0; for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) /* nothing */ ; while ((*cp == ' ') || (*cp == '\t')) *cp++ = '\0'; if (*cp) i_arg = cp; strcpy (interp, i_name); remove_arg_zero(bprm); retval = copy_strings_kernel(1, &bprm->filename, bprm); if (retval < 0) return retval; bprm->argc++; if (i_arg) { retval = copy_strings_kernel(1, &i_arg, bprm); if (retval < 0) return retval; bprm->argc++; } retval = copy_strings_kernel(1, &i_name, bprm); if (retval) return retval; bprm->argc++; file = open_exec(interp); if (IS_ERR(file)) return PTR_ERR(file); bprm->file = file; retval = prepare_binprm(bprm); if (retval < 0) return retval; return search_binary_handler(bprm,regs); }
1) if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') || (bprm->sh_bang)) 检查脚本的第一行是否以"#!"开头的。 2) strcpy (interp, i_name); 解析脚本的名字。 3) return search_binary_handler(bprm,regs); 开始递归调用