zoukankan      html  css  js  c++  java
  • 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用----20135334赵阳林

    一、理论知识

    所谓的系统调用三层皮,结合下图解释如下:

    1. API:第一层是指Libc中定义的API,这些API封装了系统调用,使用int 0x80触发一个系统调用中断;当然,并非所有的API都使用了系统调用,如完成数学加减运算的API就没有使用系统调用;也有可能某个API使用了多个系统调用;这一层存在的价值就是为应用程序员提供易于使用的API来调用系统调用;
    2. system_call:运行于内核态。system_call是所有系统调用在内核的入口点,在其中的开始处保护用户态程序执行上下文,结束处恢复用户态程序执行上下文,在中间根据传入的系统调用号对应的中断服务程序;
    3. sys_xyz 系统调用封装例程:执行具体的系统调用操作,完成用户的系统调用请求;每个系统调用都对应一个封装例程;
     

    由上面的分析可以知道,理论上要请求一个系统调用,我们即可以使用Libc提供的API,也可以直接在C中内嵌汇编代码触发0x80中断来完成,这次实验,我们就用实际的例子来演示这两种方法使用同一个系统的调用。我们选择的是比较简单的系统调用sys_write,这屏幕上打印输出“hello world”,对应的API就是printf。

    实验环境:实验楼(http://www.shiyanlou.com/courses/195

    二、方法一:使用API在屏幕上显示“hello world”

    这个其实也是C语言经典的入门程序,源代码如下

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #include "stdio.h"  
    2. #include "string.h"  
    3.   
    4. int main()  
    5. {  
    6.     char* msg = "Hello World";  
    7.     printf("%s", msg);  
    8.     return 0;  
    9. }  


    不解释了,大家主要用来和下面的内嵌汇编代码做个比较就好!

    在实验楼中,打开此次实验链接(http://www.shiyanlou.com/courses/running/731),双击Xfce终端,cd Code目录下,gedit helloworld.c,新建并打开helloworld.c文件,在其中输入上面的代码,保存退出;

    然后使用下面的指令编译链接程序:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. gcc -o helloworld helloworld.c -m32   


    接着,运行编译好的程序,

    ./helloworld

    效果如下:

    三、方法二:使用C内嵌汇编代码在屏幕上输出helloworld

    Linux中内嵌汇编代码的语法,视频中有详细介绍,这里略去,直接给出代码和注释如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int main()  
    2. {  
    3.     char* msg = "Hello World";  
    4.     int len = 11;  
    5.     int result = 0;  
    6.   
    7.     __asm__ __volatile__("movl %2, %%edx; " /*传入参数:要显示的字符串长度*/  
    8.              "movl %1, %%ecx; " /*传入参赛:文件描述符(stdout)*/  
    9.              "movl $1, %%ebx; " /*传入参数:要显示的字符串*/  
    10.              "movl $4, %%eax; " /*系统调用号:4 sys_write*/  
    11.              "int  $0x80" /*触发系统调用中断*/  
    12.              :"=m"(result) /*输出部分:本例并未使用*/  
    13.              :"m"(msg),"r"(len)  /*输入部分:绑定字符串和字符串长度变量*/  
    14.              :"%eax");   
    15.           
    16.     return 0;  
    17. }  

    使用gedit helloworld_asm.c新建文件,并输入上面的代码,使用下面的命令编译

    gcc -o helloworld_asm helloworld_asm.c -m32

    使用下面的命令运行

    ./helloworld_asm

    运行效果如下

    四、总结

    即便是最简单的程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运算,否则将很难避免使用系统调用。在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。
    Linux 下的系统调用是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。
    所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_<name> 这样的宏来定义的,如 SYS_write、SYS_exit 等。例如,经常用到的 write 函数是如下定义的:
    ssize_t write(int fd, const void *buf, size_t count);
    该函数的功能最终是通过 SYS_write 这一系统调用来实现的。根据上面的约定,参数 fb、buf 和 count 分别存在寄存器 ebx、ecx 和 edx 中,而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完毕后,返回值可以从寄存器 eax 中获得。
    或许你已经发现,在进行系统调用时至多只有 5 个寄存器能够用来保存参数,难道所有系统调用的参数个数都不超过 5 吗?当然不是,例如 mmap 函数就有 6 个参数,这些参数最后都需要传递给系统调用 SYS_mmap:
    void  *  mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
    当一个系统调用所需的参数个数大于 5 时,执行int 0x80 指令时仍需将系统调用功能号保存在寄存器 eax 中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器 ebx 中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器 eax 中。
    由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈(stack)来传递系统调用所需的参数。但要注意一点,Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参数,在执行int 0x80 指令时还应该将栈指针的当前值复制到寄存器 ebx中。

  • 相关阅读:
    Zend Framework 项目 index.php 的问题
    《MySQL 必知必会》读书总结
    phpstorm 无法格式化代码
    Windows 下 zip 版的 MySQL 的安装
    配置Windows下的PHP开发环境
    phpstorm 激活服务器
    《DOM Scripting》
    《JavaScript高级程序设计》
    sql 查找所有已经分配部门的员工
    sql 表的连接 inner join、full join、left join、right join、natural join
  • 原文地址:https://www.cnblogs.com/EliteDci/p/5297101.html
Copyright © 2011-2022 走看看