1. linux-gate.so是什么
参考这里:http://www.trilithium.com/johan/2005/08/linux-gate/

简而言之,linux-gate.so是为了实现用户程序使用sysenter/sysexit进行
系统调用的辅助机制。为什么我们需要这么一种机制来完成sysenter/sysexit?

按照我们使用int 80进行系统调用的思维,我们期待sysenter/sysexit是这样的
一个过程:
          
          user app:                        kernel:
            /*things*/                     
            /*setup parameters*/
            movl $__NR_getpid, %eax
            sysenter                ------>
                                           movl current->pid, %eax
                                           sysexit
                                    <------
            /*%eax=pid*/
            /*other things*/

我们编写一个例子试试上面的想法:

    [root@w237 vdso.d]# cat pid.c

    #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"
                         "sysenter\n"
                         : "=a"(pid));
        printf("pid=%u\n", pid);
    
        return 0;
    }

编译,gdb调试:
    [root@w237 vdso.d]# gcc -g -o pid pid.c
    [root@w237 vdso.d]# gdb -q ./pid
    Using host libthread_db library "/lib/tls/libthread_db.so.1".
    (gdb) disassemble main
    Dump of assembler code for function main:
    0x08048368 <main+0>:    push   %ebp
    0x08048369 <main+1>:    mov    %esp,%ebp
    0x0804836b <main+3>:    sub    $0x8,%esp
    0x0804836e <main+6>:    and    $0xfffffff0,%esp
    0x08048371 <main+9>:    mov    $0x0,%eax
    0x08048376 <main+14>:   add    $0xf,%eax
    0x08048379 <main+17>:   add    $0xf,%eax
    0x0804837c <main+20>:   shr    $0x4,%eax
    0x0804837f <main+23>:   shl    $0x4,%eax
    0x08048382 <main+26>:   sub    %eax,%esp
    0x08048384 <main+28>:   mov    $0x14,%eax
    0x08048389 <main+33>:   sysenter
    0x0804838b <main+35>:   mov    %eax,0xfffffffc(%ebp)
    0x0804838e <main+38>:   sub    $0x8,%esp
    0x08048391 <main+41>:   pushl  0xfffffffc(%ebp)
    0x08048394 <main+44>:   push   $0x8048488
    0x08048399 <main+49>:   call   0x80482b0
    0x0804839e <main+54>:   add    $0x10,%esp
    0x080483a1 <main+57>:   mov    $0x0,%eax
    0x080483a6 <main+62>:   leave
    0x080483a7 <main+63>:   ret
    End of assembler dump.
    (gdb)
我们在sysenter一行设置断点,并且运行跟踪:
    (gdb) b *0x8048389
    Breakpoint 1 at 0x8048389: file pid.c, line 13.
    (gdb) r
    Starting program: /home/wensg/vdso.d/pid
    Reading symbols from shared object read from target memory...done.
    Loaded system supplied DSO at 0xffffe000
    
    Breakpoint 1, 0x08048389 in main () at pid.c:13
    13          __asm__ volatile("movl $"STRINGFY(__NR_getpid)", %%eax\n"
这时候gdb中断在sysenter这一行,用stepi单步运行这条指令:
    (gdb) stepi
    0xffffe424 in __kernel_vsyscall ()
看见了么?当sysenter执行完毕(也就是sysexit的结果)以后,程序是停在了0xffffe424这一行,
这个地址位于函数__kernel_vsyscall中!!为什么不是sysenter的下一行0x804838b???

2. sysenter/sysexit指令

参考IA32的文档。

sysenter/sysexit被冠以“Fast System Call facility”。至于是否如此,我现在不关心。

sysenter调用的过程为:
设置下面寄存器值(%msr[SYSENTER_CS]表示名为SYSENTER_CS的msr值,model specific 
register,一组特别的寄存器组):
    %cs   = %msr[SYSENTER_CS]
    %eip  = %msr[SYSENTER_EIP]
    %ss   = %msr[SYSENTER_SS] + 8
    %esp  = %msr[SYSENTER_ESP]
    %CPL  = 0
然后从%cs:%eip继续执行。

sysexit调用过程为:
设置下面寄存器值:
    %cs   = %msr[SYSENTER_CS] + 16
    %eip  = %edx
    %ss   = %msr[SYSENTER_CS] + 24
    %esp  = %ecx
    %CPL  = 3
然后从%cs:%eip继续执行。

我们看到sysenter调用进入内核时,CPU不会保存用户堆栈,返回地址和其它的寄存器,
那么sysexit怎么返回到正确的用户空间呢?

一种办法就是调用前把%eip, %esp(因为%cs, %ss只是内核用来糊弄MMU的,我们先不管了)
保存在别的寄存器中,不过这样需要2个寄存器才能完成任务。

另外一种办法就是sysexit总是返回到用户进程某个固定的地址!vdso就是作为
sysenter/sysexit的存根(stub)的。sysenter只会在某个固定的位置被调用,而sysexit
也只需要返回到调用sysenter+2的位置(sysenter的机器码占2个字节)。不过%esp还是
需要保存的。

这就是为什么我们在例子1中观察到了sysenter指令会跳转到了__kernel_vsyscall()函数中,
sysexit返回的固定地址就在这个__kernel_vsyscall中。

让我们看看__kernel_vsyscall的汇编代码:
    (gdb) disassemble __kernel_vsyscall
    Dump of assembler code for function __kernel_vsyscall:
    0xffffe414 <__kernel_vsyscall+0>:       push   %ecx
    0xffffe415 <__kernel_vsyscall+1>:       push   %edx
    0xffffe416 <__kernel_vsyscall+2>:       push   %ebp
    0xffffe417 <__kernel_vsyscall+3>:       mov    %esp,%ebp
    0xffffe419 <__kernel_vsyscall+5>:       sysenter
    0xffffe41b <__kernel_vsyscall+7>:       nop
    0xffffe41c <__kernel_vsyscall+8>:       nop
    0xffffe41d <__kernel_vsyscall+9>:       nop
    0xffffe41e <__kernel_vsyscall+10>:      nop
    0xffffe41f <__kernel_vsyscall+11>:      nop
    0xffffe420 <__kernel_vsyscall+12>:      nop
    0xffffe421 <__kernel_vsyscall+13>:      nop
    0xffffe422 <__kernel_vsyscall+14>:      jmp    0xffffe417 <__kernel_vsyscall+3>
    0xffffe424 <__kernel_vsyscall+16>:      pop    %ebp  ; sysexit返回到这里
    0xffffe425 <__kernel_vsyscall+17>:      pop    %edx
    0xffffe426 <__kernel_vsyscall+18>:      pop    %ecx
    0xffffe427 <__kernel_vsyscall+19>:      ret
    End of assembler dump.
    (gdb)
看到没有,在0xffffe424这一行的上方有一个sysenter指令。Linux的设计是:进程只应当
从一个地方调用sysenter, sysexit返回到这个调用下面的某个地方,这两个地址都是固定的。
__kernel_vsyscall的sysenter到sysexit返回的地址0xffffe424中间有数个nop和jmp指令
的作用,下面再解释。

3. 如何使用sysenter

从例1的例子来看,我们是无法直接使用sysenter的,因为我们无法知道这个返回地址和
调用的协议。实际上,这样的指令对于普通的程序员来说,完全是透明的。vdso是C库的开发
者关心的问题。

__kernel_vsyscall的设计目标是代替int 80, 也就是下面两种方式应该是等价的:
     /* int80 */                  /* __kernel_vsyscall */
     movl $__NR_getpid, %eax      movl $__NR_getpid, %eax
     int $0x80                    call __kernel_vsyscall
     /* %eax=getpid() */          /* %eax=getpid() %/

C库有怎么知道有__kernel_vsyscall呢?很简单,kernel告诉C库,kernel中存在
__kernel_vsyscall。至于C库选择int80,还是sysenter进行系统调用,那就是C库管了,
kernel已经提供了这样的一种机制,策略就不管是它管的了。

kernel告诉C库__kernel_vsyscall的位置,则是通过elf的interpreter的auxiliary vector
这个的具体细节看以参考elf的技术文档,我们可以通过下面的手段观察auxiliary vector
    [root@w237 vdso.d]# LD_SHOW_AUXV=1 /bin/ls
    AT_SYSINFO:      0xffffe414
    AT_SYSINFO_EHDR: 0xffffe000
    AT_HWCAP:    fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe
    AT_PAGESZ:       4096
    AT_CLKTCK:       100
    AT_PHDR:         0x8048034
    AT_PHENT:        32
    AT_PHNUM:        8
    AT_BASE:         0x0
    AT_FLAGS:        0x0
    AT_ENTRY:        0x8049cf0
    AT_UID:          0
    AT_EUID:         0
    AT_GID:          0
    AT_EGID:         0
    AT_SECURE:       0
    AT_PLATFORM:     i686
AT_SYSINFO就是__kernel_vsyscall函数的地址,AT_SYSINFO_EHDR是vdso加载的位置。

4. 总体的结构:

用下面的图来解释:
 这张图不清楚,贴一张真正的图:

linux-gate.so(vdso)是内核镜像中的特定页,它是一个完整的elf share object,
因此在磁盘的任何位置都找不到一个它。它是由内核的某些文件编译生成的。

当使用exec()执行新的镜像时,内核把linux-gate.so的页面映射到程序的进程空间中。
内核把__kernel_vsyscall的地址以auxiliary vector的形式告诉interpreter
(C库)。

当C库要进入内核时,它就可以选择使用__kernel_vsyscall或者int80来进行系统调用。

5. 内核的细节

让我们想想内核需要做那些工作:
  5.1 生成vdso,并链接到内核中。
  5.2 设置MSR,以便sysenter能进入内核的正确位置,sysexit能返回到用户程序的正确位置。
  5.3 exec()时,将vdso映射到用户程序的地址空间中,找到__kernel_vsyscall的地址,
      传给interpreter。
  5.4 调用时,正确传递参数。
  5.5 sysenter的响应函数要正确解析参数,调用相应的系统函数完成服务;设置%ecx, %edx,
      用sysexit返回
  5.6 当程序exit时,解除vdso的映射。
 
当你理解上面的内容之后,理解内核的细节不过是把它们找出来而已。自己去翻内核看,也是理解
上面内容的一个很好的途径。

6. __kernel_vsyscall

前面还遗留了一个问题,那7个nop和jmp是干什么的呢?让我们再看看它的代码:
    0xffffe414 <__kernel_vsyscall+0>:       push   %ecx
    0xffffe415 <__kernel_vsyscall+1>:       push   %edx
    0xffffe416 <__kernel_vsyscall+2>:       push   %ebp
    0xffffe417 <__kernel_vsyscall+3>:       mov    %esp,%ebp
    0xffffe419 <__kernel_vsyscall+5>:       sysenter
    0xffffe41b <__kernel_vsyscall+7>:       nop
    0xffffe41c <__kernel_vsyscall+8>:       nop
    0xffffe41d <__kernel_vsyscall+9>:       nop
    0xffffe41e <__kernel_vsyscall+10>:      nop
    0xffffe41f <__kernel_vsyscall+11>:      nop
    0xffffe420 <__kernel_vsyscall+12>:      nop
    0xffffe421 <__kernel_vsyscall+13>:      nop
    0xffffe422 <__kernel_vsyscall+14>:      jmp    0xffffe417 <__kernel_vsyscall+3>
    0xffffe424 <__kernel_vsyscall+16>:      pop    %ebp  ; sysexit返回到这里
    0xffffe425 <__kernel_vsyscall+17>:      pop    %edx
    0xffffe426 <__kernel_vsyscall+18>:      pop    %ecx
    0xffffe427 <__kernel_vsyscall+19>:      ret

前面连个push %ecx和%edx是因为sysexit返回时,要用这两个寄存器来制定返回的eip和esp,因此先保存起来。
然后我们要把%esp的值保存在%ebp中,否则我们就无法获得当前的堆栈指针了,在覆盖%ebp前,先保存%ebp,
这是系统调用的第六个参数。
然后使用sysenter
然后一堆的nop和一个jmp,这里完全是一个死循环。这是干什么的?正常的sysexit又不会执行这里(直接到
jmp之后了)

这个问题linus在这封mail中讨论了:
http://lkml.org/lkml/2002/12/18/218
他的意思是jmp的设计是用来支持restarted system call的,如果一个system call需要restart,它只需要返
回到某个nop中,然后jmp到重新初始化%ebp的代码中,从而是sysenter再次执行。
不过什么情况下会使一个system call restart,征个人告诉我。

下面link也不错,可对照参考一下;

http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html
Linux 2.6 对新型 CPU 快速系统调用的支持

Linux-gate.so技术细节的更多相关文章

  1. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  2. 2、实现不同子网之间的信息交流(互相可以PING通)

    一.环境: 二个不同的虚拟子网 VMnet1: 192.168.155.0/24 VMnet8: 192.168.170.0/24 编辑 --> 虚拟网络编辑器 (查看自己的子网,相应修改就行) ...

  3. Linux 设置IP,gate, 以及自动获取IP的方法

    一.使用命令设置ubuntu的ip地址 1.修改配置文件blacklist.conf禁用IPV6: sudo vi /etc/modprobe.d/blacklist.conf 2.在文档最后添加 b ...

  4. 【Linux大系】Linux的概念与体系

    感谢原作者:Vamei 出处:http://www.cnblogs.com/vamei 我在这一系列文章中阐述Linux的基 本概念.Linux操作系统继承自UNIX.一个操作系统是一套控制和使用计算 ...

  5. Linux基础介绍【第八篇】

    Linux网络基础 网线 568A 568B 线序:橙白橙 绿白蓝 蓝白绿 棕白棕 交换机.路由器 交换机:DLINK.H3C.CISCO 交换机(Switch)是一种用于电信号转发的网络设备.它可以 ...

  6. Linux服务器安全配置

    众所周知,网络安全是一个非常重要的课题,而服务器是网络安全中最关键的环节.Linux被认为是一个比较安全的Internet服务器,作为一种开放源代码操作系统,一旦Linux系统中发现有安全漏洞,Int ...

  7. 【转】Linux makefile 教程 非常详细,且易懂

    From: http://blog.csdn.net/liang13664759/article/details/1771246 最近在学习Linux下的C编程,买了一本叫<Linux环境下的C ...

  8. Linux堆内存管理深入分析(下)

     Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...

  9. 记一次Linux服务器上查杀木马经历

    开篇前言 Linux服务器一直给我们的印象是安全.稳定.可靠,性能卓越.由于一来Linux本身的安全机制,Linux上的病毒.木马较少,二则由于宣称Linux是最安全的操作系统,导致很多人对Linux ...

  10. Linux的概念与体系

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我在这一系列文章中阐述Linux的基本概念.Linux操作系统继承自UNIX.一个 ...

随机推荐

  1. WIN7远程桌面重启、关机

    在使用远程桌面访问Win7系统时会发现一个小问题,在xp远程桌面中存在的重启和关机菜单在win7远程桌面中不见了,如图: 这也给我们的使用带来了一些小小的麻烦,但实际上微软依然保留了命令行的方式来实现 ...

  2. [ext/iconv/iconv.lo] Error 1

    办法1: wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.1.tar.gz tar -zxvf libiconv-1.13.1.tar.g ...

  3. wcf实例模型(随记)

    -----------------------------------------实例模型: 1.InstanceContentextMode: -------PerCall(单调):无状态,每次调用 ...

  4. media Queries实现一个响应式的菜单

    使用media Queries实现一个响应式的菜单   Media queries是CSS3引入的一个特性,使用它可以方便的实现各种响应式效果.在这个示例中我们将会使用media queries实现一 ...

  5. 【python】初识python的问题

    这两天利用晚上时间简单的了解了一下python语言,在Mac上和Windows上都安装了python,对比两个平台,还是发现在mac上体验比较好一点.安装的版本好像也不一样,语法还有点小区别.简单的对 ...

  6. vijos1060 隔板法

    排列组合问题 之前没有学过隔板法,随便学习了一下 其实挺好理解的 附上题解: 先只考虑一种球:因为有n个盒子每个盒子可以放任意多球,还可以空出来任意多球.所以可以考虑为n+1个盒子,最后一个盒子里面是 ...

  7. sprinfmvc学习--01

    springmvc框架是一个基于请求驱动的web框架,使用了前端控制器模式来设计.根据请求映射规则分发给相应的页面控制器进行处理. 1.  首先用户发送请求-->DispatcherServle ...

  8. 【C#基础知识】静态构造函数,来源于一道面试题的理解

    看到园友的一道面试题,很好奇,测试了一下结果. public class A { public static int X=B.Y ; public A() { ++X; } } public clas ...

  9. mac os apache 配置方法详细介绍

    我使用的Mac OS X版本是10.8.2,Mac自带了Apache环境. 启动Apache 设置虚拟主机 启动Apache 打开“终端(terminal)”,输入 sudo apachectl -v ...

  10. 8个免费实用的C++GUI库

    8个免费实用的C++GUI库 C++标准中并没有包含GUI,这也使得C++开发图形化界面需要依赖于第三方的库.实际上,图形界面恰恰是C++的强项,小到平常使用的各类桌面软件,大到魔兽世界这样的游戏,都 ...