下面我们来具体看一下
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 }