被glibc忽悠了

2006/05/09

一段代码其中包含两次glibc函数调用getpid(); 本意是希望后一个getpid()调用产生一次system call; 可是glibc只进行了一次system call,第二次glibc直接使用了第一次的结果; 搞得我调了半天其它代码。
整了个示例如下:
C程序:
//test.c
int main()
{
 int pid;
 pid = getpid();
 pid = getpid();
}
gcc test.c编译好程序,再用objdump反汇编,得到结果如下
08048368 <main>:
 8048368: 55                    push   %ebp
 8048369: 89 e5                 mov    %esp,%ebp
 804836b: 83 ec 08              sub    $0x8,%esp
 804836e: 83 e4 f0              and    $0xfffffff0,%esp
 8048371: b8 00 00 00 00        mov    $0x0,%eax
 8048376: 83 c0 0f              add    $0xf,%eax
 8048379: 83 c0 0f              add    $0xf,%eax
 804837c: c1 e8 04              shr    $0x4,%eax
 804837f: c1 e0 04              shl    $0x4,%eax
 8048382: 29 c4                 sub    %eax,%esp
 8048384: e8 17 ff ff ff        call   80482a0 < getpid@plt>
 8048389: 89 45 fc              mov    %eax,0xfffffffc(%ebp)
 804838c: e8 0f ff ff ff        call   80482a0 < getpid@plt>
 8048391: 89 45 fc              mov    %eax,0xfffffffc(%ebp)
 8048394: c9                    leave 
 8048395: c3                    ret   
 8048396: 90                    nop   
 8048397: 90                    nop   
调用了两次getpid ,没什么问题;
再看strace ./a.out 2> a.txt 查看程序的系统调用情况;
a.txt中只有 一次调用了system call -- getpid;
a.txt内容如下:
execve("./a.out", ["./a.out"], [/* 27 vars */]) = 0
uname({sys="Linux", node="kitty22", ...}) = 0
brk(0)                                  = 0x9e37000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=123765, ...}) = 0
old_mmap(NULL, 123765, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7ef5000
close(3)                                = 0
open("/lib/tls/libc.so.6", O_RDONLY)    = 3
read(3, "/177ELF/1/1/1/0/0/0/0/0/0/0/0/0/3/0/3/0/1/0/0/0/20/377"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1454546, ...}) = 0
old_mmap(0x9eb000, 1219772, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x9eb000
old_mmap(0xb0f000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x124000) = 0xb0f000
old_mmap(0xb13000, 7356, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb13000
close(3)                                = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ef4000
mprotect(0xb0f000, 4096, PROT_READ)     = 0
mprotect(0x9e3000, 4096, PROT_READ)     = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7ef4940, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xb7ef5000, 123765)              = 0
getpid()                                = 9583
exit_group(9583)                        = ?
Process 9583 detached
 
这才怀疑到 glibc作了手脚,正好有glibc得源码,看了一下__getpid
pid_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;
}
猜想第二次getpid的时候,THREAD_GETMEM就给出了结果,所以没有进行系统调用.
静态的代码看到了,再 动态gdb跟踪看一下情况:
编译使用gcc -g;
gdb调试时使用命令 display/i $pc,这样每次执行si,都会显示当前的指令汇编马
开始调试,看到getpid()被load到地址0xb8bab0处,
x/20i 0xb8bab0查看getpid()代码,如下:

(gdb) x/20i 0xb8bab0
0xb8bab0 <getpid>:      mov    %gs:0x4c,%edx   ;这里glibc把它的内部数据放在段gs中;
0xb8bab7 <getpid+7>:    mov    %edx,%eax
0xb8bab9 <getpid+9>:    cmp    $0x0,%edx
0xb8babc <getpid+12>:   jle    0xb8babf <getpid+15>
0xb8babe <getpid+14>:   ret
0xb8babf <getpid+15>:   jne    0xb8bad2 <getpid+34>
0xb8bac1 <getpid+17>:   mov    %gs:0x48,%eax   ;%gs  :0x48应该就是我要的数据pid;
0xb8bac7 <getpid+23>:   test   %eax,%eax
0xb8bac9 <getpid+25>:   lea    0x0(%esi),%esi
0xb8bad0 <getpid+32>:   jne    0xb8babe <getpid+14>  ;如果pid已经保存在%gs:0x48中,那么可以返回了
0xb8bad2 <getpid+34>:   mov    $0x14,%eax
0xb8bad7 <getpid+39>:   call   *%gs:0x10     ; 系统调用从这里进入,会调用到int80
0xb8bade <getpid+46>:   mov    %eax,%ecx
0xb8bae0 <getpid+48>:   test   %edx,%edx
0xb8bae2 <getpid+50>:   jne    0xb8babe <getpid+14>
0xb8bae4 <getpid+52>:   mov    %ecx,%gs:0x48   ;将得到的pid结果保存在%gs:0x48中,第二次调用的时候直接取数据了
0xb8baeb <getpid+59>:   ret
0xb8baec <getpid+60>:   no

动态跟踪的结果和静态看到的一致,而且非常明确,第一次调用system call -- getpid得时候, 在%gs:0x48中留了一个备份.
至此,这个烦人的问题算是结束了(可以我真正要调的代码还没有开始:( ) ; 当然 这一切不是glibc得错,没有谁说getpid一定会去进行一次system call;自己想当然了,以后对glibc要留点神, 直接用汇编写system call最保险.
 

你可能感兴趣的:(thread,c,汇编,gcc,null,System)