您的位置:首页 > 运维架构 > Linux

Linux系统调用

2018-03-19 13:19 127 查看
概述
相比Intel支持的快速系统调用指令sysenter/sysexit,AMD对应的是syscall/sysret,不过现在,Intel也兼容这两条指令
 
测试环境:
Ubuntu 12.04
Ubuntu 16.04 64

传统系统调用int 0x80

只用于32位系统,64位系统上不起作用;
 系统调用号和返回结果
EAX指定要调用的函数(系统调用号)
EBX传递函数的第一个参数
ECX传递函数的第二个参数
EDX传递函数的第三个参数
返回值EAX
 示例
#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/syscall.h>
#define STRINGFY_(x) #x#define STRINGFY(x) STRINGFY_(x)
int main(){ pid_t pid;
asm volatile("movl $"STRINGFY(__NR_getpid)", %%eax\n" "int $0x80\n" : "=a"(pid)); printf("pid=%u\n", pid);
return 0;}

32位系统调用sysenter

3.1 系统调用

系统调用号:
sys/syscall.h
/usr/include/i386-linux-gnu/asm/unistd_32.h
如:
#define __NR_getuid              24
#define __NR_getuid32           199
参数:
EAX指定要调用的函数(系统调用号)
EBX传递函数的第一个参数
ECX传递函数的第二个参数
EDX传递函数的第三个参数
ESI
EDI
EBP
返回值EAX
 
静态链接时,采用"call *_dl_sysinfo"指令;
动态链接时,采用"call *%gs:0x10"指令;
最终调用的是VDSO(linux-gate.so.1)中的__kernel_vsyscall函数;
__kernel_vsyscall函数包含sysenter指令;
 
syscall()函数也是类似的,根据静态/动态链接的不同分别采用的不同的指令,最终调用__kernel_vsyscall函数;

3.2 静态链接实例

millionsky@ubuntu-12:~/tmp$ gcc getuid_glibc.c -static
millionsky@ubuntu-12:~/tmp$ gdb ./a.out
(gdb) b main
Breakpoint 1 at 0x8048ee3
(gdb) r
Starting program: /home/millionsky/tmp/a.out
 
Breakpoint 1, 0x08048ee3 in main ()
(gdb) disass
Dump of assembler code for function main:
   0x08048ee0 <+0>:     push   %ebp
   0x08048ee1 <+1>:     mov    %esp,%ebp
=> 0x08048ee3 <+3>:     and    $0xfffffff0,%esp
   0x08048ee6 <+6>:     sub    $0x10,%esp
   0x08048ee9 <+9>:     call   0x8053c00 <getuid>
4000

   0x08048eee <+14>:    mov    $0x80c6088,%edx
   0x08048ef3 <+19>:    mov    %eax,0x4(%esp)
   0x08048ef7 <+23>:    mov    %edx,(%esp)
   0x08048efa <+26>:    call   0x8049980 <printf>
   0x08048eff <+31>:    mov    $0x0,%eax
   0x08048f04 <+36>:    leave  
   0x08048f05 <+37>:    ret    
End of assembler dump.
(gdb) disass 0x8053c00
Dump of assembler code for function getuid:
   0x08053c00 <+0>:     mov    $0xc7,%eax  //__NR_getuid32
   0x08053c05 <+5>:     call   *0x80ef5a4
   0x08053c0b <+11>:    ret    
End of assembler dump.
(gdb) x 0x80ef5a4
0x80ef5a4 <_dl_sysinfo>:        0xb7fff414
(gdb) disass 0xb7fff414
Dump of assembler code for function __kernel_vsyscall:
   0xb7fff414 <+0>:     push   %ecx
   0xb7fff415 <+1>:     push   %edx
   0xb7fff416 <+2>:     push   %ebp
   0xb7fff417 <+3>:     mov    %esp,%ebp
   0xb7fff419 <+5>:     sysenter
   0xb7fff41b <+7>:     nop
   0xb7fff41c <+8>:     nop
   0xb7fff41d <+9>:     nop
   0xb7fff41e <+10>:    nop
   0xb7fff41f <+11>:    nop
   0xb7fff420 <+12>:    nop
   0xb7fff421 <+13>:    nop
   0xb7fff422 <+14>:    int    $0x80
   0xb7fff424 <+16>:    pop    %ebp
   0xb7fff425 <+17>:    pop    %edx
   0xb7fff426 <+18>:    pop    %ecx
   0xb7fff427 <+19>:    ret    
End of assembler dump.

3.3 动态链接实例

millionsky@ubuntu-12:~/tmp$ gcc getuid_glibc.c
millionsky@ubuntu-12:~/tmp$ gdb ./a.out
(gdb) b main
Breakpoint 1 at 0x8048417
(gdb) r
Starting program: /home/millionsky/tmp/a.out
 
Breakpoint 1, 0x08048417 in main ()
(gdb) b getuid
Breakpoint 2 at 0xb7ed9c30
(gdb) c
Continuing.
 
