最近一段時間有點忙,好久沒有閱讀核心代碼了。堅持下去,自勉!
程序的建立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);
開始遞歸調用