1

I am trying to write a program that compares single characters, but it says the buffer is not equal to 'S' even though I gave one as input, I know there comes a new-line and terminating sign after the input string but isn't [.buffer] just supposed to give the first character of the string?

section .text
    global main

main:
    jmp start_promt

start_promt:
section .data
    .promt       db      "Choose one of the following instructions: (S)tart, (Q)uit",10,0
    .promtLen    equ     $-.promt-1   ; memory address of begin of this instruction minus memory address of begin of promt gives length of promt  
    .bufsize     dq      100
section .bss
    .buffer resd 100
section .text
    mov     rax, 1          ; rax <- 0 (syscall number for 'write')
    mov     rdi, 1          ; rdi <- 0 (stdout file descriptor)
    mov     rsi, .promt      ; address of prompt message
    mov     rdx, .promtLen   ; size of promt message
    syscall                 ; execute  write(1, promt, promtLen)

    xor rax, rax            ; rax <- 0 (syscall number for 'read')
    xor rdi, rdi            ; rdi <- 0 (stdin file descriptor)
    mov rsi, .buffer         ; rsi <- address of the buffer.  lea rsi, [rel buffer]
    mov rdx, [.bufsize]        ; rdx <- size of the buffer
    syscall                 ; execute  read(0, buffer, BUFSIZE)

    mov rdx, [.buffer]

    cmp rdx, 'S'
    je start_game 

    cmp rdx, 'Q'
    je quit


    jmp start_promt

start_game:
; loop for user-instructions
; if done
    ;jmp start_promt

quit: 
    mov rax, 60
    mov rdi, 0
    syscall
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Vexea
  • 167
  • 1
  • 1
  • 8
  • Why do you think you need `-1` in `$-.promt-1`? – David C. Rankin Jan 15 '22 at 15:28
  • To remove the termination character – Vexea Jan 15 '22 at 15:29
  • I see way on the right side. Is there any need to create `.promt` with the nul-terminating character since you are using `write` for output? Let me look further. – David C. Rankin Jan 15 '22 at 15:37
  • I saw this in a book, I dont actually write the termination sign since I write length - 1 – Vexea Jan 15 '22 at 15:39
  • 2
    You're fine there -- your problem is *Register Size*. You don't put a byte in `rdx`, you put 1-byte in `dl`, e.g. `mov dl, [.buffer]` and then `cmp dl, 'S'` and `cmp dl, 'Q'` (you know `dl` byte register, `dx` 2-bytes, `edx` 4-bytes, and finally `rdx` 8-bytes.) That said, unless you are using `.promt` as a nul-terminated string, there is no reason to make it one.the `write` syscall doesn't need it, and from what you posted, nothing else does either. Up to you, just no need for it. – David C. Rankin Jan 15 '22 at 16:19
  • Thanks, this seems like a solid answer – Vexea Jan 15 '22 at 16:32

1 Answers1

3

Continuing from the comment above, you comparison is failing because you are attempting to compare a single byte 'S' or 'Q' against an 8-byte register. That won't work. rdx has 7 other bytes that will not be 'S' or 'Q' and the test will fail every time.

Stepping each instruction in gdb makes it easy to see. Run gdb yourexecutable, then break _start to create a breakpoint at the program start. (for convenience display the current command being executed by setting display/i $pc) Now just use si to step-instruction through your code. When you get to your data entry and comparison, examine the contents of both .buffer and rdx, e.g.

1: x/i $pc
=> 0x4000e5 <start_promt+51>:   syscall
(gdb)
SoooRO
0x00000000004000e7 in start_promt ()
1: x/i $pc
=> 0x4000e7 <start_promt+53>:   mov    0x600150,%rdx
(gdb) x/6c 0x600150
0x600150:       83 'S'  111 'o' 111 'o' 111 'o' 82 'R'  79 'O'
(gdb) si
0x00000000004000ef in start_promt ()
1: x/i $pc
=> 0x4000ef <start_promt+61>:   cmp    $0x53,%rdx
(gdb) info reg rdx
rdx            0xa4f526f6f6f53     2901965242593107

Above you can see "SoooRO" was entered at the prompt just to fill a few extra characters. The address of .buffer is 0x600150 so you can check the contents of buffer with x/6c 0x600150 (examine 6-characters from buffer) and you see:

0x600150:       83 'S'  111 'o' 111 'o' 111 'o' 82 'R'  79 'O'

All good -- so why did the compare fail? That is shown in the debug as well:

=> 0x4000ef <start_promt+61>:   cmp    $0x53,%rdx
(gdb) info reg rdx
rdx            0xa4f526f6f6f53     2901965242593107

Where you see the register holds 0xa4f526f6f6f53 (the last byte is ASCII 0x53 - 'S').

So how to fix this? Use the correct register to store a byte. dl is the one-byte register, dx is a two-byte register, edx is a four-byte register and rdx is an eight-byte register. So moving the first character to dl and then comparing with dl will make the single-character comparison you are looking for, e.g.

    mov dl, [.buffer]

    cmp dl, 'S'
    je start_game 

    cmp dl, 'Q'
    je quit

Let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Can I also do, mov rdx, [.buffer], cmp dl, 'S'? – Vexea Jan 15 '22 at 16:50
  • You can, but I find it more readable to use the proper size register in all places. You are dereferencing `.buffer`, e.g. `[.buffer]` and when you dereference a pointer you get the first element of the type pointed-to. In the case of a character-string (pointer to char), you get a single byte. Keeping your register size consistent not only helps you follow the flow, but also anyone who has to maintain the code you have written later. – David C. Rankin Jan 15 '22 at 17:27
  • 1
    @Vexea: Yes, since your `.buffer` is 100 x 4 bytes long, an 8-byte load from the start of it can't cross into an unmapped page and segfault. And it's aligned by at least 8 so your 8-byte load is naturally aligned and won't have any performance downside e.g. from being split across two cache lines. Of course, it's pointlessly inefficient to use an instruction that needs a REX prefix to specify 8-byte operand-size instead of `mov edx, [.buffer]`, which would actually be the most efficient way in terms of code-size, equal to `movzx edx, byte [.buffer]` which is the normal way to load a byte. – Peter Cordes Jan 15 '22 at 17:27
  • 1
    @Vexea: Of course, for code-size you could do `cmp byte [.buffer], 'S'`, but if you're doing other compares on the byte you should load it into a reg once. `cmp` with a RIP-relative addressing mode and an immediate can't micro-fuse into a single uop, and IIRC can't macro-fuse the following jcc instruction either. (`mov dl, [.buffer]` has the downside of a false dependency on the old value of RDX, just merging a new low byte into it. If you don't specifically want that, use `movzx`. Also use `default rel` somewhere in your source because NASM defaults to absolute addressing.) – Peter Cordes Jan 15 '22 at 17:30