您的位置:首页 > 运维架构 > Linux

Linux内核如何装载和启动一个可执行程序

2016-04-09 19:06 513 查看

杨明辉 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、实验过程

1. 打开控制台,进入Linux目录下,然后输入命令rm menu -rf 删除menu,然后输入命令git clone https://github.com/mengning/menu.git 重新克隆一个menu,实验结果如图1所示。



图1
2. 输入make rootfs对程序进行编译,并启动MENUOS,我们可以看看在MENUOS中多了一个命令exec,实验结果如图2所示。



图2

3.  本次编译时使用了静态编译,并且自动的加载可执行hello可执行文件,Makefile文件的内容如图3所示。



图3

4.输入命令qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S启动MENUOS,然后打开gdb,输入命令file ../linux-3.18.6/vmlinux加载符号表,然后如何命令target remote :1234连接MENUOS。实验结果如图4所示。



图4

5.在gdb中输入命令 b execve,b load_elf_binary,b start_thread设置断点,然后利用gdb进行跟踪调试。实验结果如下图所示。



图5
跟踪调试:



图6
继续:



图7



图8

二、分析Linux内核装载和启动一个可执行程序的过程

1.使用下面的命令来创建一个可执行文件,程序的编译链接的过程如图9所示。
<span style="font-size:14px;">shiyanlou:~/ $ cd Code                                                [9:27:05]
shiyanlou:Code/ $ vi hello.c                                          [9:27:14]
shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32                    [9:34:55]
shiyanlou:Code/ $ vi hello.cpp                                        [9:35:04]
shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32      [9:35:21]
shiyanlou:Code/ $ vi hello.s                                          [9:35:28]
shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32         [9:35:58]
shiyanlou:Code/ $ vi hello.o                                          [9:38:44]
shiyanlou:Code/ $ gcc -o hello hello.o -m32                           [9:39:37]
shiyanlou:Code/ $ vi hello                                            [9:39:44]
shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static            [9:40:21]
shiyanlou:Code/ $ ls -l                                               [9:41:13]</span>




图9

2. ELF文件的头部如下所示。
shiyanlou:sharelib/ $ ldd main                                       [21:25:56]
linux-gate.so.1 =>  (0xf774e000) # 这个是vdso - virtual DSO:dynamically shared object,并不存在这个共享库文件,它是内核的一部分,为了解决libc与新版本内核的系统调用不同步的问题,linux-gate.so.1里封装的系统调用与内核支持的系统调用完全匹配,因为它就是内核的一部分嘛。而 libc里封装的系统调用与内核并不完全一致,因为它们各自都在版本更新。
libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)
libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)
libc.so.6 => /lib32/libc.so.6 (0xf7588000)
/lib/ld-linux.so.2 (0xf774f000)
shiyanlou:sharelib/ $ ldd /lib32/libc.so.6                         [21:37:00]
/lib/ld-linux.so.2 (0xf779e000)
linux-gate.so.1 =>  (0xf779d000)
# readelf -d 也可以看依赖的so文件
shiyanlou:sharelib/ $ readelf -d main                              [21:28:04]
Dynamic section at offset 0xf04 contains 26 entries:
0x00000001 (NEEDED)                     共享库:[libshlibexample.so]
0x00000001 (NEEDED)                     共享库:[libdl.so.2]
0x00000001 (NEEDED)                     共享库:[libc.so.6]
0x0000000c (INIT)                       0x80484f0
0x0000000d (FINI)                       0x8048804
0x00000019 (INIT_ARRAY)                 0x8049ef8


3.本次实验在MENUOS中新增了一个exec命令,用于创建一个新的进程,新增了一个函数,函数的代码及分析如下。
    代码:
int Exec(int argc, char *argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < 0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/*	 child process 	*/
printf("This is Child Process!\n");
execlp("/hello","hello",NULL);
}
else
{
/* 	parent process	 */
printf("This is Parent Process!\n");
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}
    分析:
    在程序中,我们可以看到,首先调用了fork()函数来创建一个子进程,然后在子进程中执行调用execlp()函数来执行hello程序。execlp函数是exec函数族中的一个,这个函数族提供了一个在进程中启动另外一个程序执行的方法。它根据执行的文件名或目录名找到可执行文件,并用它来替代当前的进程的执行映像。换句话说,execlp函数调用并没有生成新的进程,一个进程一旦调用exec函数,它本身就“死亡”了,系统把代码段替换成新程序的代码,放弃原有的数据段和堆栈段,并为新程序分配新的数据段和堆栈段,唯一保留的就是进程的ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。

4. 可执行程序的可执行环境。

