glibc 中的getpid 工作程序是什么?
what's the getpid work procedure in glibc?
情况如下:
我正在尝试对 github 中的内核进行破解 project。内核版本为linux-3.18.6.
QEMU用于模拟环境
在我的应用程序中,我尝试通过遵循 them.The 的方式来理解系统调用过程,以完成我的目标,就像 shell 程序一样。我只是为 运行 相关系统调用创建了一些命令。也许通过图片就很简单了。
some commands
代码简单如下:
1 使用 API getpid.
int Getpid(int argc, char **argv)
{
pid_t pid;
pid = getpid();
printf("current process's pid:%d\n",pid);
return 0;
}
2 直接使用 int $0x80。
int GetpidAsm(int argc, char **argv)
{
pid_t pid;
asm volatile(
"mov , %%eax\n\t"
"int [=11=]x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(pid)
);
printf("current process's pid(ASM):%d\n",pid);
return 0;
}
因为我的应用程序只是 运行 在 pid 1 的进程中,所以每次我输入命令 getpid 时,它 returns 1。当然是这样。
奇怪的是,当我使用gdb 调试系统调用进程时,它只在我键入getpid 执行时停在berakpoint sys_getpid 一次。当我一遍又一遍的时候,它就不停地输出。
显然,据我所知,int $0x80 的使用是绝对正确的。
为了解决这个问题,我做了一些研究。
我下载了 glibc 源代码 (glibc-2.25) 以查看 api getpid 如何包装 int $0x80。不幸的是,它不在那里,或者我只是没有找到合适的位置。
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;
}
如果我的代码有误,请告诉我,谢谢。
如代码所示,glibc中没有getpid的定义。看了一些资料,有人说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).
在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.
虽然有这么多的解释,我还是搞不清楚APIgetpid的工作过程。
作为对比,API时间就很好理解了。
时间定义:
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;
}
那么,
#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)
最后是嵌入式asm,内核源码的正常使用方式
#define internal_syscall1(v0_init, input, number, err, arg1) \
({ \
long _sys_result; \
\
{ \
register long __s0 asm ("") __attribute__ ((unused)) \
= (number); \
register long __v0 asm (""); \
register long __a0 asm ("") = (long) (arg1); \
register long __a3 asm (""); \
__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; \
})
谁能解释清楚API getpid 是如何工作的?为什么 getpid 只陷入系统调用 sys_getpid 一次?如果可能,请推荐一些参考资料。
感谢您的帮助。
首先请注意,glibc 源代码几乎无法导航。
正如您所注意到的,文档指出 getpid() 缓存了它的结果。
您发现的代码看起来像
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;
}
只是一个包装器。它查找 getpid
符号,并调用该函数。
该功能是您需要找到的。它是 __getpid() 函数的别名,您可以在 sysdeps/unix/sysv/linux/getpid.c
文件中找到该函数,并且也显示在此 post.
的底部
现在 - 您可能正在查看与您当前的 glibc 不匹配的 glibc 源代码 - 据我所知,2016 年 11 月 this commit 中的 getpid() 缓存发生了很大变化告诉更改将成为 2017 年 2 月发布的 glibc-2.25 的一部分
旧的 getpid() 实现缓存了它的值以避免多次调用 getpid() 系统调用,可以在这里看到:
http://repo.or.cz/glibc.git/blob/93eb85ceb25ee7aff432ddea0abf559f53d7a5fc:/sysdeps/unix/sysv/linux/getpid.c 看起来像
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)
情况如下:
我正在尝试对 github 中的内核进行破解 project。内核版本为linux-3.18.6.
QEMU用于模拟环境
在我的应用程序中,我尝试通过遵循 them.The 的方式来理解系统调用过程,以完成我的目标,就像 shell 程序一样。我只是为 运行 相关系统调用创建了一些命令。也许通过图片就很简单了。 some commands
代码简单如下:
1 使用 API getpid.
int Getpid(int argc, char **argv)
{
pid_t pid;
pid = getpid();
printf("current process's pid:%d\n",pid);
return 0;
}
2 直接使用 int $0x80。
int GetpidAsm(int argc, char **argv)
{
pid_t pid;
asm volatile(
"mov , %%eax\n\t"
"int [=11=]x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(pid)
);
printf("current process's pid(ASM):%d\n",pid);
return 0;
}
因为我的应用程序只是 运行 在 pid 1 的进程中,所以每次我输入命令 getpid 时,它 returns 1。当然是这样。
奇怪的是,当我使用gdb 调试系统调用进程时,它只在我键入getpid 执行时停在berakpoint sys_getpid 一次。当我一遍又一遍的时候,它就不停地输出。
显然,据我所知,int $0x80 的使用是绝对正确的。
为了解决这个问题,我做了一些研究。 我下载了 glibc 源代码 (glibc-2.25) 以查看 api getpid 如何包装 int $0x80。不幸的是,它不在那里,或者我只是没有找到合适的位置。
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;
}
如果我的代码有误,请告诉我,谢谢。
如代码所示,glibc中没有getpid的定义。看了一些资料,有人说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).
在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.
虽然有这么多的解释,我还是搞不清楚APIgetpid的工作过程。
作为对比,API时间就很好理解了。 时间定义:
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;
}
那么,
#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)
最后是嵌入式asm,内核源码的正常使用方式
#define internal_syscall1(v0_init, input, number, err, arg1) \
({ \
long _sys_result; \
\
{ \
register long __s0 asm ("") __attribute__ ((unused)) \
= (number); \
register long __v0 asm (""); \
register long __a0 asm ("") = (long) (arg1); \
register long __a3 asm (""); \
__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; \
})
谁能解释清楚API getpid 是如何工作的?为什么 getpid 只陷入系统调用 sys_getpid 一次?如果可能,请推荐一些参考资料。
感谢您的帮助。
首先请注意,glibc 源代码几乎无法导航。
正如您所注意到的,文档指出 getpid() 缓存了它的结果。 您发现的代码看起来像
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;
}
只是一个包装器。它查找 getpid
符号,并调用该函数。
该功能是您需要找到的。它是 __getpid() 函数的别名,您可以在 sysdeps/unix/sysv/linux/getpid.c
文件中找到该函数,并且也显示在此 post.
现在 - 您可能正在查看与您当前的 glibc 不匹配的 glibc 源代码 - 据我所知,2016 年 11 月 this commit 中的 getpid() 缓存发生了很大变化告诉更改将成为 2017 年 2 月发布的 glibc-2.25 的一部分
旧的 getpid() 实现缓存了它的值以避免多次调用 getpid() 系统调用,可以在这里看到: http://repo.or.cz/glibc.git/blob/93eb85ceb25ee7aff432ddea0abf559f53d7a5fc:/sysdeps/unix/sysv/linux/getpid.c 看起来像
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)