用户态经过库函数glibc进入内核态

我用的环境Ubuntu 12.04 (Linux 2.9.0)

参考:http://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html


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

为了配合内核使用新的系统调用方式,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 -g -o static test.c -static
[root@test opt]# gdb ./static
(gdb) disassemble main
0x08048204 <main+0>:    push   %ebp
0x08048205 <main+1>:    mov    %esp,%ebp
0x08048207 <main+3>:    sub    $0x8,%esp
0x0804820a <main+6>:    and    $0xfffffff0,%esp
0x0804820d <main+9>:    mov    $0x0,%eax
0x08048212 <main+14>:   sub    %eax,%esp
0x08048214 <main+16>:   call   0x804cb20 <__getuid>
0x08048219 <main+21>:   leave
0x0804821a <main+22>:   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
//这里的0xffffe4000就是我们要找的固定的进入核心态的地址。


可以看到,_dl_sysinfo指针指向的数值已经发生了变化,指向了0xffffe400,如果我们继续运行程序,__getuid函数将会调用地址0xffffe400处的代码。
接下来,我们将上面的代码编译成动态链接的方式,即默认方式,用gdb装载并反编译main函数
[root@test opt]# gcc -g -o dynamic test.c 
[root@test opt]# gdb ./dynamic
(gdb) disassemble main
0x08048204 <main+0>:    push   %ebp
0x08048205 <main+1>:    mov    %esp,%ebp
0x08048207 <main+3>:    sub    $0x8,%esp
0x0804820a <main+6>:    and    $0xfffffff0,%esp
0x0804820d <main+9>:    mov    $0x0,%eax
0x08048212 <main+14>:   sub    %eax,%esp
0x08048214 <main+16>:   call   0x8048288
0x08048219 <main+21>:   leave
0x0804821a <main+22>:   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.
                

(如果系统调用使用的是int 0x80软中断方式。)

这里是:

int 0x80

ret











我在自己的虚拟机上测试的时候结果是使用int 0x80的方法。我对open,malloc,read,getuid这几个系统调用都测试了。地址都是0x110414.所以这个应该是0x80中断的入口地址。


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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言库函数的实现源码可以在多个地方进行获取。 首先,你可以查看操作系统提供的标准C库源码。比如,对于Linux系统来说,它使用的是GNU C Library (glibc),其源码可以在GNU官方网站或Linux发行版的仓库中获得。 其次,你可以查看编译器的实现源码。大多数编译器会包含标准C库的实现源码。比如,GCC是一个开源编译器套件,包含大量常用库函数的实现源码。 另外,一些开源项目和网站也提供了常用库函数的实现源码。比如GitHub上有很多开源项目,如GNU Coreutils、BusyBox等,这些项目提供了很多常用库函数的实现源码。 最后,如果你想了解更底层的实现细节,你可以查看操作系统的源码。比如,Linux的内核源码提供了很多系统调用和库函数的底层实现,这些可以帮助你深入理解库函数的工作原理。 总的来说,C语言库函数的实现源码可以在操作系统的标准C库源码、编译器源码、开源项目以及操作系统源码中进行获取。 ### 回答2: C语言库函数的实现源码可以在不同的地方找到。 首先,C语言的标准库函数的实现源码通常是由C编译器厂商提供的。大多数C编译器都附带了标准库的源码,可以在编译器的安装目录下找到。例如,对于GNU GCC编译器,可以在其安装目录下的"lib"文件夹中找到相关的源码文件。 其次,C语言的开源实现,如GNU C库(glibc)以及其它一些开源库,也提供了标准库函数的实现源码。这些开源库的源码通常可以在它们的官方网站或代码托管平台上找到,如Github等。 另外,还可以通过在互联网上搜索特定库函数的实现源码来找到相关的资源。有许多社区和论坛上都有人分享了自己实现的库函数代码。 需要注意的是,虽然C语言标准库函数的实现源码可以找到,但这并不意味着我们可以直接修改它们。标准库函数的源码通常是经过优化和经过验证的,适用于各种平台和环境。如果有需要对标准库的行为进行扩展或修改,应当采用编写自定义函数的方式来实现。 ### 回答3: C语言库函数的实现源码可以在多个地方找到。 首先,可以查看C语言的标准库函数的实现源码。C标准库函数的实现源码通常是由编译器或操作系统提供的。例如,GNU C 编译器中的源码集合在GNU C 库(又称为glibc)中,可以在GNU官方网站或其他开源源码托管平台(如Github)上找到。此外,还有其他的一些标准C库的实现,如uclibc、newlib等。 其次,很多C语言库函数的实现源码也可以在开源软件项目或库中找到。许多常用的C库,如数据库访问库MySQL、图形库OpenGL、网络编程库libcurl等,都是开源的,并且提供了源码。你可以在它们的官方网站或开源源码托管平台上找到它们的源码。 最后,一些编程书籍或教程也会提供一些库函数的实现源码作为示例。这些源码通常会通过书籍附带的光盘或相关网站提供。 总之,C语言库函数的实现源码可以通过搜索相关的开源项目、编程书籍或教程来找到。通过查看这些源码可以更深入地了解库函数的实现原理,同时也可以借鉴和学习他人的代码实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值