博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
汇编学习——使用Linux系统调用
阅读量:7034 次
发布时间:2019-06-28

本文共 8986 字,大约阅读时间需要 29 分钟。

hot3.png

一、Linux内核

     Linux操作系统的核心是内核。

     1、内核组成

      内核软件是操作系统的核心。它控制系统的硬件和软件,在必要时分配硬件、并且在需要时执

行软件。内核主要有4个责任:

  •  内存管理
  •  设备管理
  •  文件系统管理
  •  进程管理

      1) 内存管理

       操作系统内核的一个主要功能是内存管理。内核不仅管理服务器上可用的物理内存,它还负

责创建和管理“虚拟内存”,或者说在物理上不存在于主板上的内存。

       内核通过使用硬盘上的空间完成这个工作,这种空间叫做交换空间(swap space),它从硬盘

到实际的物理内存来回地交换内存位置。这使系统能够假设可用的内存比物理上存在的内存要

多。内存位置被分组为称为页面(page)的块。每个页面要么位于物理内存上,要么位于交换空

间。内核必须维护一个表明哪些页面在哪些位置的内存页面表。

        内核自动把一段时间内没有被访问的内存页面复制到硬盘上的交换空间区域。当程序要访问

已经被“交换出”的内存页面时,内核必须交换出其他内存页面并且从交换空间交换出所需的页

面。

        在Linux系统上,通过查看专门的/proc/meminfo文件可以确定虚拟内存的当前状态:

       可以使用ipcs命令查看系统上当前的共享内存段:

         2) 设备管理

          内核的另一个责任是硬件管理。必须与Linux系统进行通信的任何设备都需要插入到内核代

码中的驱动代码,驱动代码使内核可以在通用接口到设备之间来回传递数据。有两种方法用于把

设备驱动代码插入到Linux内核中:

  • 把驱动代码编译到内核代码中
  • 把驱动代码插入到正在运行的内核中

        以前的重新编译内核代码效率低下。而现在有内核模块(kernel module)的概念,它允许

把驱动代码插入正在运行的内核中,当设备使用完毕时也可以从内核删除驱动代码。

         在UNIX服务器上,硬件设备被标识为特殊的设备文件,有3种不同类型的设备文件:

  • 字符
  • 网络

        字符文件代表一次只处理一个字符数据的设备。大多数类型的终端接口被创建为字符文件。

块文件代表一次处理一大块数据的设备,比如磁盘驱动器。网络文件类型代表使用包发送和接收

数据的设备。这包括网卡和特殊的回送设备,回送设备允许Linux系统使用通用网络编程协议和自

身进行通信。

        在文件系统中,设备文件被创建为节点。每个节点都具有对Linux内核标识它的唯一数字

对。这个数字对包含主要设备号和次要设备号。相似的设备被分配到相同的主设备号组中。次要

设备号用于在主设备号相同的设备之间标识设备。

       第五列是主设备节点号,第六列是次设备节点号。第一列是文件权限,第一个字符表示文件

类型。b是块文件,c是字符文件。

       3) 文件系统管理

        下面是Linux系统上可用的标准文件系统。

         Linux使用虚拟文件系统(Virtual File System,VFS)与每种文件系统进行交互。这为内

核与任何类型的文件系统的通信提供了标准的接口。挂载和使用每种文件系统时,VFS把信息缓

存在内存中。

       4) 进程管理

       Linux操作系统把程序作为进程进行管理。内核控制如何在系统中管理进程。内核创建的第一

个进程(称为init进程)启动系统上的所有其他进程。内核启动时,init进程被加载到虚拟内存中。

每个进程启动时,为它分配虚拟内存中的区域,用于存储数据和系统将执行的代码。

        Linux操作系统使用一种利用运行级别(run level)的init系统。运行级别用于指示init进程只

运行特定类型的进程。在Linux系统上有5个init运行级别。

         在运行级别1,只启动基本的系统进程,还有一个控制台终端进程。这称为单一用户模式

(single-user mode)。单一用户模式经常用于文件系统维护。标准的init运行级别是3。在这个