Breakpoint 2, 0xb7ed9c30 in getuid () from /lib/i386-linux-gnu/libc.so.6
(gdb) disass
Dump of assembler code for function getuid:
=> 0xb7ed9c30 <+0>:     mov    $0xc7,%eax
   0xb7ed9c35 <+5>:     call   *%gs:0x10
   0xb7ed9c3c <+12>:    ret    
End of assembler dump.
(gdb) si
0xb7ed9c35 in getuid () from /lib/i386-linux-gnu/libc.so.6
(gdb)
0xb7fdd414 in __kernel_vsyscall ()
(gdb) disass
Dump of assembler code for function __kernel_vsyscall:
=> 0xb7fdd414 <+0>:     push   %ecx
   0xb7fdd415 <+1>:     push   %edx
   0xb7fdd416 <+2>:     push   %ebp
   0xb7fdd417 <+3>:     mov    %esp,%ebp
   0xb7fdd419 <+5>:     sysenter
   0xb7fdd41b <+7>:     nop
   0xb7fdd41c <+8>:     nop
   0xb7fdd41d <+9>:     nop
   0xb7fdd41e <+10>:    nop
   0xb7fdd41f <+11>:    nop
   0xb7fdd420 <+12>:    nop
   0xb7fdd421 <+13>:    nop
   0xb7fdd422 <+14>:    int    $0x80
   0xb7fdd424 <+16>:    pop    %ebp
   0xb7fdd425 <+17>:    pop    %edx
   0xb7fdd426 <+18>:    pop    %ecx
   0xb7fdd427 <+19>:    ret    
End of assembler dump.

3.4 汇编使用

3.4.1 模拟__kernel_vsyscall

直接执行sysenter指令,执行完成后,内核sysexit会跳转到__kernel_vsyscall的后半部分继续执行:
Dump of assembler code for function __kernel_vsyscall:
   0xb7fff414 <+0>:     push   %ecx
   0xb7fff415 <+1>:     push   %edx
   0xb7fff416 <+2>:     push   %ebp
   0xb7fff417 <+3>:     mov    %esp,%ebp
   0xb7fff419 <+5>:     sysenter
   0xb7fff41b <+7>:     nop
   0xb7fff41c <+8>:     nop
   0xb7fff41d <+9>:     nop
   0xb7fff41e <+10>:    nop
   0xb7fff41f <+11>:    nop
   0xb7fff420 <+12>:    nop
   0xb7fff421 <+13>:    nop
   0xb7fff422 <+14>:    int    $0x80
   0xb7fff424 <+16>:    pop    %ebp     //跳转到这里
   0xb7fff425 <+17>:    pop    %edx
   0xb7fff426 <+18>:    pop    %ecx
   0xb7fff427 <+19>:    ret    
因此需要注意以下几点:
保存返回地址(sysenter指令的下一条指令)
保存ecx、edx寄存器(sysexit指令需要使用这两个寄存器);
asm("push %ecx\n");
asm("push %edx\n");
增加函数头
asm("push %ebp\n");
asm("mov %esp,%ebp\n"); 

#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/syscall.h>
#define STRINGFY_(x) #x#define STRINGFY(x) STRINGFY_(x)
int main(){ pid_t pid;
//simulate __kernel_vsyscall    asm("jmp label2\n");    asm("label1:\n");
    asm("push %ecx\n");    asm("push %edx\n");    asm("push %ebp\n");    asm("mov %esp,%ebp\n");
    asm("movl $"STRINGFY(__NR_getpid)", %eax\n");    asm("sysenter\n");    asm("label2:"); asm("call label1\n");    asm("": "=a"(pid));     printf("pid=%u\n", pid);
return 0;}
 

3.4.2调用__kernel_vsyscall

#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/syscall.h>
#define STRINGFY_(x) #x#define STRINGFY(x) STRINGFY_(x)
int main(){ pid_t pid;
asm("movl $"STRINGFY(__NR_getpid)", %eax\n"); asm("call *%gs:0x10\n"); asm("": "=a"(pid)); printf("pid=%u\n", pid);
return 0;}

64位系统调用syscall

4.1 系统调用

系统调用号:
sys/syscall.h
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
 
参数(man syscall):
 


 
参数寄存器:
 


静态链接时,直接调用syscall指令;
动态链接时,调用libc的系统调用代码,调用syscall指令;
syscall()函数也是类似的,最终调用syscall指令;

4.2 静态链接实例

