科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Linux2.6对新型CPU快速系统调用的支持 (4)

Linux2.6对新型CPU快速系统调用的支持 (4)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

为了配合内核使用新的系统调用方式,glibc 中要做一定的修改。新的 glibc-2.3.2(及其以后版本中)中已经包含了这个改动,在 glibc 源代码的 sysdeps/unix/sysv/linux/i386/sysdep.h 文件中,处理系统调用的宏 INTERNAL_SYSCALL 在不同的编译选项下有不同的结果。

作者:赛迪网技术社区 来源:赛迪网技术社区 2007年10月28日

关键字: 支持 调用 CPU Linux

  • 评论
  • 分享微博
  • 分享邮件
 

由用户态经库函数进入内核态

为了配合内核使用新的系统调用方式,glibc 中要做一定的修改。新的 glibc-2.3.2(及其以后版本中)中已经包含了这个改动,在 glibc 源代码的 sysdeps/unix/sysv/linux/i386/sysdep.h 文件中,处理系统调用的宏 INTERNAL_SYSCALL 在不同的编译选项下有不同的结果。在打开支持 sysenter/sysexit 指令的选项 I386_USE_SYSENTER 下,系统调用会有两种方式,在静态链接(编译时加上 -static 选项)情况下,采用 "call *_dl_sysinfo" 指令;在动态链接情况下,采用 "call *%gs:0x10" 指令。这两种情况由 glibc 库采用哪种方法链接,实际上最终都相当于调用某个固定地址的代码。下面我们通过一个小小的程序,配合 gdb 来验证。

首先是一个静态编译的程序,代码很简单:

main() {  getuid(); }

将代码加上 static 选项用 gcc 静态编译,然后用 gdb 装载并反编译 main 函数。

[root@test opt]# gcc test.c -o ./static -static [root@test opt]
# gdb ./static (gdb) disassemble main 0x08048204 :  
 push   %ebp 0x08048205 :    mov    %esp,%ebp 0x08048207 :    
sub    $0x8,%esp 0x0804820a :    
and    $0xfffffff0,%esp 0x0804820d :    
mov    $0x0,%eax 0x08048212 :   sub    
%eax,%esp 0x08048214 :   call   0x804cb20
 <__getuid> 0x08048219 :   leave 0x0804821a :   ret

可以看出,main 函数中调用了 __getuid 函数,接着反编译 __getuid 函数。

(gdb) disassemble 0x804cb20 0x0804cb20 <__getuid+0>:     
   push   %ebp 0x0804cb21 <__getuid+1>:        mov   
 0x80aa028,%eax 0x0804cb26 <__getuid+6>:        mov  
  %esp,%ebp 0x0804cb28 <__getuid+8>:        test  
 %eax,%eax 0x0804cb2a <__getuid+10>:       jle   
 0x804cb40 <__getuid+32> 0x0804cb2c <__getuid+12>:  
     mov    $0x18,%eax 0x0804cb31 <__getuid+17>:     
  call   *0x80aa054 0x0804cb37 <__getuid+23>:      
 pop    %ebp 0x0804cb38 <__getuid+24>:       ret

上面只是 __getuid 函数的一部分。可以看到 __getuid 将 eax 寄存器赋值为 getuid 系统调用的功能号 0x18 然后调用了另一个函数,这个函数的入口在哪里呢?接着查看位于地址 0x80aa054 的值。

(gdb) X 0x80aa054 0x80aa054 <_dl_sysinfo>:        0x0804d7f6

看起来不像是指向内核映射页面内的代码,但是,可以确认,__dl_sysinfo 指针的指向的地址就是 0x80aa054。下面,我们试着启动这个程序,然后停在程序第一条语句,再查看这个地方的值。

(gdb) b main Breakpoint 1 at 0x804820a (gdb)
 r Starting program: /opt/static Breakpoint 1, 
0x0804820a in main () (gdb) X 0x80aa054 0x80aa054 
<_dl_sysinfo>:        0xffffe400

可以看到,_dl_sysinfo 指针指向的数值已经发生了变化,指向了 0xffffe400,如果我们继续运行程序,__getuid 函数将会调用地址 0xffffe400 处的代码。

