最近一段时间有点忙,好久没有阅读内核代码了。坚持下去,自勉!
进程的创建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);
开始递归调用