简体   繁体   中英

strange output after vfork invoked

#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);
}

the output of the program is below:

child proc:0

0

parent proc:6958

child proc:0

Segmentation fault

As I know, vfork will suspend the parent process unless the exec or exit function is called and the stack segment is shared. So Here I have two questions:

  1. Since they share a common address space, does exit(0) affect both process? If so, how? If not, why?

  2. Why there is a line of "child proc:0" after "parent proc:6958"? I don't expect an answer like unexpected behavior.

Besides, through disassemble, I notice that the call of vfork didn't behave as normal function. There is no stack balance: Dump of assembler code for function 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> 

It actually pop the return address into ecx and push back after the system call(0xb7ed206e <+30>: int 0x80 0xb7ed2070 <+32>: push ecx). With the most unusual thing that there is a ret instruction: 0xb7ed2083 <+51>: ret

I am not familiar with assemble language, can anyone explain it to me?

The only things you're allowed to do in the child process after vfork is:

  • store the return value of the function into a variable.
  • call _exit
  • call some function provided to you by your operating system that has a name that starts with exec .

Absolutely nothing else. If you intend to do anything else (and printf is probably one of the worst functions you can call inside vfork and definitely counts as "anything else"), do not use vfork , use fork instead.

The reason for this is that in old times the fork system call could be pretty slow when it copied the whole address space of the process and in most cases fork was immediately followed by exec* which threw that address space away. So vfork was invented that instead of copying the address space, the new process borrows the address space of the parent process and suspends it until exit or exec is called.

So what your code does is first it does memory allocations for printf buffers in the child process (but the address space of the parent). This may or may not be unsafe and confuse the parent. Then the createproc function returns possibly overwriting the stack frame that the parent will use when it comes back, then it calls printf again, this time definitely destroying the stack frame and further confusing the internals of printf, then it returns from main which flushes the printf buffers, destroys the stack frame that main in the parent used, probably frees a lot of state and stdio state that's necessary for printf to work, then it exits. That exit unsuspends the parent, which probably crashes because the stack frame it returns to is already wrecked. If it isn't wrecked and somehow it manages to call printf, the internal state in printf is wrecked and if that isn't wrecked, the stdout descriptor has already been freed by the child and that definitely crashes.

In other words, if the only thing in your code that runs in the child isn't _exit or exec , then your code has no chance of working because that's not something that vfork can handle.

I just can quote the man vfork :

(From POSIX.1) The vfork() function has the same effect as fork(2), except that the behavior is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(), or returns from the function in which vfork() was called, or calls any other function before successfully calling _exit(2) or one of the exec(3) family of functions.

The important part here is:

the behavior is undefined if the child returns from the function in which vfork() was called before calling _exit

So, modify your code:

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

to avoid the undefined behaviour. So you specifically have to exit the application. (Yes, the 6 is just a value)

of course, you can call also a function from the family of exec if that is what you desire (ie: to run another application).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM