【问题标题】:strange output after vfork invoked调用 vfork 后的奇怪输出
【发布时间】:2014-06-15 08:42:47
【问题描述】:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int createproc();
pid_t pid;
int main()
{
    createproc();
    printf("%d\n", pid);
    exit(0);//_exit(0) gives the same result
}
int createproc()
{
    if(!(pid=vfork())) {
        printf("child proc:%d\n", pid);
    }
    else
        printf("parent proc:%d\n", pid);
}

程序的输出如下:

子进程:0

0

父进程:6958

子进程:0

分段错误

据我所知,除非调用 exec 或 exit 函数并且共享堆栈段,否则 vfork 将暂停父进程。 所以这里我有两个问题:

  1. 由于它们共享一个公共地址空间,exit(0) 会影响两个进程吗?如果是这样,怎么做?如果不是,为什么?

  2. 为什么在“parent proc:6958”后面有一行“child proc:0”?我不希望得到像意外行为这样的答案。

此外,通过反汇编,我注意到 vfork 的调用不像正常函数那样表现。没有堆栈平衡: 函数 vfork 的汇编代码转储:

0xb7ed2050 <+0>:        pop    ecx 
=> 0xb7ed2051 <+1>:         mov    edx,DWORD PTR gs:0x6c 
   0xb7ed2058 <+8>:     mov    eax,edx 
   0xb7ed205a <+10>:    neg    eax 
   0xb7ed205c <+12>:    jne    0xb7ed2063 <vfork+19> 
   0xb7ed205e <+14>:    mov    eax,0x80000000 
   0xb7ed2063 <+19>:    mov    gs:0x6c,eax 
   0xb7ed2069 <+25>:    mov    eax,0xbe 
   0xb7ed206e <+30>:    int    0x80 
   0xb7ed2070 <+32>:    push   ecx 
   0xb7ed2071 <+33>:    test   eax,eax 
   0xb7ed2073 <+35>:    je     0xb7ed207c <vfork+44> 
   0xb7ed2075 <+37>:    mov    DWORD PTR gs:0x6c,edx 
   0xb7ed207c <+44>:    cmp    eax,0xfffff001 
   0xb7ed2081 <+49>:    jae    0xb7ed2084 <vfork+52> 
   0xb7ed2083 <+51>:    ret    
   0xb7ed2084 <+52>:    call   0xb7f44d87 <__i686.get_pc_thunk.cx> 
   0xb7ed2089 <+57>:    add    ecx,0xedf77 
   0xb7ed208f <+63>:    mov    ecx,DWORD PTR [ecx-0x104] 
   0xb7ed2095 <+69>:    xor    edx,edx 
   0xb7ed2097 <+71>:    sub    edx,eax 
   0xb7ed2099 <+73>:    add    ecx,DWORD PTR gs:0x0 
   0xb7ed20a0 <+80>:    mov    DWORD PTR [ecx],edx 
   0xb7ed20a2 <+82>:    or     eax,0xffffffff 
   0xb7ed20a5 <+85>:    jmp    0xb7ed2083 <vfork+51> 

它实际上将返回地址弹出到ecx并在系统调用后推回(0xb7ed206e : int 0x80 0xb7ed2070 : push ecx)。最不寻常的是有一个ret指令:0xb7ed2083 : ret

我不熟悉汇编语言,谁能给我解释一下?

【问题讨论】:

标签: c linux disassembly vfork


【解决方案1】:

vfork 之后,您可以在子进程中做的唯一事情是:

  • 将函数的返回值存储到变量中。
  • 致电_exit
  • 调用您的操作系统提供给您的某个函数,该函数的名称以exec 开头。

绝对没有别的。如果您打算做任何其他事情(printf 可能是您可以在vfork 中调用的最糟糕的函数之一,并且绝对算作“其他任何事情”),请不要使用vfork,而是使用fork

