Linux系统调用

概述

相比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>

   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。

 

 

猜你喜欢

转载自blog.csdn.net/luozhaotian/article/details/79610175