© Jo Van Hoey 2019
J. Van HoeyBeginning x64 Assembly Programminghttps://doi.org/10.1007/978-1-4842-5076-1_20

20. File I/O

Jo Van Hoey1 
(1)
Hamme, Belgium
 

File manipulation can be complex in software development. Different operating systems have different methods for file management, each with a list of different options. In this chapter, we will discuss file I/O for Linux systems; you will see in Chapter 43 that file I/O in Windows is entirely different.

In Linux, file management is complex and involves creating and opening a file for read-only or read/write, writing to a new file or appending to a file, and deleting files...not to mention the security settings for ‘user’, ‘group’, and ‘other’. Brush up your admin skills on the Linux filesystem if necessary, and dust off your Linux system administration manual to refresh your memory. In the code, we specified only the flags for the current ‘user’, but you can also add flags for ‘group’ and ‘other’. If you have no clue what we are talking about, it is time to study a bit about basic Linux file management.

Using syscalls

Files are created, opened, closed, and so on, via a syscall . In this chapter, we will use a lot of syscalls, so we are going to simplify things a bit. At the beginning of our code, we will define constants that are easier to refer to than syscall numbers. You can recognize the syscall constants in the following code because they start with NR_. Using these NR_syscall constants makes the code more readable. You can find a list of syscall symbol names in the following file on your system:
/usr/include/asm/unistd_64.h

We will use the same names in our program. Note that there is also a file named unistd_32h for 32-bit legacy compatibility.

We also created symbolic constants for create flags, status flags, and access mode flags. These flags indicate if a file is to be created or appended, read-only, write-only, and so on. You can find a list and description of these flags in the file on your system.
/usr/include/asm-generic/fcntl.h

There these flags are given in octal notation (e.g., O_CREAT = 00000100). A value that starts with 0x is a hexadecimal value, and a value that starts with 0 without an x is an octal value. For readability, you can append the character q to an octal number.

When creating a file, the file permission will have to be specified. Remember in Linux, you have read, write, and execute permissions for user, group, and other. You can get an overview and find out a lot of subtleties with the following CLI command:
      man 2 open

The file permissions are also given in octal notation and are familiar to a Linux system administrator. For the sake of consistency, we will borrow the symbolic names used in these files.

The example program is quite lengthy, but we will analyze it step-by-step, which can be accomplished using conditional assembly. This gives you a chance to analyze the program piece by piece.

File Handling

In the program, we do the following:
  1. 1.

    Create a file and then write data in the file.

     
  2. 2.

    Overwrite part of the content of the file.

     
  3. 3.

    Append data to the file.

     
  4. 4.

    Write data at a certain position in the file.

     
  5. 5.

    Read data from the file.

     
  6. 6.

    Read data from a certain position in the file.

     
  7. 7.

    Delete the file.

     
Listing 20-1 shows the code.
; file.asm
section .data
; expressions used for conditional assembly
      CREATE     equ   1
      OVERWRITE  equ   1
      APPEND     equ   1
      O_WRITE    equ   1
      READ       equ   1
      O_READ     equ   1
      DELETE     equ   1
; syscall symbols
      NR_read    equ   0
      NR_write   equ   1
      NR_open    equ   2
      NR_close   equ   3
      NR_lseek   equ   8
      NR_create  equ   85
      NR_unlink  equ   87
; creation and status flags
      O_CREAT    equ   00000100q
      O_APPEND   equ   00002000q
; access mode
      O_RDONLY   equ   000000q
      O_WRONLY   equ   000001q
      O_RDWR     equ   000002q
; create mode (permissions)
      S_IRUSR    equ   00400q      ;user read permission
      S_IWUSR    equ   00200q      ;user write permission
      NL         equ   0xa
      bufferlen        equ   64
      fileName   db    "testfile.txt",0
      FD         dq    0    ; file descriptor
      text1 db   "1. Hello...to everyone!",NL,0
      len1  dq   $-text1-1       ;remove 0
      text2 db   "2. Here I am!",NL,0
      len2  dq   $-text2-1       ;remove 0
      text3 db   "3. Alife and kicking!",NL,0
      len3  dq   $-text3-1       ;remove 0
      text4 db   "Adios !!!",NL,0
    len4    dq   $-text4-1
    error_Create db "error creating file",NL,0
    error_Close  db "error closing file",NL,0
    error_Write  db "error writing to file",NL,0
    error_Open   db "error opening file",NL,0
    error_Append db "error appending to file",NL,0
    error_Delete db "error deleting file",NL,0
    error_Read   db "error reading file",NL,0
    error_Print  db "error printing string",NL,0
    error_Position db "error positioning in file",NL,0
    success_Create    db "File created and opened",NL,0
    success_Close     db "File closed",NL,NL,0
    success_Write     db "Written to file",NL,0
    success_Open      db "File opened for R/W",NL,0
    success_Append    db "File opened for appending",NL,0
    success_Delete    db "File deleted",NL,0
    success_Read      db "Reading file",NL,0
    success_Position  db "Positioned in file",NL,0