millionsky@ubuntu-16:~/tmp/VDSO$ cat getuid_glibc.c
/**
 * filename: getuid_glibc.c
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main(int argc, char *argv[])
{
    printf("uid:%d\n", getuid());
    return 0;
}
millionsky@ubuntu-16:~/tmp/VDSO$ gcc getuid_glibc.c -o getuid_glibc -static
 
GDB调试
millionsky@ubuntu-16:~/tmp/VDSO$ gdb ./getuid_glibc -q
Reading symbols from ./getuid_glibc...(no debugging symbols found)...done.
(gdb) tb __getuid
Temporary breakpoint 1 at 0x43e8e0
(gdb) r
Starting program: /home/millionsky/tmp/VDSO/getuid_glibc
 
Temporary breakpoint 1, 0x000000000043e8e0 in getuid ()
(gdb) disass
Dump of assembler code for function getuid:
=> 0x000000000043e8e0 <+0>:  mov    $0x66,%eax
   0x000000000043e8e5 <+5>:  syscall
   0x000000000043e8e7 <+7>:  retq   
End of assembler dump.
值0x66是__NR_getuid在x64上的值,即把__NR_getuid放到eax寄存器后,不再是执行指令int 0x80,而是执行指令syscall。

4.3 动态链接实例

millionsky@ubuntu-16:~/tmp/SROP$ gcc getuid_glibc.c
millionsky@ubuntu-16:~/tmp/SROP$ gdb ./a.out
(gdb) b main
Breakpoint 1 at 0x40056a
(gdb) r
Starting program: /home/millionsky/tmp/SROP/a.out
 
Breakpoint 1, 0x000000000040056a in main ()
(gdb) b __getuid
Breakpoint 2 at 0x7ffff7ada240: file ../sysdeps/unix/syscall-template.S, line 65.
(gdb) c
Continuing.
 
Breakpoint 2, getuid () at ../sysdeps/unix/syscall-template.S:65
65      ../sysdeps/unix/syscall-template.S: 没有那个文件或目录.
(gdb) disass
Dump of assembler code for function getuid:
=> 0x00007ffff7ada240 <+0>:     mov    $0x66,%eax
   0x00007ffff7ada245 <+5>:     syscall
   0x00007ffff7ada247 <+7>:     retq   
End of assembler dump.
 

4.4 syscall函数实例

millionsky@ubuntu-16:~/tmp/VDSO$ cat getuid_syscall.c
 /**
  * filename: getuid_syscall.c
  */
#include <stdio.h>
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
 
int main(int argc, char *argv[])
{
    printf("uid:%ld\n", syscall(__NR_getuid));
    return 0;
}
millionsky@ubuntu-16:~/tmp/VDSO$ gcc getuid_syscall.c -o getuid_syscall -static
 
GDB调试
millionsky@ubuntu-16:~/tmp/VDSO$ gdb ./getuid_syscall -q
Reading symbols from ./getuid_syscall...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x4009b2
(gdb) r
Starting program: /home/millionsky/tmp/VDSO/getuid_syscall
 
Breakpoint 1, 0x00000000004009b2 in main ()
(gdb) disass
Dump of assembler code for function main:
   ...
 0x00000000004009bd <+15>: mov    $0x66,%edi
   0x00000000004009c2 <+20>: mov    $0x0,%eax
   0x00000000004009c7 <+25>: callq  0x43fc70 <syscall>
   ...
End of assembler dump.
(gdb) disass 0x43fc70
Dump of assembler code for function syscall:
   0x000000000043fc70 <+0>:  mov    %rdi,%rax
   0x000000000043fc73 <+3>:  mov    %rsi,%rdi
   0x000000000043fc76 <+6>:  mov    %rdx,%rsi
   0x000000000043fc79 <+9>:  mov    %rcx,%rdx
   0x000000000043fc7c <+12>: mov    %r8,%r10
   0x000000000043fc7f <+15>: mov    %r9,%r8
   0x000000000043fc82 <+18>: mov    0x8(%rsp),%r9
   0x000000000043fc87 <+23>: syscall
   ...
End of assembler dump.

5 结论

1. 系统调用指令:
传统的32位系统调用int 0x80
Intel的sysenter/sysexit
AMD的syscall/sysret
 
2. 传统Int 0x80系统调用
系统调用号:EAX
参数:EBX、ECX、EDX、ESI、EDI、EBP
返回值:EAX
 
3. 32位系统调用sysenter
系统调用号:EAX
参数:EBX、ECX、EDX、ESI、EDI、EBP
返回值:EAX
 
静态链接时,采用"call *_dl_sysinfo"指令;
动态链接时,采用"call *%gs:0x10"指令;
最终调用的是VDSO(linux-gate.so.1)中的__kernel_vsyscall函数;
__kernel_vsyscall函数包含sysenter指令;
syscall()函数也是类似的,根据静态/动态链接的不同分别采用的不同的指令,最终调用__kernel_vsyscall函数;
 
4. 64位系统调用syscall
系统调用号:RAX
参数:RDI、RSI、RDX、R10、R8、R9
返回值:RAX
 
静态链接时,直接调用syscall指令;
动态链接时,调用libc的系统调用代码,调用syscall指令;
syscall()函数也是类似的,最终调用syscall指令;

参考文章

1. 64位Linux下的系统调用。http://www.lenky.info/archives/2013/02/2199。
2. Linux 2.6 对新型 CPU 快速系统调用的支持。https://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html。
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  系统调用