16.20 Linux系统调用
http://scz.617.cn/unix/201205081639.txt
D:
查看当前Linux内核版本:
$ uname -r 2.6.18-4-686
http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.tar.xz
系统调用号在"include/asm-i386/unistd.h"中定义:
#define __NR_gettid 224
通常系统调用foo()对应的内核函数是sys_foo(),参看:
arch/i386/kernel/syscall_table.S
数组sys_call_table[]用于存放这些内核函数指针。
以前系统调用是通过"int 0x80"进行的。eax对应系统调用号,6个形参依次对应:
ebx ecx edx esi edi ebp
$ gdb /usr/bin/col (gdb) catch syscall open Catchpoint 1 (syscall 'open' [5]) (gdb) r Starting program: /usr/bin/col
Catchpoint 1 (call to syscall 'open'), 0xb7ffab54 in ?? () from /lib/ld-linux.so.2 (gdb) x/i $eip-2 0xb7ffab52: int $0x80 (gdb) i r eax ebx ecx edx esi edi ebp eax 0xffffffda -38 ebx 0xbfffd600 -1073752576 ecx 0x0 0 edx 0x0 0 esi 0x1 1 edi 0xb7fe3848 -1208076216 ebp 0xbfffd5e8 0xbfffd5e8 (gdb) x/s $ebx 0xbfffd600: "/usr/local/lib/tls/i686/sse2/cmov/libc.so.6" (gdb) x/5i $eip 0xb7ffab54: pop %ebx 0xb7ffab55: cmp $0xfffff001,%eax 0xb7ffab5a: jae 0xb7ffab5d 0xb7ffab5c: ret 0xb7ffab5d: call 0xb7ffb887 (gdb) ni 0xb7ffab54 in ?? () from /lib/ld-linux.so.2 (gdb) i r eax eax 0xfffffffe -2
gdb的"catch syscall ..."断下来时,eip位于"int 0x80"之后,但实际上系统调用 尚未进行,eax里保存的不是返回值。ni之后,eip未变,系统调用已经返回,eax里 保存返回值。就本例而言,由于"/usr/local/lib/tls/i686/sse2/cmov/libc.so.6" 不存在,故eax不是有效值。"man 2 open"得知这个系统调用的第一形参是pathname, 在gdb中查看第一形参。
高版本的Intel芯片提供sysenter/sysexit指令,AMD芯片提供syscall/sysret指令。 后来的Linux系统调用开始使用这些指令,以提高效率。
系统调用gettimeofday()被频繁使用,如果有一个高效实现会降低系统负载。办法之 一是将当前时间写在一个固定位置,其所在内存页被映射到所有进程空间,每次时钟 中断都会自动更新这个固定位置的数据。进程只需要读这个固定位置的数据即可获取 当前时间,不需要跨越用户态、内核态的边界,不需要真实的系统调用。还有一些数 据,比如当前pid,为获取它也不需要真实的系统调用。这些系统调用称为vsyscall。
从Linux 2.5.53开始,有一个固定页,vsyscall page,由内核负责填充有效内容。 在内核初始化阶段调用sysenter_setup(),它会建立一个不可写的页,参看:
arch/i386/kernel/sysenter.c
C库函数通过跳到"vsyscall page"中某个固定地址进行快速系统调用。该页内容是ELF 格式的,从Linux 2.5.74开始该页被命名为"linux-gate.so.1"。
$ ldd which col
linux-gate.so.1 => (0xffffe000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e60000)
/lib/ld-linux.so.2 (0x80000000)
在没有ASLR机制的年代,vsyscall page被映射到固定地址(0xffffe000-0xffffefff)。 从Linux 2.6.18开始,该页被映射到一个随机地址:
$ setarch uname -m
-R cat /proc/self/maps | grep vdso
b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso]
参看"arch/i386/kernel/vsyscall-sysenter.S",了解vsyscall page的组成。
C库函数调用__kernel_vsyscall()进行系统调用,其位于vsyscall page中。内核通 过ELF Auxiliary Vectors中的AT_SYSINFO将__kernel_vsyscall()的地址传递给用户 态进程。也可以通过GS:0x10获取__kernel_vsyscall()的地址,关于这点,参看:
《在GDB里如何查看GS:0x10的内容》
在当前进程的栈中寻找AT_SYSINFO很不方便,因此libc.so被加载时,会从栈中复制 AT_SYSINFO的值到tcbhead_t.sysinfo,后者可以用GS:0x10访问。
/*
- gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include <stdio.h>
int tid;
int main ( int argc, char * argv[], char * envp[] )
{
asm
("
movl $224, %eax ;
call %gs:0x10 ;
movl %eax, tid ;
");
printf( "tid is %d\n", tid );
return( 0 );
} / end of main */
/*
- gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include <stdio.h>
int main ( int argc, char * argv[], char * envp[] ) { int tid;
asm \
( \
" \
movl $224, %%eax ;\
call *%%gs:0x10 ;\
movl %%eax, %0 ;\
"
:"=m"(tid)
);
printf( "tid is %d\n", tid );
return( 0 );
$ gdb ./vsyscall_demo (gdb) catch syscall gettid Catchpoint 1 (syscall 'gettid' [224]) (gdb) r Starting program: /tmp/vsyscall_demo
Catchpoint 1 (call to syscall 'gettid'), 0xb7fe4410 in ?? () (gdb) x/i $eip-2 0xb7fe440e: jmp 0xb7fe4403 (gdb) i r eax eax 0xffffffda -38 (gdb) ni 0xb7fe4410 in ?? () (gdb) i r eax eax 0xfec 4076
/*
- gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include <stdio.h>
int main ( int argc, char * argv[], char * envp[] ) { int pid;
asm \
( \
" \
movl $20, %%eax ;\
int $0x80 ;\
movl %%eax, %0 ;\
"
:"=m"(pid)
);
printf( "pid is %d\n", pid );
return( 0 );
$ gdb ./vsyscall_demo (gdb) catch syscall getpid Catchpoint 1 (syscall 'getpid' [20]) (gdb) r Starting program: /tmp/vsyscall_demo