读写文件程序集

时间:2016-06-21 09:34:57

标签: linux assembly x86

我有一个inputfile.txt,如下所示:3 4 2 0 8 1 5 3

我试图在outputfile.txt内写入输入文件的每个字符,加1。 所以在outputfile.txt里面我应该看到4 5 3 1 9 2 6 4。 我试着写这段代码,但我有几个疑问。

.section .data
  buff_size: .long 18

.section .bss
  .lcomm buff, 18

.section  .text              # declaring our .text segment
  .globl _start              # telling where program execution should start

_start:

    popl %eax       # Get the number of arguments
    popl %ebx       # Get the program name
    popl %ebx       # Get the first actual argument - file to read

    # open the file
    movl $5, %eax       # open 
    movl $0, %ecx       # read-only mode
    int $0x80        

    # read the file

    movl $0, %esi
    movl %eax, %ebx     # file_descriptor

    analyzecharacter:   #here I want to read a single character
        movl $3, %eax       
        movl $buff, %edi    
        leal (%esi,%edi,1), %ecx    
        movl $1, %edx
        int $0x80
        add $1, %esi #this point is not clear to me, what I'd like to do is to increment the index of the buffer in order to be positioned on the next cell of buffer array, I've added 1 but I think is not correct
        cmp $8, %esi  # if I've read all 8 characters then I'll exit
        je exit

    openoutputfile:
    popl %ebx       # Get the second actual argument - file to write
    movl $5, %eax       # open
    movl $2, %ecx       # read-only mode
    int $0x80       

    writeinoutputfile:
    #increment by 1 and write the character to STDOUT
    movl %eax, %ebx     # file_descriptor
    movl $4, %eax       
    leal (%esi,%edi,1), %ecx
    add $1, %ecx #increment by 1        
    movl $1, %edx   
    int $0x80
    jmp analyzecharacter        

    exit:
    movl $1, %eax       
    movl $0, %ebx       
    int $0x80   

我有两个问题/怀疑:

1-我首先怀疑的是这条指令:add $1, %esi。这是通过buffer数组的正确方法吗?

2-第二个疑问是:当我分析每个角色时,我是否应该始终调用openoutputfile标签?我认为通过这种方式我重新打开文件并覆盖以前的内容。 实际上,如果我运行该程序,我只会看到一个字符\00(垃圾字符,由此指令中%esi的值引起,我猜:leal (%esi,%edi,1), %ecx)。

我希望我的问题很清楚,我对装配很陌生,而且我花了好几个小时。

FYI: 
I'm using GAS Compiler and the syntax is AT&T.
Moreover I'm on Ubuntu 64 bit and Intel CPU. 

2 个答案:

答案 0 :(得分:2)

那么,我将如何做代码...... 考虑到这一点,我已经习惯了英特尔的语法,我无法在网上写出AT& T来源而没有错误(而且我实在太懒了事情和调试它),所以我会尽量避免完全编写指令,只是描述过程,让你填写说明。

因此,让我们决定你要通过我的来源的第1版char来做char:

start:
  ; verify the command line has enough parameters, if not jump to exitToOs
  ; open both input and output files at the start of the code
processingLoop:
  ; read single char
  ; if no char was read (EOF?), jmp finishProcessing
  ; process it
  ; write it
  jmp processingLoop
finishProcessing:
  ; close both input and output files
exitToOs:
  ; exit back to OS
  • 现在"跑"在你的脑海中,验证所有主要分支点是否有意义,并将正确处理所有主要的角落案例。
  • 确保您了解代码的工作方式,代码的循环方式以及循环的中断和原因。
  • 确保没有无限循环或资源泄漏

通过我的清单后,这个设计存在一个微妙的问题,它没有严格检查文件系统错误,例如无法打开任何一个文件,或者写字符(但是你的来源)也不关心)。否则我认为它应该运作良好。