原因是在过去,fork 系统调用在复制进程的整个地址空间时可能会非常慢,并且在大多数情况下,fork 紧随其后的是exec*,它抛出了该地址远离空间。于是发明了vfork,新进程不复制地址空间,而是借用父进程的地址空间并挂起,直到调用exitexec

因此,您的代码首先会为子进程(但父进程的地址空间)中的 printf 缓冲区分配内存。这可能不安全,也可能不会让家长感到困惑。然后createproc函数返回可能会覆盖父级返回时将使用的堆栈帧,然后它再次调用printf,这一次肯定会破坏堆栈帧并进一步混淆printf的内部,然后它从@返回987654336@ 刷新 printf 缓冲区,破坏父级中 main 使用的堆栈帧,可能释放 printf 工作所必需的许多状态和 stdio 状态,然后退出。该出口解除了父级的挂起,这可能会崩溃,因为它返回的堆栈帧已经被破坏了。如果它没有被破坏并且它设法调用了 printf,那么 printf 中的内部状态就会被破坏,如果它没有被破坏,那么 stdout 描述符已经被子进程释放并且肯定会崩溃。

换句话说,如果你的代码中唯一在子进程中运行的不是_exitexec,那么你的代码就没有机会工作,因为这不是vfork 可以处理的。

【讨论】:

  • 由于孩子和父母共享同一个堆栈,孩子的堆栈是否从父母过去的位置增长?如果是这样,由于调用函数时存在堆栈平衡,我认为堆栈帧不会被覆盖。为了防止这种情况,我删除了 main 中的 printf 函数,除了原来的第二行之外,我得到了相同的结果。
  • 从函数返回会弄乱堆栈,因为调用函数(在本例中为 main)可以随意使用它做任何事情。并且由于您正在从 main 调用其他函数(无论是 printfexit 甚至是 _exit),这些调用将覆盖父用于调用 createproc 的堆栈帧。从 main 返回会产生相同的效果,因为这将调用许多调用来清理状态的函数(您可以想象,无论调用 main 都会执行类似 exit(main(...)) 的操作。除了手册说的内容之外,您没有其他安全的事情可以做.
  • 哦,是的,谢谢您的回答。那你知道为什么“child proc: 0”会输出两次吗(我注意到createproc函数输入了两次!!)?我认为当 createproc 返回时(在父 proc 中第二次),堆栈已损坏。那怎么会这样呢?
  • 我已经通过拆解了解了它的工作原理。简而言之,永远不要从调用 vfork 的地方返回。在我的程序中,当调用 exit 时,它恰好推送了 createproc 的第一条指令的地址,因此函数 createproc 运行了两次。最后,由于堆栈已损坏,肯定是分段错误!感谢您的所有回答。
【解决方案2】:

我只能引用man vfork:

(来自 POSIX.1)vfork() 函数与 fork(2) 的效果相同, 除非由 vfork() 创建的进程修改了除用于存储 从 vfork() 返回值,或从调用 vfork() 的函数返回,或在成功调用 _exit(2) 或 exec(3) 系列函数之一之前调用任何其他函数。

这里的重要部分是:

如果子进程在调用 _exit 之前从调用 vfork() 的函数返回,则行为未定义

所以,修改你的代码:

int createproc()
{
    if(!(pid=vfork())) {
        _exit(6);
    }
    else
        printf("parent proc:%d\n", pid);
 }

以避免未定义的行为。因此,您必须特别退出应用程序。 (是的,6 只是一个值)

当然,您也可以调用 exec 家族的函数,如果这是您想要的(即:运行另一个应用程序)。

【讨论】:

  • 此代码仍未定义,因为您正在调用“printf”。那确实是 _exit 或 exec* 以外的函数
  • @perh 你说得很好,我只是假设 printf 不会修改变量,但你永远不知道......
  • @fritzone,printf 是否修改变量都没有关系——你仍在调用它,这在手册页中是明确禁止的。
  • @davmac 和 perv :没错,我只是在阅读这个人时在精神上错过了这句话。修改了答案。
猜你喜欢
  • 2018-08-23
  • 2013-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多