本文内容写于2023年12月18日,为JOS Lab的分析笔记。今正式公开。
如果sys_exofork()不内联,在ret指令前,%esp为0xeebfdf34,%ebp为0xeebfdf70(实际上是fork()的%ebp,因为sys_exofork()没有push %ebp和mov %esp, %ebp)。
新建的子环境会卡在ret之前,等待iret从系统调用返回。而父环境继续fork()。在调用sys_page_map()进行COW映射之前,%esp会降至%0xeebfdf28,即父环境会覆盖子环境的返回地址(因为此时两者公用栈)。仔细来说,是在调用sys_page_map()之前push %edi传入参数时。这个参数是dstva。
而如果sys_exo_fork()是内联的,就不会有以上问题。fork()调用的叶函数不会修改fork()的返回地址,而fork()完成后,已经完成了栈的COW映射,父环境的其他过程不会修改子环境fork()的返回地址。
事实上,只考虑测试样例的话,我已经设计出了非内联版本的sys_exofork()。原理很简单:把返回地址搬到一个不会被修改的地方。
c
static envid_t __attribute__((noinline))
sys_exofork(void)
{
envid_t ret;
asm volatile("movl (%esp), %eax\n"
"movl %eax, -0x80(%esp)\n");
asm volatile("int %2" : "=a"(ret) : "a"(SYS_exofork), "i"(T_SYSCALL));
asm volatile("push %eax\n"
"movl -0x7c(%esp), %eax\n"
"movl %eax, 0x4(%esp)\n"
"pop %eax\n");
return ret;
}或者:
c
asm(".type sys_exofork, @function\n"
"sys_exofork:\n"
"\tmovl (%esp), %eax\n"
"\tmovl %eax, -0x80(%esp)\n"
"\tmovl $7, %eax\n"
"\tint $48\n"
"\tpush %eax\n"
"\tmovl -0x7c(%esp), %eax\n"
"\tmovl %eax, 0x4(%esp)\n"
"\tpop %eax\n"
"\tret\n");