杨怡泽 原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
实验
设置断点sys_execeve,并继续
代码执行到了SyS_execve。在QEMU中执行exec,可以看到只能出现两句,没有完全执行完毕。
设置断点load_elf_binary和start_thread,并执行,可以看到代码停在了两个断点处。
在代码停到SyS_execve处时候,list进入代码
1599 old = ACCESS_ONCE(mm->flags);
1600 new = (old & ~MMF_DUMPABLE_MASK) | value;
1601 } while (cmpxchg(&mm->flags, old, new) != old);
1602 }
1603
1604 SYSCALL_DEFINE3(execve,
1605 const char __user *, filename,
1606 const char __user *const __user *, argv,
1607 const char __user *const __user *, envp)
1608 {
这段代码中DEFINE3中的filename对应文件名,argv对应环境变量,envp对应命令行
继续执行,进入do_execve的内部
继续执行直到start_thread,显示的po new_ip中的内存地址是0x8048d0a。同时新开终端在menu文件中执行readelf -h hello命令,可以发现入口点地址同样是0x8048d0a。
继续执行直到代码执行完毕
分析
ELF
Linux的标准可执行格式是ELF,其格式为
:name:type:offset:string:mask:interpreter:flags
exec函数
所有的exec函数都是C库定义的封装例程,并利用execve()系统调用,也是Linux所提供的处理程序执行的唯一系统调用。ELF文件会被默认映射到0x8048000这个地址。
sys_execve()服务例程接受下列参数:
- 可执行文件路径名的地址
- 以NULL结束的字符串指针数组的地址。每个字符串表示一个命令行参数。
- 以NULL结束的字符串指针数组的地址。每个字符串表示一个命令行参数。每个字符串以NAME=value形式表示一个环境变量。
sys_execve()依次执行以下操作:
1. 动态地分配一个linux_binprm数据结构,并用新的可执行文件的数据填充这个结构。
2. 调用path_lookup(),dentry_open()和path_release(),以获得与可执行文件相关的目录项对象,文件对象和索引节点对象。
3. 检查是否可以由当前的进程执行该文件,再检查索引节点的i_writecount字段,以确定可执行文件没有被写入。
4. 在多处理器系统中,调用sched_exec()函数来确定最小负载CPU以执行程序。
5. 调用init_new_context()检查当前进程是否使用自定义局部描述符表。
6. 调用prepare_binprm()函数填充linux_binprm()数据结构。
7. 把文件路径名、命令行参数及环境串拷贝到一个或多个新分配叶框中。
8. 调用search_binary_handler()函数对formats链表进行扫描,并尽力应用每个元素的load_binary方法,把Linux_binprm数据结构传递给这个函数。只要load_binary方法成功应答了文件的可执行格式,对formats的扫描就终止。
9. 如果在formats链表中,就释放所分配的所有页框并返回错误代码。
10. 否则,函数释放linux_binprm数据结构,返回从这个文件可执行格式的load_binary方法中所获得的代码。
如果可执行文件是静态链接,可执行文件对应的load_binary()需要将程序的正文段,数据段,bss段和堆栈段映射到进程线性区,然后把用户态eip寄存器的内容设置为新程序的入口。
总结
程序是以可执行文件的形式放到磁盘上的,可执行文件既包括执行函数的目标代码,也包括这些函数所使用的数据。当装入一个程序的时候,用户可以提供影响程序执行方式的环境变量和命令行参数两种信息。
当execve()终止时,系统调用的代码不复存在,要执行的新成效已经被映射到进程的地址空间。这样程序就可以成功运行了。