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

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

为了配合内核使用新的系统调用方式,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>:

push %ebp 0x0804cb21 <__getuid>: mov

0x80aa028,%eax 0x0804cb26 <__getuid>: mov

%esp,%ebp 0x0804cb28 <__getuid>: test

%eax,%eax 0x0804cb2a <__getuid>: jle

0x804cb40 <__getuid> 0x0804cb2c <__getuid>:

mov $0x18,%eax 0x0804cb31 <__getuid>:

call *0x80aa054 0x0804cb37 <__getuid>:

pop %ebp 0x0804cb38 <__getuid>: 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>: push %ebp 0x40219e51

<__getuid>: mov %esp,%ebp 0x40219e53

<__getuid>: push %ebx 0x40219e54

<__getuid>: call 0x40219e59

<__getuid> 0x40219e59 <__getuid>:

pop %ebx 0x40219e5a <__getuid>:

add $0x84b0f,%ebx 0x40219e60

<__getuid>: mov 0xffffd87c(%ebx),

%eax 0x40219e66 <__getuid>: test

%eax,%eax 0x40219e68 <__getuid>:

jle 0x40219e80 <__getuid> 0x40219e6a

<__getuid>: mov $0x18,%eax 0x40219e6f

<__getuid>: call *%gs:0x10 0x40219e76

<__getuid>: pop %ebx 0x40219e77

<__getuid>: pop %ebp 0x40219e78 <__getuid>: 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 入口处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值