因此,让我们在版本2中扩展它以更接近真实的ASM指令(星号标记的指令是由我来的,因此可能使用混乱的语法,由您来决定这些指令的最终版本):

start:
  ; verify the command line has enough parameters, if not jump to exitToOs
    popl %eax       # Get the number of arguments
   * cmpl $3,eax   ; "./binary fileinput fileoutput" will have $3 here?? Debug!
   * jnz exitToOs

  ; open both input and output files at the start of the code
    movl $5, %eax       # open 
    popl %ebx       # Get the program name

  ; open input file first
    popl %ebx       # Get the first actual argument - file to read
    movl $0, %ecx       # read-only mode
    int $0x80
    cmpl $-1, %eax  ; valid file handle?
    jz exitToOs
   * movl %eax, ($varInputHandle) ; store input file handle to memory

  ; open output file, make it writable, create if not exists
    movl $5, %eax       # open 
    popl %ebx       # Get the second actual argument - file to write
   * ; next two lines should use octal numbers, I hope the syntax is correct
   * movl $0101, %ecx # create flag + write only access (if google is telling me truth)
   * movl $0666, %edx ; permissions for out file as rw-rw-rw-
    int $0x80
    cmpl $-1, %eax  ; valid file handle?
    jz exitToOs
    movl %eax, ($varOutputHandle) ; store output file handle to memory

processingLoop:

  ; read single char to varBuffer
    movl $3, %eax
    movl ($varInputHandle), %ebx
    movl $varBuffer, %ecx
    movl $1, %edx
    int $0x80

  ; if no char was read (EOF?), jmp finishProcessing
    cmpl $0, %eax
    jz finishProcessing ; looks like total success, finish cleanly

  ;TODO process it
   * incb ($varBuffer) ; you wanted this IIRC?

  ; write it
    movl $4, %eax       
    movl ($varOutputHandle), %ebx     # file_descriptor
    movl $varBuffer, %ecx  ; BTW, still set from char read, so just for readability
    movl $1, %edx    ; this one is still set from char read too
    int $0x80

  ; done, go for the next char
    jmp processingLoop

finishProcessing:
    movl $0, ($varExitCode) ; everything went OK, set exit code to 0

exitToOs:
  ; close both input and output files, if any of them is opened
    movl ($varOutputHandle), %ebx     # file_descriptor
    call closeFile
    movl ($varInputHandle), %ebx
    call closeFile

  ; exit back to OS
    movl $1, %eax
    movl ($varExitCode), %ebx
    int $0x80

closeFile:
    cmpl $-1, %ebx
    ret z ; file not opened, just ret
    movl $6, %eax  ; sys_close
    int $0x80
    ; returns 0 when OK, or -1 in case of error, but no handling here
    ret

.data
varExitCode: dd 1 ; no idea about AT&T syntax, "dd" is "define dword" in NASM
  ; default value for exit code is "1" (some error)
varInputHandle: dd -1 ; default = invalid handle
varOutputHandle: dd -1 ; default = invalid handle
varBuffer: db ? ; (single byte buffer)

哇,我其实是完全写的? (当然它需要语法检查+清除星号,";"用于评论等...)

但是我的意思是,版本1的评论已经非常详细,每个都只需要少量的ASM指令,所以并不困难(虽然现在我看到我确实在53分钟前提交了第一个答案,所以这是关于〜1小时为我工作(包括谷歌搜索和其他地方的一些其他事情))。

我绝对不知道有些人可能想要使用AT& T语法,这是如此荒谬冗长。我很容易理解为什么GCC正在使用它,对于编译器来说这非常好。

但是也许你应该检查NASM,这是人类"面向(尽可能少写语法糖,并专注于指令)。 NASM的主要问题(或我认为的优势)是英特尔语法,例如: MOV eax,ebx将数字ebx放入eax,这是Intels错误,从其他微处理器制造商处获取LD语法,忽略 LD = load 含义,并将其更改为 MOV = move 以不公开复制指令集。

