Sk6
Sk6

Reputation: 27

syscall read/write 1 byte only?

I am new to assembly and trying to write a version of the "echo" built-in but only operating on 1 byte at a time.

I have the following which works the way I want, except it overflows more than 1 byte on both read and write even though I explicitly say 1 byte in x2 on both syscalls. What am I doing wrong?

Example run:

sh-4.2$ ./echo1b
f
f
o
o
b
b
bar
bar
bazbazbaz
bazbazbaz
q
sh-4.2$

Here is the code:

.data
temp:   .byte 1  

.text
.globl _start
_start:
    /* read one byte from stdin, store to temp */
    mov x0, #0x0
    adr x1, temp
    mov x2, #0x1
    mov x8, #0x3F
    svc #0x0

    /* write newline to stdout */
    mov x0, #0x1
    mov x1, #0xA
    mov x2, #0x1
    mov x8, #0x40
    svc #0x0
    
    /* if byte in temp is "q", exit */
    mov x5, #0x71
    ldr x1, temp
    cmp x1, x5
    beq exit

    /* otherwise, write it to stdout and repeat */
    mov x0, #0x1
    adr x1, temp
    mov x2, #0x1
    mov x8, #0x40
    svc #0x0
    b _start

exit:
    /* exit cleanly */  
    eor x0, x0, x0 
    eor x1, x1, x1
    eor x2, x2, x2
    mov x8, #0x5D
    svc #0x0

Upvotes: 1

Views: 1102

Answers (1)

fcdt
fcdt

Reputation: 2493

There are several issues in your code:

  • As mentioned in the comments, the output buffer's address has to be in x1 when calling sys_write as you did with temp
  • When comparing temp with the newline character, you had to use ldrb w1, [x0] instead of ldr x1, temp where x0 points to temp. The latter would read 4 bytes while it's not guaranteed that the upper three bytes are zero.

I also improved some parts of your code:

  • cmp can be used with a 12 bit immediate so there's no need to put 0x71 in a register.
  • Moving the second sys_write call before _start avoids the unconditional jump.
  • sys_exit uses only x0 as parameter so there's no need to set x1 and x2 to zero.

Here's the final code, tested on Raspbian 4.19 (debian based):

.data
    temp:    .byte 1
    newline: .byte 0x0A

.text
.globl _start

loop:
    // 4: Otherwise, write it to stdout and repeat
    mov  x0, #0x1    // int    fd
    adr  x1, temp    // void*  buf
    mov  x2, #0x1    // size_t count
    mov  x8, #0x40   // sys_write
    svc  #0x0
    
_start:
    // 1: Read one byte from stdin and store to temp (including newline)
    mov  x0, #0x0   // int    fd
    adr  x1, temp   // void*  buf
    mov  x2, #0x1   // size_t count
    mov  x8, #0x3F  // sys_read
    svc  #0x0
    
    // 2: If byte in temp is 'q', exit
    adr  x0, temp
    ldrb w1, [x0] // instead of temp
    cmp  x1, #0x71
    bne  loop

    // 5: Exit cleanly
    eor  x0, x0, x0  // int status
    mov  x8, #0x5D   // sys_exit
    svc  #0x0

Edit after comment: To flush stdin at exit, you could add this lines before step 5:

    // 5: Flush stdin (read until newline)
flush:
    mov  x0, #0x0   // int    fd
    adr  x1, temp   // void*  buf
    mov  x2, #0x1   // size_t count
    mov  x8, #0x3F  // sys_read
    svc  #0x0

    adr  x0, temp
    ldrb w1, [x0]
    cmp  x1, #0x0A
    bne flush       // loop until x0 == 0x0A

Upvotes: 5

Related Questions