section .bss
      buffer resb bufferlen
section .text
      global main
main:
      push rbp
      mov  rbp,rsp
%IF CREATE
;CREATE AND OPEN A FILE, THEN CLOSE --------------------
; create and open file
      mov   rdi, fileName
      call  createFile
      mov   qword [FD], rax ; save descriptor
; write to file #1
      mov   rdi, qword [FD]
      mov   rsi, text1
      mov   rdx, qword [len1]
      call  writeFile
; close file
      mov   rdi, qword [FD]
      call  closeFile
%ENDIF
%IF OVERWRITE
;OPEN AND OVERWRITE A FILE, THEN CLOSE -----------------
; open file
      mov   rdi, fileName
      call  openFile
      mov   qword [FD], rax ; save file descriptor
; write to file #2 OVERWRITE!
      mov   rdi, qword [FD]
      mov   rsi, text2
      mov   rdx, qword [len2]
      call  writeFile
; close file
      mov   rdi, qword [FD]
      call  closeFile
%ENDIF
%IF APPEND
;OPEN AND APPEND TO A FILE, THEN CLOSE ----------------
; open file to append
      mov   rdi, fileName
      call  appendFile
      mov   qword [FD], rax ; save file descriptor
; write to file #3 APPEND!
      mov   rdi, qword [FD]
      mov   rsi, text3
      mov   rdx, qword [len3]
      call  writeFile
; close file
      mov   rdi, qword [FD]
      call  closeFile
%ENDIF
%IF O_WRITE
;OPEN AND OVERWRITE AT AN OFFSET IN A FILE, THEN CLOSE ----
; open file to write
      mov   rdi, fileName
      call  openFile
      mov   qword [FD], rax ; save file descriptor
; position file at offset
      mov   rdi, qword[FD]
      mov   rsi, qword[len2] ;offset at this location
      mov   rdx, 0
      call  positionFile
; write to file at offset
      mov   rdi, qword[FD]
      mov   rsi, text4
      mov   rdx, qword [len4]
      call  writeFile
; close file
      mov   rdi, qword [FD]
      call  closeFile
%ENDIF
%IF READ
;OPEN AND READ FROM A FILE, THEN CLOSE ----------------
; open file to read
      mov   rdi, fileName
      call  openFile
      mov  qword [FD], rax ; save file descriptor
; read from file
      mov   rdi, qword [FD]
      mov   rsi, buffer
      mov   rdx, bufferlen
      call  readFile
      mov   rdi,rax
      call  printString
; close file
      mov  rdi, qword [FD]
      call  closeFile
%ENDIF
%IF O_READ
;OPEN AND READ AT AN OFFSET FROM A FILE, THEN CLOSE -----
; open file to read
      mov   rdi, fileName
      call  openFile
      mov   qword [FD], rax ; save file descriptor
; position file at offset
      mov   rdi, qword[FD]
      mov   rsi, qword[len2]      ;skip the first line
      mov   rdx, 0
      call  positionFile
; read from file
      mov   rdi, qword [FD]
      mov   rsi, buffer
      mov   rdx, 10    ;number of characters to read
      call  readFile
      mov   rdi,rax
      call  printString
; close file
      mov   rdi, qword [FD]
      call  closeFile
%ENDIF
%IF DELETE
;DELETE A FILE ----------------------------------
; delete file   UNCOMMENT NEXT LINES TO USE
      mov   rdi, fileName
      call  deleteFile
%ENDIF
leave
ret
; FILE MANIPULATION FUNCTIONS--------------------
;------------------------------------------------
global readFile
readFile:
      mov   rax, NR_read
      syscall      ; rax contains # of characters read
      cmp   rax, 0
      jl    readerror
      mov   byte [rsi+rax],0 ; add a terminating zero
      mov   rax, rsi
      mov   rdi, success_Read
      push  rax        ; caller saved
      call  printString
      pop   rax        ; caller saved
      ret
readerror:
      mov   rdi, error_Read
      call  printString
      ret
;--------------------------------------------------
global deleteFile
deleteFile:
      mov   rax, NR_unlink
      syscall
      cmp   rax, 0
      jl    deleteerror
      mov   rdi, success_Delete
      call  printString
      ret
deleteerror:
      mov   rdi, error_Delete
      call  printString
      ret
;-------------------------------------------------
global appendFile
appendFile:
      mov   rax, NR_open
      mov   rsi,  O_RDWR|O_APPEND
      syscall
      cmp   rax, 0
      jl    appenderror
      mov   rdi, success_Append
      push  rax        ; caller saved
      call  printString
      pop   rax        ; caller saved
      ret