1.在启动一个可执行程序时,会将输入的命令行参数和环境串都放在用户态堆栈中。
2.动态链接分为可执行程序装载时动态链接和运行时动态链接。
3.可执行程序装载时的动态链接过程如下。

1.输入命令gcc -shared dllibexample.c -o libdlibexample.so -m32生成链接库。
2.然后在程序中使用#include导入该头文件即可使用该链接库中的函数。
3.使用命令gcc main.c -o main -L /path/to/your/dir -l shlibexample -ldl -m32执行。
4.运行时动态链接过程如下。

1.同样使用使用上述命令生成动态链接库。

2.在程序中动态调用,调用代码及分析如下

void * handle = dlopen("libdllibexample.so",RTLD_NOW);
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return FAILURE;
}
int (*func)(void);
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi");
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func();
dlclose(handle);

3.首先,我们使用函数dlopen打开动态链接库,然后定义一个函数指针,然后通过函数dlsym来找到我们想要的函数并附值给上面定义的函数指针,最后利用函数指针调用该函数。

5.调用exec系统调用装载一个可执行程序的过程。

1. 首先在用户态调用exec*函数,对应的内核入口为sys_execve.

2. 在sys_execve中调用do_execve函数,

3. 在do_execve函数中调用open_execve打开可执行文件。

4. 调用copy_strings_kernel从系统空间拷贝。

5. 调用copy_strings从用户空间拷贝。

6. 调用exec_binprm函数,然后调用search_binary_handler函数。

7. 调用list_for_each_entry来搜寻formats队列中的成员来执行。

8. 调用copy_thread来附值进程控制快的相关信息。

9. 最后调用start_thread函数来设置新的程序执行的开始地址。

6. do_execve_commen关键代码和解析如下。

430static int do_execve_common(struct filename *filename,
1431 struct user_arg_ptr argv,
1432 struct user_arg_ptr envp)
1433{
1434 struct linux_binprm *bprm;
1435 struct file *file;
1436 struct files_struct *displaced;
1437 int retval;
1438
1439 if (IS_ERR(filename))
1440 return PTR_ERR(filename);
1441
1442 /*
1443 * We move the actual failure in case of RLIMIT_NPROC excess from
1444 * set*uid() to execve() because too many poorly written programs
1445 * don't check setuid() return code. Here we additionally recheck
1446 * whether NPROC limit is still exceeded.
1447 */
1448 if ((current->flags & PF_NPROC_EXCEEDED) &&
1449 atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) {
1450 retval = -EAGAIN;
1451 goto out_ret;
1452 }
1453
1454 /* We're below the limit (still or again), so we don't want to make
1455 * further execve() calls fail. */
1456 current->flags &= ~PF_NPROC_EXCEEDED;
1457
1458 retval = unshare_files(&displaced);
1459 if (retval)
1460 goto out_ret;
1461
1462 retval = -ENOMEM;
1463 bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
1464 if (!bprm)
1465 goto out_files;
1466
1467 retval = prepare_bprm_creds(bprm);
1468 if (retval)
1469 goto out_free;
1470
1471 check_unsafe_exec(bprm);
1472 current->in_execve = 1;
1473
1474 file = do_open_exec(filename);
1475 retval = PTR_ERR(file);
1476 if (IS_ERR(file))
1477 goto out_unmark;
1478
1479 sched_exec();
1480
1481 bprm->file = file;
1482 bprm->filename = bprm->interp = filename->name;
1483
1484 retval = bprm_mm_init(bprm);
1485 if (retval)
1486 goto out_unmark;
1487
1488 bprm->argc = count(argv, MAX_ARG_STRINGS);
1489 if ((retval = bprm->argc) < 0)
1490 goto out;
1491
1492 bprm->envc = count(envp, MAX_ARG_STRINGS);
1493 if ((retval = bprm->envc) < 0)
1494 goto out;
1495
1496 retval = prepare_binprm(bprm);
1497 if (retval < 0)
1498 goto out;
1499
1500 retval = copy_strings_kernel(1, &bprm->filename, bprm);
1501 if (retval < 0)
1502 goto out;
1503
1504 bprm->exec = bprm->p;
1505 retval = copy_strings(bprm->envc, envp, bprm);
1506 if (retval < 0)
1507 goto out;
1508
1509 retval = copy_strings(bprm->argc, argv, bprm);
1510 if (retval < 0)
1511 goto out;
1512
1513 retval = exec_binprm(bprm);
1514 if (retval < 0)
1515 goto out;
1516
1517 /* execve succeeded */
1518 current->fs->in_exec = 0;
1519 current->in_execve = 0;
1520 acct_update_integrals(current);
1521 task_numa_free(current);
1522 free_bprm(bprm);
1523 putname(filename);
1524 if (displaced)
1525 put_files_struct(displaced);
1526 return retval;
1527
1528out:
1529 if (bprm->mm) {
1530 acct_arg_size(bprm, 0);
1531 mmput(bprm->mm);
1532 }
1533
1534out_unmark:
1535 current->fs->in_exec = 0;
1536 current->in_execve = 0;
1537
1538out_free:
1539 free_bprm(bprm);
1540
1541out_files:
1542 if (displaced)
1543 reset_files_struct(displaced);
1544out_ret:
1545 putname(filename);
1546 return retval;
1547}

