Skip to content

Latest commit

 

History

History
207 lines (170 loc) · 6.48 KB

201205081639.txt.md

File metadata and controls

207 lines (170 loc) · 6.48 KB

16.20 Linux系统调用

http://scz.617.cn/unix/201205081639.txt

D:

查看当前Linux内核版本:

$ uname -r 2.6.18-4-686

从"www.kernel.org"下载相应内核源码:

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 );

} /* end of main */

$ 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 );

} /* end of main */

$ gdb ./vsyscall_demo (gdb) catch syscall getpid Catchpoint 1 (syscall 'getpid' [20]) (gdb) r Starting program: /tmp/vsyscall_demo

Catchpoint 1 (call to syscall 'getpid'), 0x0804838d in ?? () (gdb) x/i $eip-2 0x804838b: int $0x80 (gdb) i r eax eax 0xffffffda -38 (gdb) ni 0x0804838d in ?? () (gdb) i r eax eax 0x100c 4108