execve源码分析

C语言的execve经过库函数,最终也会通过int 0x80中断陷入内核,并通过eax寄存器来传递调用号。
内核会根据系统初始化时注册的中断处理函数来对其进行处理。它的中断处理函数为system_call。这个函数定义在system_call.s中,它会保存一些寄存器的值(对于系统调用来说,保存的主要是参数),然后根据调用号调用syscall_table中相应的表项。syscall_table定义在include/linux/sys.h文件中,它是一个函数指针数组。对于execve来说它找到的表项就是sys_execve。它仍然是定义在system_call.s中的,在这里面它最终又调用了do_execve。

下面我们来具体看一下
system_call.s:
 80 _system_call:
 81     cmpl $nr_system_calls-1,%eax
 82     ja bad_sys_call
 83     push %ds
 84     push %es
 85     push %fs
 86     pushl %edx
 87     pushl %ecx      # push %ebx,%ecx,%edx as parameters
 88     pushl %ebx      # to the system call
 89     movl $0x10,%edx     # set up ds,es to kernel space
 90     mov %dx,%ds
 91     mov %dx,%es

可以看到每次系统调用都会设置fs指向用户段,这使得内核可以和用户空间拷贝数据
 92     movl $0x17,%edx     # fs points to local data space
 93     mov %dx,%fs

 94     call _sys_call_table(,%eax,4)
这里根据调用号会找到sys_execve

_sys_execve:
199 .align 2
200 _sys_execve:
201     lea EIP(%esp),%eax
202     pushl %eax
203     call _do_execve
204     addl $4,%esp
205     ret
201-202行把eip压栈,作为do_execve参数
我们分段来看do_execve的过程:
179 /*
180  * 'do_execve()' executes a new program.
181  */
182 int do_execve(unsigned long * eip,long tmp,char * filename,
183     char ** argv, char ** envp)
184 {
185     struct m_inode * inode;
186     struct buffer_head * bh;
187     struct exec ex;
188     unsigned long page[MAX_ARG_PAGES];
189     int i,argc,envc;
190     int e_uid, e_gid;
191     int retval;
192     int sh_bang = 0;
193     unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
注意这里p是用来保存环境变量和函数参数的,它的大小为128K,这里p就指向该数组最后一个长字地址

195     if ((0xffff & eip[1]) != 0x000f)
196         panic("execve called from supervisor mode");
195行检查段选择符(eip之上保存的就是原cs段),不能是内核代码段,因为内核代码不能被替换

197     for (i=0 ; i 198         page[i]=0;
清空用于保存参数和环境变量的内存
199     if (!(inode=namei(filename)))       /* get executables inode */
200         return -ENOENT;
获取可执行文件对应的inode
201     argc = count(argv);
202    envc = count(envp);
然后计算出参数个数和环境变量个数

204 restart_interp:
205     if (!S_ISREG(inode->i_mode)) {  /* must be regular file */
206         retval = -EACCES;
207         goto exec_error2;
208     }
209     i = inode->i_mode;
210     e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
211      e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
如果不是常规文件,就直接返回。否则,取得它的模式,然后如果没有设置SUID,就获得inode节点的用户id,否则获得它的有效用户id,对于用户组id是同样的道理。关于这一点,我们在shell中也已经看到过。继续看:
212     if (current->euid == inode->i_uid)
213         i >>= 6;
214     else if (current->egid == inode->i_gid)
215         i >>= 3;
216     if (!(i & 1) &&
217         !((inode->i_mode & 0111) && suser())) {
218         retval = -ENOEXEC;
219         goto exec_error2;
220     }
221     if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
222         retval = -EACCES;
223         goto exec_error2;
224     }
可以推测第221行之前就是根据用户id和组id进行权限的判断.第221行读取文件的第一个逻辑块。如果读不到就返回。

225     ex = *((struct exec *) bh->b_data); /* readexec-header */

226     if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
227         /*
228          * This section does the #! interpretation.
229          * Sorta complicated, but hopefully it will work.  -TYT
230          */
231
232         char buf[1023], *cp, *interp, *i_name, *i_arg;
233         unsigned long old_fs;
234
235         strncpy(buf, bh->b_data+2, 1022);
236         brelse(bh);
237         iput(inode);
238         buf[1022] = '\0';
239         if (cp = strchr(buf, '\n')) {
240             *cp = '\0';
241             for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);     //跳过''和'\t'
242         }
243         if (!cp || *cp == '\0') {
244             retval = -ENOEXEC; /* No interpreter name found */
245             goto exec_error1;
246         }
如果文件头以#!开头,我们把该数据块除去"#!"的剩余内容拷贝到buf里,归还i_node节点和bh 。241行跳过空格和tab。继续分析
247         interp = i_name = cp;         //这时cp所指的位置就是文件名的起始地址
248         i_arg = 0;
249         for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
250             if (*cp == '/')
251                 i_name = cp+1;
252         }
通过249行-252行的for循环我们就能取得相应的文件名.比如对于"/etc/passwd",我们就能取得passwd.
253         if (*cp) {
254             *cp++ = '\0';         //解释程序字符串结束
255              i_arg = cp;
256         }
255行的i_arg用来保存参数的起始地址
257         /*
258          * OK, we've parsed out the interpreter name and
259          * (optional) argument.
260          */
261         if (sh_bang++ == 0) {
262             p = copy_strings(envc, envp, page, p, 0);
263             p = copy_strings(--argc, argv+1, page, p, 0);
264         }
262行和263行用于把用户空间的环境变量和参数信息复制到page数组中,并标记sh_bang
265         /*
266          * Splice(粘接) in (1) the interpreter's name for argv[0]
267          *           (2) (optional) argument to interpreter
268          *           (3) filename of shell script
269          *
270          * This is done in reverse order, because of how the
271          * user environment and arguments are stored.
272          */
273         p = copy_strings(1, &filename, page, p, 1);
274         argc++;
275         if (i_arg) {
276             p = copy_strings(1, &i_arg, page, p, 2);
277             argc++;
278         }
279         p = copy_strings(1, &i_name, page, p, 2);
280         argc++;
上面用于把解释器文件名,解释器参数和shell脚本文件名拷贝到page中(之前已经拷贝了环境变量和参数)