7. list_for_each_entry关键代码如下

list_for_each_entry(fmt, &formats, lh) {
1370 if (!try_module_get(fmt->module))
1371 continue;
1372 read_unlock(&binfmt_lock);
1373 bprm->recursion_depth++;
1374 retval = fmt->load_binary(bprm);
1375 read_lock(&binfmt_lock);
1376 put_binfmt(fmt);
1377 bprm->recursion_depth--;
1378 if (retval < 0 && !bprm->mm) {
1379 /* we got to flush_old_exec() and failed after it */
1380 read_unlock(&binfmt_lock);
1381 force_sigsegv(SIGSEGV, current);
1382 return retval;
1383 }
1384 if (retval != -ENOEXEC || !bprm->file) {
1385 read_unlock(&binfmt_lock);
1386 return retval;
1387 }
1388 }

8.  copy_thread关键代码

132int copy_thread(unsigned long clone_flags, unsigned long sp,
133	unsigned long arg, struct task_struct *p)
134{
135	struct pt_regs *childregs = task_pt_regs(p);
136	struct task_struct *tsk;
137	int err;
138
139	p->thread.sp = (unsigned long) childregs;
140	p->thread.sp0 = (unsigned long) (childregs+1);
141	memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
142
143	if (unlikely(p->flags & PF_KTHREAD)) {
144		/* kernel thread */
145		memset(childregs, 0, sizeof(struct pt_regs));
146		p->thread.ip = (unsigned long) ret_from_kernel_thread;
147		task_user_gs(p) = __KERNEL_STACK_CANARY;
148		childregs->ds = __USER_DS;
149		childregs->es = __USER_DS;
150		childregs->fs = __KERNEL_PERCPU;
151		childregs->bx = sp;	/* function */
152		childregs->bp = arg;
153		childregs->orig_ax = -1;
154		childregs->cs = __KERNEL_CS | get_kernel_rpl();
155		childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156		p->thread.io_bitmap_ptr = NULL;
157		return 0;
158	}
159	*childregs = *current_pt_regs();
160	childregs->ax = 0;
161	if (sp)
162		childregs->sp = sp;
163
164	p->thread.ip = (unsigned long) ret_from_fork;
165	task_user_gs(p) = get_user_gs(current_pt_regs());
166
167	p->thread.io_bitmap_ptr = NULL;
168	tsk = current;
169	err = -ENOMEM;
170
171	if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
172		p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
173						IO_BITMAP_BYTES, GFP_KERNEL);
174		if (!p->thread.io_bitmap_ptr) {
175			p->thread.io_bitmap_max = 0;
176			return -ENOMEM;
177		}
178		set_tsk_thread_flag(p, TIF_IO_BITMAP);
179	}
180
181	err = 0;
182
183	/*
184	 * Set a new TLS for the child thread?
185	 */
186	if (clone_flags & CLONE_SETTLS)
187		err = do_set_thread_area(p, -1,
188			(struct user_desc __user *)childregs->si, 0);
189
190	if (err && p->thread.io_bitmap_ptr) {
191		kfree(p->thread.io_bitmap_ptr);
192		p->thread.io_bitmap_max = 0;
193	}
194	return err;
195}


 9.   start_thread关键代码

start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
set_user_gs(regs, 0);
regs->fs		= 0;
regs->ds		= __USER_DS;
regs->es		= __USER_DS;
regs->ss		= __USER_DS;
regs->cs		= __USER_CS;
regs->ip		= new_ip;
regs->sp		= new_sp;
regs->flags		= X86_EFLAGS_IF;
/*	 * force it to the iret return path by making it look as if there was	 * some work pending.
*/
set_thread_flag(TIF_NOTIFY_RESUME);
}


三、总结

1. exec函数族用于在fork()函数创建的子进程中加载一个新的可执行程序。
2. exec函数加载的可执行程序会覆盖掉调用进程的代码段和数据段。
3. 所有新加载的可执行程序映射de开始执行的地址为ox8048000。
4. 在start_thread函数中设置新的可执行程序的入口地址。

   
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: