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. GIMP也疯狂之动态图的制作(二)

    首先看下效果: (素材丢失,无法提供) 所用工具:GIMP.GIMP-GAP(在源中直接搜索安装) 文后会添加一个从U2B上搬运过来的视频教程,效果不错,值得一看本想也制作个人物变换,但几次实验,相同 ...

  2. leetcode第一题--two sum

    Problem:Given an array of integers, find two numbers such that they add up to a specific target numb ...

  3. solr连接数据库

    solr与.net系列课程(三)solr连接数据库    solr与.net系列课程(三)solr连接数据库 上一章直接讲述的配置文件把大部分人看的很迷惑,大家都想听的是solr到底是怎么用的,好,这 ...

  4. SSIS Package to Call Web Service

    原文 SSIS Package to Call Web Service SSIS Package to Call Web Service. You can Call WebService from S ...

  5. Oracle中的Union、Union All、Intersect、Minus

    Oracle中的Union.Union All.Intersect.Minus  众所周知的几个结果集集合操作命令,今天详细地测试了一下,发现一些问题,记录备考. 假设我们有一个表Student,包括 ...

  6. c/c++中typedef详解

    1. typedef 最简单使用 typedef long byte_4; // 给已知数据类型long起个新名字,叫byte_4 你可以在任何需要 long 的上下文中使用 byte_4.注意 ty ...

  7. [Usaco2008 Nov]Buying Hay 购买干草[背包]

    Description     约翰的干草库存已经告罄,他打算为奶牛们采购日(1≤日≤50000)磅干草.     他知道N(1≤N≤100)个干草公司,现在用1到N给它们编号.第i个公司卖的干草包重 ...

  8. asp.net打印网页后自动关闭网页【无需插件】

    项目遇需要网页加载自动打印网页后需要自动关闭该网页,但是百度了好久发现都是需要插件什么的 于是就自己摸索摸索,用js弄了个定时器,意外的发现,当打印设置窗口弹出后,定时器就暂停了 不管你点击取消或者打 ...

  9. MongoDB的.Net驱动

    mongo的驱动主要使用了两个,即samus和官方驱动. 个人感觉差别不大,且官方驱动也支持了LinQ.但在使用DBRef的时候,发现samus的驱动似乎不太好用,并没有达到想要的效果,也许是我的使用 ...

  10. 聊天工具mychat

    python学习,自己写了个简单聊天工具mychat 最近在学习python,自己写了个最最简单的聊天工具mychatv0.1. 第一版,完成基本的聊天功能. GUI用的是自带的TKinter,用到的 ...