天天看點

核心代碼閱讀(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);
   開始遞歸調用      

繼續閱讀