运行级别,启动大多数应用程序软件(如网络支持软件)。在Linux中,另一个常用的运行级别是

5。在这个运行级别上启动X Window软件。注意Linux系统如何通过控制init运行级别来控制全面

的系统功能。通过把运行级别从3改到5,系统可以从基本控制台的系统改变为高级的图形化的X

Window系统。

           为了查看Linux系统上当前活动的进程,可以使用ps命令。格式如下:

ps options

        

      第三列显示进程的当前状态,

二、系统调用

      1、查找系统调用

       Linux针对一个程序设计开发环境配置过了,系统调用在下面的文件中定义:

/usr/include/asm/unistd.h#ifndef _ASM_X86_UNISTD_32_H#define _ASM_X86_UNISTD_32_H 1#define __NR_restart_syscall 0#define __NR_exit 1#define __NR_fork 2#define __NR_read 3#define __NR_write 4#define __NR_open 5#define __NR_close 6#define __NR_waitpid 7#define __NR_creat 8#define __NR_link 9#define __NR_unlink 10#define __NR_execve 11#define __NR_chdir 12#define __NR_time 13

      每个系统调用都被定义为一个名称(前面加上_NR_),和它的系统调用号。

      2、查找系统调用定义

      为了访问系统调用定义,可以从命令提示符使用man命令:

man 2 exit

      命令中2指定man页面的第2部分。不能忘记它,因为有些系统调用也包含在man页的第一部

分中列出的Shell命令。man页包括4个主要部分:

  • Name(名称):显示这个系统调用的名称
  • Synopsis(提要):显示如何使用这个系统调用
  • Description(描述):对这个系统调用的简要描述
  • Return Value(返回值):系统调用完成时返回的值

      3、常用系统调用

      内存访问内核系统调用:

     

        下面是设备访问内核系统调用:

       下面是文件系统系统调用:

     最后是进程系统调用:

三、使用系统调用

     系统调用格式

     为了启动系统调用,需要使用INT指令。Linux系统调用位于终端0x80。执行INT指

令时,所有操作转移到内核中的系统调用处理程序。系统调用完成时,执行转移回

INT指令之后的下一条指令(除非是exit系统调用)。

     下面介绍如何创建系统调用。

      1、系统调用值

       EAX寄存器用于保存系统调用值。这个值定义使用内核支持的哪个系统调用。

        unistd.h文件中系统调用名称旁边列出的整数就是系统调用值(value)。例如:

movl $1, %eaxint $0x80

         unistd.h文件中exit系统调用定义如下:

#define __NR_exit 1

       2、系统调用输入值

       在C样式的函数中,输入值被存放在堆栈中;系统调用与之不同,需要输入值被存放在寄存器

中。每个输入值要被按照特定的顺序存放到寄存器中。把错误的输入值存放在错误的寄存器中可

能导致灾难性的结果。系统调用期望的输入值顺序如下:

  • EBX(第一个参数)
  • ECX(第二个参数)
  • EDX(第三个参数)
  • ESI(第四个参数)
  • EDI(第五个参数)

       超过6个输入参数的系统调用使用不同的方法把参数传递给系统调用。EBX寄存器用于保存

指向输入参数的内存位置指针,输入参数按照连续的顺序储存。

      下一个技巧是决定系统调用函数中的哪个参数属于哪个顺序。例如:

       系统调用write()用于把数据写入文件描述符。man页如下:

      按照参数顺序从左至右设置参数号。第一个参数(fd)是代表输出设备的文件描述符的整数

值。第二个参数(buf)是指向写入设备的字符串的指针。第三个参数(count)是要写入的字符

串的长度。

       使用这一规范,输入值应该被分配到下面的寄存器中:

  • EBX:整数文件描述符
  • ECX:指向要写入的字符串的指针
  • EDX:要写入的字符串的长度

       下面是例子:

.section .data  output:    .ascii "This is a test message.\n"  output_end:    .equ len, output_end - output.section .text  .globl _start_start:  movl $4, %eax #把write系统调用的调用值写入EAX  movl $1, %ebx #把文件描述符写入EBX  movl $output, %ecx #把要写入的字符串的指针写入ECX  movl $len, %edx #把要要写入的字符串长度写入EDX  int $0x80 #系统调用  movl $1, %eax   movl $0, %ebx  int $0x80

       3、系统调用返回值

       