接下来,我们将上面的代码编译成动态链接的方式,即默认方式,用 gdb 装载并反编译 main 函数

[root@test opt]# gcc test.c -o ./dynamic [root@test opt]
# gdb ./dynamic (gdb) disassemble main 0x08048204 :   
 push   %ebp 0x08048205 :    mov    %esp,%ebp 0x08048207 :
    sub    $0x8,%esp 0x0804820a :    and    $0xfffffff0,
%esp 0x0804820d :    mov    $0x0,%eax 0x08048212 :   sub 
   %eax,%esp 0x08048214 :   call   0x8048288 0x08048219 : 
  leave 0x0804821a :   ret

由于 libc 库是在程序初始化时才被装载,所以我们先启动程序,并停在 main 第一条语句,然后反汇编 getuid 库函数。

(gdb) b main Breakpoint 1 at 0x804820a 
(gdb) r Starting program: /opt/dynamic Breakpoint 1, 
0x0804820a in main () (gdb) disassemble getuid Dump 
of assembler code for function getuid: 0x40219e50 
<__getuid+0>:        push   %ebp 0x40219e51
 <__getuid+1>:        mov    %esp,%ebp 0x40219e53 
<__getuid+3>:        push   %ebx 0x40219e54
 <__getuid+4>:        call   0x40219e59
 <__getuid+9> 0x40219e59 <__getuid+9>:     
   pop    %ebx 0x40219e5a <__getuid+10>:    
   add    $0x84b0f,%ebx 0x40219e60 
<__getuid+16>:       mov    0xffffd87c(%ebx),
%eax 0x40219e66 <__getuid+22>:       test  
 %eax,%eax 0x40219e68 <__getuid+24>:       
jle    0x40219e80 <__getuid+48> 0x40219e6a 
<__getuid+26>:       mov    $0x18,%eax 0x40219e6f 
<__getuid+31>:       call   *%gs:0x10 0x40219e76
 <__getuid+38>:       pop    %ebx 0x40219e77
 <__getuid+39>:       pop    %ebp 0x40219e78 <__getuid+40>:       ret

可以看出,库函数 getuid 将 eax 寄存器设置为 getuid 系统调用的调用号 0x18,然后调用 %gs:0x10 所指向的函数。在 gdb 中,无法查看非 DS 段的数据内容,所以无法查看 %gs:0x10 所保存的实际数值,不过我们可以通过编程的办法,内嵌汇编将 %gs:0x10 的值赋予某个局部变量来得到这个数值,而这个数值也是 0xffffe400,具体代码这里就不再赘述。

由此可见,无论是静态还是动态方式,最终我们都来到了 0xffffe400 这里的一段代码,这里就是内核为我们映射的系统调用入口代码。在 gdb 中,我们可以直接反汇编来查看这里的代码。

(gdb) disassemble 0xffffe400 0xffffe414 
Dump of assembler code from 0xffffe400 to 
0xffffe414: 0xffffe400:     push   
%ecx 0xffffe401:     push   
%edx 0xffffe402:     push  
 %ebp 0xffffe403:     mov    
%esp,%ebp 0xffffe405:     
sysenter 0xffffe407:     nop 0xffffe408:     
nop 0xffffe409:     nop 0xffffe40a:    
 nop 0xffffe40b:     nop 0xffffe40c:     
nop 0xffffe40d:     nop 0xffffe40e:     
jmp    0xffffe403 0xffffe410:     pop   
 %ebp 0xffffe411:     pop    %edx 0xffffe412:  
   pop    %ecx 0xffffe413:     ret End of assembler dump.

这段代码正是 arch/i386/kernel/vsyscall-sysenter.S 文件中的代码。其中,在 sysenter 之前的是入口代码,在 0xffffe410 开始的是内核返回处理代码(后面提到的 SYSENTER_RETURN 即指向这里)。在入口代码中,首先是保存当前的 ecx,edx(由于 sysexit 指令需要使用这两个寄存器)以及 ebp。然后调用 sysenter 指令,跳转到内核 Ring 0 代码,也就是 sysenter_entry 入口处。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章