Linux汇编教程13:系统调用和文件处理上

来源:互联网 发布:淘宝网上购物蚕丝被 编辑:程序博客网 时间:2024/05/20 04:50

缓冲区和.bss

 

缓冲区是连续的字节块,用于批量数据传输。.bss段类似于数据段,不同的是它不占用可执行程序空间。.bss段可以保存存储位置,却不能对其进行初始化。

为了实现这一点,我们需要如下指令:

.section .bss

.lcomm buffer, 500

.lcomm指令将开辟500字节的空间,使用buffer这个符号指向这个区域。

接下来,假设我们打开了一个文件进行读取,将文件描述符放在了%ebx里

movl $buffer, %eax

movl 500, %edx

movl 3, %eax

上面的指令将最多500字节的数据读入缓冲区。read是系统调用3.

 

系统调用

 

关于系统调用%eax保存系统调用号,返回值和错误代码也存储在%eax中

 

Linux内核给汇编提供系统调用的接口是数字,eax寄存器用来保存这个系统调用的数字,这些数字在linux 内核源代码中所对应的平台下的unist_32.h中可以查到。Linux通过系统调用软件中断的方式来完成一个系统调用。int是AT&T汇编中的中断指令,Linux 的系统调中断号为0x80。故而int $0x80指令引发一个Linux系统调用软件中断。系统调用软件中断发生时,根据eax中所定的系统调用去调用系统调用所对应的的内核函数,然后根据相关寄存器内所保存的数据实现一次系统调用。

完成一个系统调用的涉及的方面:

[1] 决定系统调用类型

在明确目标系统调用对应的数字后,给eax寄存器赋所需系统调用对应的数字。eax寄存器用来保存系统调用接口。

movl  $n, %eax    #n为对应的系统调用提供的接口

[2]为系统调用准备相关的数据

在C中,程序所需要的数据保存在栈、堆等内存中。而对于汇编来说,这些数据需要保存在寄存器中,在将数据存到各寄存器的过程中要保证正确的顺序,否则有可能为程序带来灾难性的后果。

在一次系统调用中,只使用ebx、ecx、edx、esi、edi五个寄存器来保存相对程序来说的输入数据,这些输入数据其实是传递给在系统调用过程中所访问的内核函数的实参。输入数据被保存的顺序如下:

  • ebx—第一个参数
  • ecx—第二个参数
  • edx—第三个参数
  • esi—第四个参数
  • edi—第五个参数

这里第*个参数的含义是指系统调用函数的参数次序,可以通过“man 2函数名”的方式查询到。在之前的“汇编程序打印字符”汇编程序中,往ebx,ecx,edx寄存器中保存的参数依次对应“写系统调用”函数write(int  fd, const  void  *buf,size_t  count)的三个参数。所以,ebx中保存的是文件描述符(STDOUT:1),ecx中保存的是要向控制台输出的字符串地址,edx保存的是字符串的长度。So,使用系统调用,man 2相应的内核函数也是必须的。

对于超过六个参数的情况是使用ebx保存相继而存的数据所在内存的起始地址。系统调用通过ebx内的地址访问这些数据。

[3]系统调用的返回值

执行一次系统调用的本质是调用了对应的内核函数,一部分内核函数是有返回值的。一次系统调用完成后(内核函数执行完毕)的返回值保存在eax寄存器中。这里突然想起以前的笔记,关于栈地址的返回:return 栈地址。

[4] 总结使用系统调用

  • 搭一个汇编程序框架。
  • 查询应赋给eax的系统调用接口。[最好的地方是内核源代码unist_32.h中 +搜索引擎]
  • man 2 系统调用函数的参数,按照顺序将函数参数赋给ebx,ecx,edx,esi,edi。
  • 使用int $0x80系统调用软件中断。
  • …….
  • 退出汇编程序的系统调用。

 

查看系统调用号,可以区寻找unist_32.h查看,不用GNU/Linux不同,我的是Ubuntu发行版。在/usr/include/x86_64-linux-gnu/asm/目录下可以找到