.section .bss  .lcomm pid, 4  .lcomm uid, 4  .lcomm gid, 4.section .text  .globl _start_start:  movl $20, %eax  int $0x80  movl %eax, pid    movl $24, %eax  int $0x80  movl %eax, uid    movl $47, %eax  int $0x80  movl %eax, gidend:  movl $1, %eax  movl $0, %ebx  int $0x80

     上面程序使用下面介绍的3个单独的系统调用:

四、复杂的系统调用返回值

       有时候系统调用涉及C样式结构的复杂数据结构。

       1、sysinfo系统调用

        系统调用sysinfo可以用于返回关于系统如何配置以及有什么可用资源的信息。

man页面如下:

 

       系统调用sysinfo使用单一输入值,它指向保存包含返回数据的结构的内存位置。man页面还

显示了这种结构的情况:

      每个系统值被返回到结构内的特定位置中。必须在一个内存位置创建这个结构,以便值可以

被返回到这里:

        在数据定义的开头有两个标签。当对程序进行汇编时,它们都指向相同的内存位置。标签

result可以用于引用整个结构,标签uptime可以用于引用结构中的第一个值。

       2、使用返回结构

       定义好返回结构之后,就可以在汇编语言程序中使用这些信息。

五、跟踪系统调用

     1、strace程序

      strace程序截取程序发出的系统调用并且显示它们以供查看。被跟踪的程序可以是

从strace命令运行的,也可以是系统上已经运行的进程。如果具有适当的权限,就可以研

究现有的进程并且监视发出的系统调用。在调试汇编语言和高级语言的程序时它是价值无法估量

的工具。

      通常,只需要使用strace程序的默认选项生成关于程序的必要信息即可:

      上面的输出显示了syscalltest2程序发出的所有系统调用,按照应用程序执行系统调用的顺序

排序。左侧显示系统调用名称,右侧显示系统调用生成的返回值。系统调用execve显示操作系统

shell如何运行这个程序。这包括堆栈中的命令行参数和环境变量。

       命令行参数-c在程序执行之后创建一个报告,概述发出的所有系统调用,以及每个系统调用

花费了多长时间:

     2、高级strace参数

     strace程序的基本操作生成很多参数,可以使用命令行参数调整strace程序以便适

合特定的应用程序。

   

       -e参数很方便,用于只显示系统调用的子集:

trace = call_list/*其中call_list以逗号分隔*/

    

     3、监视程序系统调用

     strace程序最好的特性之一是它可以用于系统上的任何程序。

     按照一般模式使用strace监视id命令:

strace -o outfile id

     下面是使用-c参数:

      可以看到open出现3个错误,access出现2个,statfs64出现2个,connect出现4个错误,可以

用strace挑出这些调用:

mimiasd@mimiasd-ThinkPad-T440:~/桌面/exe$ strace -e trace=open,connect idopen("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3open("/proc/filesystems", O_RDONLY|O_LARGEFILE) = 3open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3open("/usr/share/locale/zh_CN/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)open("/usr/share/locale/zh/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)open("/usr/share/locale-langpack/zh_CN/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3open("/usr/share/locale-langpack/zh/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libnss_compat.so.2", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libnsl.so.1", O_RDONLY|O_CLOEXEC) = 3open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libnss_nis.so.2", O_RDONLY|O_CLOEXEC) = 3open("/lib/i386-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 3connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/proc/sys/kernel/ngroups_max", O_RDONLY) = 3open("/proc/sys/kernel/ngroups_max", O_RDONLY) = 3open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3open("/etc/group", O_RDONLY|O_CLOEXEC)  = 3uid=1000(mimiasd) gid=1000(mimiasd) 组=1000(mimiasd),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)+++ exited with 0 +++

     4、附加到正在运行的程序

     strace程序的另一个非常好的特性是监视已经运行在系统上的程序的能力。-p参数

可以把strace程序附加到一个PID并且捕获系统调用。

