1

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.

Community
  • 1
  • 1
xjas
  • 75
  • 2
  • 11
  • 1
    Exactly what is your question? You have read the manual: glibc does cache the value returned by the getpid-syscall. Obviously this cache must be re-set after fork(2) in the child-process. – Lorinczy Zsigmond Apr 06 '17 at 08:43
  • Thanks for your answer. What's the mechanism of getpid using dlsym? Why the realization of getpid is different from the others? That' what I want to know. Thanks. – xjas Apr 06 '17 at 14:07

1 Answers1

4

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)
nos
  • 223,662
  • 58
  • 417
  • 506
  • Thanks for your detailed answer. Maybe I understand your meaning. After got your message, I just checked the version of my ubuntu glibc version with ldd, with which I compile the kernel. As you indicated, it's glibc-2.23, and I found the code you have pointed out in glibc-2.23. So the pizzle should be solved. However, I still can't figure out why the realization of getpid is different from time or the other api that needs to enter the kernel mode. Also I don't konw the mechanism of dlsym used by getpid. If possible, can you provide some references to the detail? I'd like to dig deeper, Tks. – xjas Apr 06 '17 at 13:44
  • @a-thorn The pid of a process never ever changes. Therefore glibc can get the pid from the kernel once, and then cache it for the future, without having to perform another syscall. I hope it is obvious why a call to time() can not be cached in that same manner- and the same goes for most other syscalls - it does not make sense to cache their results. The code you posted that looks up getpid using the dlsym() function seems to come from `./elf/restest2.c` , this file is part of the testsuite for glibc, not the getpid() implementation. – nos Apr 06 '17 at 14:07
  • Thanks for your answer, I admire your efficiency. Yes, now I finally know the difference. Please forgive my being stupid. Now I konw it's testsuite for getpid, but I really can't find the getpid() implementation. Can you pointed out it for me? I use getpid, but I just find the __getpid implementation. Also, I understand time because time() uses int $0x80 to enter the kernel mode, then the kernel find the implementation sys_time. As to getpid(), I'm confused. Thans for your attention. – xjas Apr 06 '17 at 14:20
  • Maybe I understand your meaning, because getpid is a alias of __getpid, when getpid is called the first time, __getpid is called in fact, because it's in libc, so the INTERNAL_SYSCALL is called. The realization of macro INTERNAL_SYSCALL is also embedded asm. If it was called again, the cache is used. Thanks for your consideration. – xjas Apr 06 '17 at 14:35
  • 2
    All that happens is that your getpid() call always ends up in `__getpid ()` and in here you see it does `pid_t result = THREAD_GETMEM (THREAD_SELF, pid)` , This is the cached value. If that call returns <= 0, it calls `really_getpid ` which calls the actual kernel syscall and stores/caches the return value from the syscall. The really_getpid() function performs `INTERNAL_SYSCALL (getpid, err, 0)` , which does `int $0x80` or whatever is needed on the platform to do a kernel syscall. – nos Apr 06 '17 at 16:44
  • Thanks for your patience. I should have known about the mechanism with your help. Maybe I need to be more patient to go deeper. Thank you again. – xjas Apr 07 '17 at 00:21