unist_32.h

查看函数异read为例子,在终端输入

man 2 read

出现下面界面,函数的参数对应前面的寄存器位置。

man-2-read

标准文件和特殊文件

  • STDIN
    标准输入,文件描述符0
  • STDOUT
    标准输出,文件描述符1
  • STDERR
    标准错误,文件描述符2

 

在程序中使用文件

 

需要实现的功能:从一个文件中读取数据,把小写字母转换为大写字母,然后写入到另一个文件中。

由于这小段代码涉及到一些系统调用号、中断号等等。多了以后,容易搞错,所以我们需要利用一个.equ指令把这些数字分配一个人类可以理解的符号。具体用法看完代码就知道了。

 .section .data .equ SYS_READ, 3 .equ SYS_WRITE, 4 .equ SYS_OPEN, 5 .equ SYS_CLOSE, 6 .equ SYS_EXIT, 1 .equ O_RDONLY, 0 .equ O_CREAT_WRONLY_TRUNC, 03101 .equ STDIN, 0 .equ STDOUT, 1 .equ STDERR, 2 .equ LINUX_SYSCALL, 0x80 .equ END_OF_FILE, 0 .equ NUMBER_ARGUMENTS, 2 .section .bss .equ BUFFER_SIZE, 500 .lcomm BUFFER, BUFFER_SIZE.section .text#栈位置 .equ ST_SIZE_RESERVE, 8 .equ ST_FD_IN, -4 .equ ST_FD_OUT, -8 .equ ST_AGRC, 0 .equ ST_ARGV_0, 4 .equ ST_ARGV_1, 8 .equ ST_ARGV_2, 12 .globl _start_start: # 保留栈指针movl %esp, %ebp#开辟空间 subl $ST_SIZE_RESERVE, %espopen_files:open_fd_in: movl $SYS_OPEN, %eax #将输入文件名放到%ebx movl ST_AGRV_1(%ebp), %ebx# 只读 movl O_RDONLY, %ecx#权限 movl $0666, %edx#调用Linux int $LINUX_SYSCALLstore_fd_in: #保存给定的文件描述符 movl %eax, ST_FD_IN(%ebp)open_fd_out: # 打开文件 movl $SYS_OPEN, %eax # 将输入文件名放入%ebx movl ST_ARGV_2(%ebp), %ebx # 写入文件标志 movl $O_CREAT_WRONLY_TRUNC, %ecx # 新文件模式 movl $0666, %edx # 调用linux int $LINUX_SYSCALLstroe_fd_out: # 存储文件描述符 movl %eax, ST_FD_OUT(%ebp) ### 主循环 ###read_loop_begin: movl $SYS_READ, %eax movl ST_FD_IN(%ebp), %ebx movl $BUFFER, %ecx movl $BUFFER_SIZE, edx int $LINUX_SYSCALL cmpl $END_OF_FILE, %eax jle end_loopcontinue_read_loop: pushl $BUFFER pushl $BUFFER_SIZE call convert_to_upper popl %eax addl $4, %esp movl %eax, %edx movl $SYS_WRITE, %eax movl ST_FD_OUT(%ebp), %ebx movl $BUFFER, %ecx int $LINUX_SYSCALL jmp read_loop_beginend_loop: movl $SYS_CLOSE, %eax movl ST_FD_OUT(%ebp), %ebx int $LINUX_SYSCALL movl $SYS_CLOSE, %eax movl ST_FD_IN(%ebp), %ebx int $LINUX_SYSCALL movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL

现在这一部分实现了主程序的基本框架,convert_to_upper函数部分在下一节给出。

版权声明

Moriarty_221为本文的CSDN博客

如未注明,均为原创,转载请注明出处

转载请注明:coskimo » Linux汇编教程13:系统调用和文件处理上

版权所有 © 科斯基摩 | 本网站采用cc by-nc-sa 3.0协议进行授权

0 0