linuxlinux-kernelsystem-callsglibcvdso

what's the getpid work procedure in glibc?


The situation is as below:

I am trying to do the project which hacks the kernel in github. Kernel version is linux-3.18.6.

QEMU is used to simulate the environment.

In my application, I try to understand the syscall procedure by follow them.The way to complete my aim is just like the shell program. I just create some commands to run the relative syscall. Maybe it's simple through the picture. some commands

Code is simple as follows:

1 Use API getpid.

int Getpid(int argc, char **argv)
{
    pid_t pid;
    pid = getpid();
    printf("current process's pid:%d\n",pid);
    return 0;
}

2 Use int $0x80 directly.

int GetpidAsm(int argc, char **argv)
{
    pid_t pid;
    asm volatile(
    "mov $20, %%eax\n\t"
    "int $0x80\n\t"
    "mov %%eax, %0\n\t"
    :"=m"(pid)
    );
    printf("current process's pid(ASM):%d\n",pid);
    return 0;
}

Because my application just run in the process with pid 1, so every time I type command getpid, it returns 1. Of course that's true.

The weird thing is that when I use gdb to debug the syscall process, it stops at the berakpoint sys_getpid only once when I type getpid to execute. When I do it again and again, it just outputs without stopping.

Obviously, the use of int $0x80 is absolutely correct as I understand.

To fix the problem, I did some research. I download the glibc source(glibc-2.25) code to see how the api getpid wraps int $0x80. Unfortunately, it wasn't there or I just didn't find the right position.

some code in glibc.

pid_t getpid(void)
{
  pid_t (*f)(void);
  f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid");
  if (f == NULL)
    error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror ());
  return (pid2 = f()) + 26;
}

If I got the wrong code, please tell me,tks.

As the code indicates, the definition of getpid is not contained in glibc. After read some data, someone said the VDSO....

Notice that, AFAIK, a significant part of the cost of simple syscalls is going from user-space to kernel and back. Hence, for some syscalls (probably gettimeofday, getpid ...) the VDSO might avoid even that (and technically might avoid doing a real syscall).

In the man getpid pgae:

C library/kernel differences Since glibc version 2.3.4, the glibc wrapper function for getpid() caches PIDs, so as to avoid additional system calls when a process calls getpid() repeatedly. Normally this caching is invisible, but its correct operation relies on support in the wrapper functions for fork(2), vfork(2), and clone(2): if an application bypasses the glibc wrappers for these system calls by using syscall(2), then a call to getpid() in the child will return the wrong value (to be precise: it will return the PID of the parent process). See also clone(2) for dis‐ cussion of a case where getpid() may return the wrong value even when invoking clone(2) via the glibc wrapper function.

Though there exit so many explain, I can't figure out the work procedure of API getpid.

As a contrast, API time is easy to understand. The definition of time:

time_t
time (time_t *t)
{
  INTERNAL_SYSCALL_DECL (err);
  time_t res = INTERNAL_SYSCALL (time, err, 1, NULL);
  /* There cannot be any error.  */
  if (t != NULL)
    *t = res;
  return res;
}

then,

#define INTERNAL_SYSCALL(name, err, nr, args...)            \
    internal_syscall##nr ("li\t%0, %2\t\t\t# " #name "\n\t",    \
                  "IK" (SYS_ify (name)),            \
                  0, err, args)

Finally, it's embedded asm, the normal way to use kernel source.

#define internal_syscall1(v0_init, input, number, err, arg1)        \
({                                  \
    long _sys_result;                       \
                                    \
    {                               \
    register long __s0 asm ("$16") __attribute__ ((unused))     \
      = (number);                           \
    register long __v0 asm ("$2");                  \
    register long __a0 asm ("$4") = (long) (arg1);          \
    register long __a3 asm ("$7");                  \
    __asm__ volatile (                      \
    ".set\tnoreorder\n\t"                       \
    v0_init                             \
    "syscall\n\t"                           \
    ".set reorder"                          \
    : "=r" (__v0), "=r" (__a3)                  \
    : input, "r" (__a0)                     \
    : __SYSCALL_CLOBBERS);                      \
    err = __a3;                         \
    _sys_result = __v0;                     \
    }                               \
    _sys_result;                            \
})

Can somebody explain clearly how the API getpid works? Why the getpid just trap into the syscall sys_getpid only once? Some references is admired if possible.

Thanks for your help.


Solution

  • First of all note that that the glibc source code is nigh impossible to navigate.

    The documentation states that getpid() caches its result, as you have noticed. The code you have found that looks like

    pid_t getpid(void)
    {
      pid_t (*f)(void);
      f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid");
      if (f == NULL)
        error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror ());
      return (pid2 = f()) + 26;
    }
    

    is merely a wrapper. It looks up a getpid symbol, and calls that function. That function is what you need to find. It is aliased to the __getpid() function which you will find in the sysdeps/unix/sysv/linux/getpid.c file, and is also shown in the bottom of this post.

    Now - you might be looking at glibc source code that does not match your current glibc - there was a big change regarding exactly the getpid() caching in November 2016 in this commit , as far as I can tell that change would be part of glibc-2.25 released in February 2017

    The older getpid() implementation that cached its value to avoid calling the getpid() syscall more than once, can be seen here: http://repo.or.cz/glibc.git/blob/93eb85ceb25ee7aff432ddea0abf559f53d7a5fc:/sysdeps/unix/sysv/linux/getpid.c and looks like

    static inline __attribute__((always_inline)) pid_t
    really_getpid (pid_t oldval)
    {
      if (__glibc_likely (oldval == 0))
        {
          pid_t selftid = THREAD_GETMEM (THREAD_SELF, tid);
          if (__glibc_likely (selftid != 0))
        return selftid;
        }
    
      INTERNAL_SYSCALL_DECL (err);
      pid_t result = INTERNAL_SYSCALL (getpid, err, 0);
    
      /* We do not set the PID field in the TID here since we might be
         called from a signal handler while the thread executes fork.  */
      if (oldval == 0)
        THREAD_SETMEM (THREAD_SELF, tid, result);
      return result;
    }
    #endif
    
    pid_t
    __getpid (void)
    {
    #if !IS_IN (libc)
      INTERNAL_SYSCALL_DECL (err);
      pid_t result = INTERNAL_SYSCALL (getpid, err, 0);
    #else
      pid_t result = THREAD_GETMEM (THREAD_SELF, pid);
      if (__glibc_unlikely (result <= 0))
        result = really_getpid (result);
    #endif
      return result;
    }
    
    libc_hidden_def (__getpid)
    weak_alias (__getpid, getpid)
    libc_hidden_def (getpid)