概念总结
1.可执行程序是怎么得来的
C代码——预处理——汇编代码——目标代码——可执行文件
可执行文件的格式
可执行文件最初为A.out格式,后来演化为COFF格式,再后来变成PE(windows系统)和ELF(linux系统)。ELF:executable and linkable format,即可执行可链接格式。
可执行程序的执行环境
- 命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
- $ ls -l /usr/bin 列出/usr/bin下的目录信息
- Shell本身不限制命令行参数的个数, 命令行参数的个数受限于命令自身
- 例如,int main(int argc, char *argv[])
- 又如, int main(int argc, char *argv[], char *envp[])
- Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
- int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
- 库函数exec*都是execve的封装例程
关于几个问题的回答
1.新的可执行程序是从哪里开始执行的?
默认elf文件从0x8048000出开始加载,前面是elf头部信息,大小一般各不相同。实际的入口在0x8048x000(x为不定值),即程序的入口点地址。
2.为什么execve系统调用返回后新的可执行程序能顺利执行?
在创建一个新的用户态堆栈时,实际上是把命令行参数内容和环境变量的内容通过指针传递到系统调用内核处理函数,然后内核处理函数在创建一个新的可执行程序的用户态堆栈时将参数拷贝到户态堆栈,以此来初始化新的可执行程序的上下文环境。先函数调用参数传递,再系统调用参数传递。
命令行参数和环境串都放在用户态堆栈中
execve和fork都是特殊的系统调用,陷入到内核态在返回到用户态继续执行。fork比较特殊,父进程和一般的系统调用一样,子进程从ret_from_fork开始执行返回到用户态。execve陷入到内核态,用加载的新的可执行文件将当前进程的可执行程序覆盖,当其返回时已经不是原来的可执行程序,而是新的可执行程序了。
3.对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?
静态链接方法:#pragma comment(lib, "test.lib") ,静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来
静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库
load_elf_binary中调用了函数start_thread,其中的参数pt_regs就是内核堆栈的栈底
start_thread(regs, elf_entry, bprm->p)
静态链接的elf_entry就是可执行文件的entry,新的程序在返回用户态之前需要修改int 0x80亚茹内核堆栈的eip
动态链接方法:LoadLibrary()/GetProcessAddress()和FreeLibrary(),使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序。