.section .data  timespec:   #指定timespec结构    .int 5, 0  output:    .ascii "This is a test\n"  output_end:    .equ len, output_end - output  .section .bss  .lcomm rem, 8.section .text  .globl _start_start:  movl $10, %ecx  loop1:  pushl %ecx #因为系统调用会改变ECX的值,所以要先压入堆栈,结束后弹出堆栈  movl $4, %eax  movl $1, %ebx  movl $output, %ecx  movl $len, %edx  int $0x80  movl $162, %eax  movl $timespec, %ebx  movl $rem, %ecx  int $0x80  popl %ecx  loop loop1    movl $1, %eax  movl $0, %ebx  int $0x80

六、系统调用和C库

      1、C库

       C库函数的文档在man页的第三部分。

 

          所有输入参数都存放在堆栈中,顺序和函数提要中的顺序相反。如printf:

printf("The answer is %d\n", k);

          汇编版本如下:

pushl kpushl $outputcall printfaddl $8, %esp #复位堆栈指针以便清除堆栈中的输入值

      2、跟踪C函数

      C库函数也是通过底层系统调用完成它们的工作。也可以使用strace程序监视C函

数的执行情况。

.section .data  output:    .asciz "This is a test\n".section .text  .globl _start_start:  movl $10, %ecx  loop1:  pushl %ecx  pushl $output  call printf  addl $4, %esp  pushl $5  call sleep  addl $4, %esp  popl %ecx  loop loop1  pushl $0  call exit

          用strace监视程序运行:

      可以用strace参数-c查看C函数发出的所有系统调用:

     

      3、系统调用和C库的比较

           

         可以看到nanotest几乎没花时间,而cfunctest花费0.000062秒,显然主要时间差异是由于C

函数在执行程序之前必须加载动态连接器程序。在大型汇编语言中,这种开销将不这么明显:

          使用原始Linux系统调用的主要原因是:

  • 它创建长度尽可能最短的代码,因为不需要把外部库连接到程序中
  • 它创建尽可能最快执行的代码,同样因为不需要把外部库连接到程序中
  • 连接后的可执行文件独立于任何外部库代码

          在汇编语言程序中使用C库函数的主要原因如下:

  • C库包含很多函数,模拟它们需要许多汇编语言代码(比如ASCII到整数或者浮点数据类型的转换)
  • C库在操作系统之间是可移植的(比如在Intel平台上运行的FreeBSD上编译的程序也以运行在Linux系统上)
  • C库函数可以在程序之间利用共享库,减少内存需求

 

转载于:https://my.oschina.net/u/2537915/blog/698628

你可能感兴趣的文章
WatchStor观察:如何让云计算“尽在掌控”?
查看>>
无惧浩瀚数据 超云XS5000集群存储为扩展而生
查看>>
想要高效工作得学会的办公新姿势
查看>>
Ubuntu时间错乱漏洞仍未修复 不知道密码可获root权限
查看>>
HyperGrid将超融合基础设施、容器带入云中
查看>>
陕西省西咸新区管委会副主任刘宇斌:打造双创示范基地
查看>>
做好觉悟了吗?弃用短信双因素身份验证!
查看>>
16WiFi退场引发迷思 公共WIFI走到尽头了吗?
查看>>
小度战胜“水哥”王昱珩,到底有没有黑幕?
查看>>
大数据预测楼市 到底有多靠谱?
查看>>
ApsaraDB for HBase - 规格的的选择
查看>>
物联网还是泄秘网?嗅探流量即可知用户动向
查看>>
数据中心网络里的链路检测技术漫谈
查看>>
信息时代大数据引爆资本市场 全产业链条遍地开花
查看>>
发挥你数据存储专家的价值
查看>>
技术 | 使用Python来学习数据科学的完整教程
查看>>
php错误提醒FastCGI Error Error Number: -2147467259 (0x80004005)错误原因及解决方法
查看>>
微软为Office和写字板软件发布零日漏洞补丁
查看>>
德驻华大使:德国正讨论立法限制中企并购德高科技企业
查看>>
ShadowBroker放大招-多种Windows零日利用工具公布
查看>>