在elf和elftoolchain中提到,通过ltrace浅析查看ps
是如何通过调用getpid得到进程id的。
对此,可以发现有多处错误:
1、ltrace可能好像应该也许并然是跟不进syscall的(我们就把ltrace -S
当成是strace
好吧),这是strace的工作。
2、strace没有发现ps调用了getpid。
但是getpid的确如所想那样位于glibc中,glibc再调用对应的syscall。来看一个例子:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>int main(){ int pid; pid = getpid(); pid = getpid(); return 0;}
编译后使用strace跟踪,是可以看到getpid syscall的:
...munmap(0xb77d1000, 61281) = 0getpid() = 4827fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0...
这证明了有getpid这么个syscall。同时也发现一个现象(其实是很废话的现象):syscall是由glibc做代理调入内核的。
更重要的,我看到getpid在strace的结果中只出现了一次,但是我在程序中明显地调用了两次,这是为什么呢?看这两份资料:“被glibc 忽悠了"、"...getpid captured only once..."。简言之就是glibc cache了结果,第二次调用就径直用第一次的结果了。
有时候我们不想glibc使用cache的结果,怎么办?
答案就是不用glibc的接口,以获取pid为例,就是不调用getpid。后面再看来如何实现。
再来看这段代码编出来的程序:
$ ldd ./test linux-gate.so.1 => (0xb7726000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb75ce000) /lib/ld-linux.so.2 (0xb7727000)
可以看到,它的确用到glibc(就是这里的libc)。
然而遗憾的是,上面ldd的结果是不能拿来当证据的。因为:
1、不负责任地说,用户态程序用到glibc的地方太多了,你避无可避,所以证据无效。
2、稍微负责任一点地,可以说程序的入口函数极有可能就是定义在glibc里面的,所以你逃不脱使用glibc啊,所以证据无效。
3、负责任地说,用ltrace -l
“验明正身”吧。见下面的操作结果。
去除程序的getpid调用,ldd仍然得到libc。
去除/未去除getpid的ltrace 结果:
// 没有getpid$ ltrace -l /lib/i686/cmov/libc.so.6 ./test __libc_start_main(0x8048394, 1, 0xbff8f834, 0x80483c0, 0x80483b0 <unfinished ...>+++ exited (status 0) +++// 有getpid$ ltrace -l /lib/i686/cmov/libc.so.6 ./test__libc_start_main(0x80483c4, 1, 0xbff9b524, 0x8048400, 0x80483f0 <unfinished ...>getpid( = 5076getpid( = 5076+++ exited (status 0) +++))
发现ltrace飙出两个getpid,而strace只出现一个,这说明getpid是会去判断是否已经有结果在内存中,如有就不进syscall,没有才进syscall。
差点忘记:ldd可以看出glibc是个动态库,我是否可以直接使用工具(如nm、objdump等)去查看其中是否包含getpid呢?这实际上和动态连接库、elf文件的组成相关了。
事实上在了解系统调用的大致流程之后,getpid的流程也不言自明,只不过现在仍不清楚细节。此处也不列出详细的细节,又以时间不足为理由略过吧。
从glibc 2.11的代码看出部分细节,可以发现,这里getpid最终是直接通过汇编代码去触发系统调用的。类似地,我们也可以绕过 libc,直接使用汇编去触发系统调用,内核代码中的syscallN对需要的汇编代码做了一些封装:
// nptl/sysdeps/unix/sysv/linux/getpid.cpid_t__getpid (void){#ifdef NOT_IN_libc INTERNAL_SYSCALL_DECL (err); pid_t result = INTERNAL_SYSCALL (getpid, err, 0);#else pid_t result = THREAD_GETMEM (THREAD_SELF, pid); if (__builtin_expect (result <= 0, 0)) result = really_getpid (result);#endif return result;}// INTERNAL_SYSCALL 的一个定义位于 sysdeps/unix/sysv/linux/i386# define INTERNAL_SYSCALL(name, err, nr, args...) ({ register unsigned int resultvar; EXTRAVAR_##nr asm volatile ( LOADARGS_##nr "movl %1, %%eax\n\t" "call *%%gs:%P2\n\t" RESTOREARGS_##nr : "=a" (resultvar) : "i" (__NR_##name), "i" (offsetof (tcbhead_t, sysinfo)) ASMFMT_##nr(args) : "memory", "cc"); (int) resultvar; })
联系客服