appenderror:
      mov   rdi, error_Append
      call  printString
      ret
;------------------------------------------------
global openFile
openFile:
      mov   rax, NR_open
      mov   rsi, O_RDWR
      syscall
      cmp   rax, 0
      jl    openerror
      mov   rdi, success_Open
      push  rax        ; caller saved
      call  printString
      pop   rax        ; caller saved
      ret
openerror:
      mov   rdi, error_Open
      call  printString
      ret
;----------------------------------------------
global writeFile
writeFile:
      mov   rax, NR_write
      syscall
      cmp   rax, 0
      jl    writeerror
      mov   rdi, success_Write
      call  printString
      ret
writeerror:
      mov   rdi, error_Write
      call  printString
      ret
;---------------------------------------------
global positionFile
positionFile:
      mov   rax, NR_lseek
      syscall
      cmp   rax, 0
      jl    positionerror
      mov   rdi, success_Position
      call  printString
      ret
positionerror:
      mov   rdi, error_Position
      call  printString
      ret
;---------------------------------------------
global closeFile
closeFile:
      mov   rax, NR_close
      syscall
      cmp   rax, 0
      jl    closeerror
      mov   rdi, success_Close
      call  printString
      ret
closeerror:
      mov   rdi, error_Close
      call  printString
      ret
;------------------------------------------------
global createFile
createFile:
      mov   rax, NR_create
      mov   rsi, S_IRUSR |S_IWUSR
      syscall
      cmp   rax, 0                 ; file descriptor in rax
      jl    createerror
      mov   rdi, success_Create
      push  rax        ; caller saved
      call  printString
      pop   rax        ; caller saved
      ret
createerror:
      mov   rdi, error_Create
      call  printString
      ret
; PRINT FEEDBACK
;-----------------------------------------------
global printString
printString:
; Count characters
      mov   r12, rdi
      mov   rdx, 0
strLoop:
      cmp   byte [r12], 0
      je    strDone
      inc   rdx                    ;length in rdx
      inc   r12
      jmp   strLoop
strDone:
      cmp   rdx, 0                 ; no string (0 length)
      je    prtDone
      mov   rsi,rdi
      mov   rax, 1
      mov   rdi, 1
      syscall
prtDone:
      ret
Listing 20-1

file.asm

Conditional Assembly

Because this is quite a long program, to make it easier to analyze, we use conditional assembly. We created different constants such as CREATE, WRITE, APPEND, and so on. If you set such a variable to 1, then certain code, enclosed by %IF 'variable' and %ENDIF, will be assembled. If that variable is set to 0, the assembler will ignore the code. The %IF and %ENDIF parts are called assembler preprocessor directives. Start with the variable CREATE equ 1, and set the other variables equal to 0, assemble, run, and analyze the program. Gradually work your way down. Continue with CREATE equ 1 and OVERWRITE equ 1 and set the other variables equal to 0 on the second build, and so on.

NASM gives you a considerable collection of preprocessor directives; here we use conditional assembly. To define macros, as we explained before, we also used preprocessor directives. In Chapter 4 of the NASM manual, you will find a complete description of preprocessor directives.

The File-Handling Instructions

Let’s begin with creating a file. Move the file name into rdi, and call createFile. In createFile, put the symbolic variable NR_create into rax, and specify in rsi the flags for creating the file. In this case, the user gets read and write permissions and then does a syscall.

When for some reason the file cannot be created, createFile returns a negative value in rax, and in this case we want an error message to be displayed. If you want more detail, the negative value in rax indicates what kind of error occurred. If the file is created, the function returns a file descriptor in rax. In the calling program, we save the file descriptor to the variable FD for further file manipulations. You can see that we have to be careful to preserve the content of rax before calling the printString function . A call to printString will destroy the content of rax, so we push rax to the stack before calling. According to the calling conventions, rax is a caller-saved register.

Next in the code, some text is written to the file, and then the file is closed. Note that when you create a file, a new file will be created; if a file exists with the same name, it will be deleted.

Build and run the program with CREATE equ 1; the other conditional assembly variables equal 0. Then go to the command prompt and verify that a testfile.txt file is created and that it has the message in it. If you want to see the content of the file in hexadecimal, which is sometimes useful, use xxd testfile.txt at the CLI prompt.

Continue by gradually putting the conditional assembly variables to 1, one at the time, and check in testfile.txt what happens.

Note that in this case we created and used functions without a function prologue and epilogue. Figure 20-1 shows the output, with all the conditional assembly variables set to 1.
../images/483996_1_En_20_Chapter/483996_1_En_20_Fig1_HTML.jpg
Figure 20-1

file.asm output

Summary

In this chapter, you learned about the following:
  • File creation, opening, closing, deleting

  • Writing to a file, appending to a file, and writing to a file at a specific position

  • Reading from a file

  • The different parameters for file handling

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.163.197