然后,我完全不知道为什么ADD $1,%eax是AT& T中的正确方式(而不是eax,1顺序),我甚至不想知道,但它对我没有任何意义(由于英特尔MOV语法的LD来源,反向MOV至少有一定意义。

OTOH我可以与cmp $number,%reg有关,因为我开始使用" yoda"在C ++中格式化以避免if中的变量值变化(比较:if (0 = variable)if (variable = 0),两者都有拼写错误=而不是想要的== .. " yoda"即使有警告OFF也不会编译。)

但是......哦......这是我本周的最后一次AT& T ASM答案,它让我感到很生气。 (我知道这是个人偏好,但所有额外的$%使我感到烦恼,就像颠倒的顺序一样。)

拜托,我花了很多时间写这篇文章。尽量花时间研究它,并试图理解它。如果感到困惑,请在评论中提问,但如果你完全错过了这一点并且没有从中学到任何有用的东西,那将是可怜的浪费我们的时间。 :)所以继续。

最后的注意事项:并努力寻找一些调试器,找到适合自己的东西(可能是一些视觉上适合你的东西" TD"来自Borland的DOS时代对于新人来说会非常好),但它&# 39; s 绝对必要让您快速改进,能够通过指令逐步指令,并观察寄存器和内存内容如何改变值。真的,如果你能够调试自己的代码,你很快就会意识到你正在从%ebx中的错误文件句柄中读取第二个字符......(至少我希望如此)。

答案 1 :(得分:0)

早期清除1):add $1, %esi确实等同于inc %esi

当你学习汇编程序时,我会选择inc变体,所以你不要忘记它的存在并习惯它。回到286-586次,它的执行速度也会更快,今天使用add代替 - 因为微架构(μops)的复杂性,其中inc对于CPU而言更加复杂(将其翻译为添加μops我猜,但你不应该在学习基础知识的同时担心这一点,而是要考虑源的“人类”可读性,不要有任何性能技巧。

这是正确的方法吗?

那么,首先应该决定是否要为每个字符解析它(或者更确切地说是byte,因为字符现在通常是utf8字形,其大小可以是1到6或者字节数; I “甚至不确定”或者用缓冲区处理它。

这两者的混合使代码中的其他错误变得容易。

从快速看,我看到了:

  • 您只读取每个系统调用的单个字节,但是您将其存储在新位置 在缓冲区+计数器中(为什么?只使用单字节缓冲区,如果你按字节工作)
  • 当计数器为8时,退出(不处理第8个 完全读取字节。)
  • popl%ebx第一次打开输出文件后,你永远失去输入文件描述符(泄漏文件句柄非常糟糕)
  • 然后从输出文件中读取第二个字符(从写入重用文件句柄)
  • 然后再次弹出%ebx,但命令行上没有第三个参数,即从堆栈中获取未定义的内存
  • 确实每次都重新打开输出文件,所以除非它处于追加模式,否则它将覆盖内容。

这可能是你所做的所有重大错误,但实际上这么多,我建议你从头开始。

我将尝试在下一个答案中快速完成我的版本(因为这个版本有点长),向您展示我将如何做到这一点。但首先请尝试(努力)找到我在上面强调的所有要点,并了解您的代码是如何工作的。如果你完全理解你的指令是做什么的,以及为什么他们真的做了我描述的错误,你将有更多的时间来设计你的下一个代码,再加上调试它。因此,您将真正找到并充分理解的点越多,对您来说就越好。

“BTW笔记”:

我从未做过linux asm编程(我现在很想在阅读你的努力之后做点什么),但是从一些关于系统调用的wiki中我读到了:

  

系统调用期间保留所有寄存器。

当然,eax中的返回值除外。

请记住这一点,如果您正确地对系统调用进行分组,它可能会在调用之前重复注册设置,从而为您节省一些麻烦。