天天看点

内核代码阅读(22) - 进程之execve

最近一段时间有点忙,好久没有阅读内核代码了。坚持下去,自勉!

进程的创建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, &regs);
        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, &regs);
   开始执行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(&current->sigmask_lock);
        if (current->sig != oldsig)
                kfree(current->sig);
        current->sig = oldsig;
        spin_unlock_irq(&current->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(&current->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(&current->mm->mmap_sem);
                if (error != N_TXTADDR(ex)) {
                        send_sig(SIGKILL, current, 0);
                        return error;
                }
                down(&current->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(&current->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(&current->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(&current->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);
   开始递归调用      

继续阅读