2017-04-06 91 views
0

情况如下:glibc中的getpid工作程序是什么?

我想要做的project它在github中篡改内核。内核版本是linux-3.18.6。

QEMU用于模拟环境。

在我的应用程序中,我尝试通过遵循它们来了解系统调用过程。完成我的目标的方式就像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 $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; 
} 

因为我的应用程序只是运行在pid 1的进程中,所以每次输入命令getpid时,它都会返回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; 
} 

如果我得到了错误的代码,请告诉我,tks。

如代码所示,getpid的定义不包含在glibc中。读完一些数据后,有人说the VDSO...

注意,据我所知,简单的系统调用 成本的显著部分从用户空间将内核和背部。因此,对于某些系统调用 (可能是gettimeofday,getpid ...),VDSO可能会避免即使那 (并在技术上可能会避免做一个真正的系统调用)。

在男子GETPID PGAE:

C库/内核差异 由于glibc的版本2.3.4,为GETPID glibc的包装函数() 缓存的PID,以避免额外的系统调用当进程 重复调用getpid()。正常情况下,这种缓存是不可见的,但其 正确的操作依赖于对分支函数 fork(2),vfork(2)和clone(2)的支持:如果应用程序绕过glibc通过使用这些系统调用的包装系统调用(2),然后调用 getpid()将返回错误的值(准确地说:它会返回父进程的PID)。即使当 通过glibc包装函数调用clone(2)时,getpid()也可能返回错误的值,请参阅clone(2)中的dis- 。

尽管有这么多的解释,我无法弄清楚API getpid的工作过程。

作为对比,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) 

最后,它是嵌入汇编,采用内核源代码的正常方式。

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

有人可以清楚地解释API getpid的工作原理吗?为什么getpid只能陷入syscall sys_getpid一次?如果可能的话,可以推荐一些参考。

感谢您的帮助。

+1

到底什么是你的问题?您已阅读手册:glibc会缓存getpid-syscall返回的值。显然,这个缓存必须在子进程中的fork(2)之后重新设置。 –

+0

感谢您的回答。 getpid使用dlsym的机制是什么?为什么getpid的实现与其他实现不同?这就是我想知道的。谢谢。 –

回答

1

首先请注意,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文件中找到该函数,该函数也显示在本文的底部。

现在 - 您可能正在查看与当前glibc不匹配的glibc源代码 - 关于在2016年11月完成的getpid()缓存,在this commit中发生了很大变化,据我所知可能会发生变化的glibc-2.25在2017年2月

缓存的它的价值,以避免调用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) 
+0

感谢您的详细解答。也许我明白你的意思。得到你的消息后,我只是用ldd检查了我的ubuntu glibc版本,我用它编译了内核。正如你所说的,它是glibc-2.23,我发现你在glibc-2.23中指出的代码。所以应该解决p p。但是,我仍然无法弄清为什么getpid的实现不同于时间或需要进入内核模式的其他api。我也不知道getpid使用dlsym的机制。如果可能,你能否提供一些参考资料?我想深入挖掘,Tks。 –

+0

@ a-thorn一个进程的pid永远不会改变。因此,glibc可以从内核中获取一次pid,然后将其缓存起来,而不必执行另一个系统调用。我希望这很明显,为什么time()的调用不能以同样的方式进行缓存 - 对于大多数其他系统调用也是如此 - 对缓存结果没有任何意义。使用dlsym()函数查找getpid的代码似乎来自'./ elf/restest2.c',该文件是glibc的测试套件的一部分,而不是getpid()实现。 – nos

+0

谢谢你的回答,我很佩服你的效率。是的,现在我终于知道了差异。请原谅我的愚蠢。现在我知道它是getpid的测试套件,但我实际上找不到getpid()实现。你能为我指出吗?我使用getpid,但我只找到__getpid实现。另外,我了解time,因为time()使用int $ 0x80进入内核模式,然后内核找到实现sys_time。至于getpid(),我很困惑。 Thans请注意。 –