281         if (!p) {
282             retval = -ENOMEM;
283             goto exec_error1;
284         }
285         /*
286          * OK, now restart the process with the interpreter's inode.
287          */
288         old_fs = get_fs();
289         set_fs(get_ds());         //需要从内核空间获取
290         if (!(inode=namei(interp))) { /* get executables inode */
291             set_fs(old_fs);
292             retval = -ENOENT;
293             goto exec_error1;
294         }
295         set_fs(old_fs);
上面用于获取解释器inode节点
296         goto restart_interp;     //跳转,重新执行解释器
297     }

298     brelse(bh);
299     if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
300         ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
301         inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
302         retval = -ENOEXEC;
303         goto exec_error2;
304     }
//检查文件格式

305     if (N_TXTOFF(ex) != BLOCK_SIZE) {//可执行文件开始地址必须页对齐
306         printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
307         retval = -ENOEXEC;
308         goto exec_error2;
309     }

310     if (!sh_bang) {     //已经复制过就不许再复制了
311         p = copy_strings(envc,envp,page,p,0);
312         p = copy_strings(argc,argv,page,p,0);
         如果没有复制过,现在进行复制
313         if (!p) {
314             retval = -ENOMEM;
315             goto exec_error2;
316         }
              如果128k不足以容纳参数和环境变量,出错
317     }

318 /* OK, This is the point of no return */
319     if (current->executable)
320         iput(current->executable);
321     current->executable = inode;
322     for (i=0 ; i<32 ; i++)
323         current->sigaction[i].sa_handler = NULL;
324     for (i=0 ; i 325         if ((current->close_on_exec>>i)&1)
326             sys_close(i);
这里开始为新task创建结构体,并做一些初始化工作
327     current->close_on_exec = 0;
328     free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
329     free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
释放ldt中的段描述符
330     if (last_task_used_math == current)
331         last_task_used_math = NULL;
332     current->used_math = 0;
为执行新程序做准备工作
333     p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
这时p其实是从数据段末端开始的偏移,也就是说把环境变量和参数放到数据段末端
334     p = (unsigned long) create_tables((char *)p,argc,envc);
这里是为main函数的执行创建参数的
335    current->brk = ex.a_bss +
336         (current->end_data = ex.a_data +
337         (current->end_code = ex.a_text));
338    current->start_stack = p & 0xfffff000;
339    current->euid = e_uid;
340    current->egid = e_gid;
341     i = ex.a_text+ex.a_data;
其他初始化工作,比如设置brk,栈,用户id,用户组id。
342     while (i&0xfff)
343         put_fs_byte(0,(char *) (i++));
如果代码长度加数据长度不再内存页末端,则把剩余内存初始化为0.
344     eip[0] = ex.a_entry;        /* eip, magic happens :-) */
修改eip
345     eip[3] = p;         /* stack pointer */
修改堆栈指针
346     return 0;
347 exec_error2:
348     iput(inode);
349 exec_error1:
350     for (i=0 ; i 351         free_page(page[i]);
352     return(retval);
353 }








你可能感兴趣的:(linux0.11内核源码分析)