Search code examples
stringassemblycomparenasmstring-comparison

Comparing two strings only works half of the time in Assembly


I made a program that should compare the user input and a string. It does that successfully the first time I type ping and responds with pong, but the second time it doesn't output anything. The third time it starts working again, and then the forth time it again stops working. What might be the problem? This is the relevant code:

key_press:
  mov ah, 0x00
  int 0x16
  cmp al, 0x21
  jge compare
  jl other_chars
  jmp key_press

compare:
  cmp al, 0x7e
  jl print

print:
  pusha
  mov ah, 0x0e
  int 0x10
  popa
  mov [si], al
  inc si
  jmp key_press

other_chars:
  cmp ah, 0x1C
  je enter
  cmp ah, 0x39
  je space
  cmp al, 0x8
  je backspace
  jmp key_press

enter:
  pusha
  mov ah, 0x0e
  mov al, 0x0a
  int 0x10
  mov al, 0x0d
  int 0x10
  popa
  mov di, reserved
  mov bx, ping_command
  jmp loop1

loop1:
  cmp di, si
  je middle_loop1
  cmp byte [bx], 0
  je go_back_loop1
  mov ch, [di]
  cmp ch, [bx]
  jne go_back_loop1
;  pusha
;  mov ah, 0x0e
;  mov al, [di]
;  int 0x10
;  popa
  inc di
  inc bx
  jmp loop1

go_back_loop1:
  mov si, reserved
  mov bx, ping_command
  jmp key_press

middle_loop1:
  mov bx, pong
  pusha
  jmp print_ping

print_ping:
  cmp byte [bx], 0
  je go_back_print_ping
  mov ah, 0x0e
  mov al, [bx]
  int 0x10
  inc bx
  jmp print_ping

go_back_print_ping:
  mov al, 0x0a
  int 0x10
  mov al, 0x0d
  int 0x10
  popa
  jmp key_press

This is the entire code:

org 0x7c00
bits 16

init:
  mov ah, 0x0e
  mov bx, string
  mov si, reserved
  jmp loop

loop:
  mov al, [bx]
  cmp al, 0
  je key_press
  int 0x10
  inc bx
  jmp loop

key_press:
  mov ah, 0x00
  int 0x16
  cmp al, 0x21
  jge compare
  jl other_chars
  jmp key_press

compare:
  cmp al, 0x7e
  jl print

print:
  pusha
  mov ah, 0x0e
  int 0x10
  popa
  mov [si], al
  inc si
  jmp key_press

other_chars:
  cmp ah, 0x1C
  je enter
  cmp ah, 0x39
  je space
  cmp al, 0x8
  je backspace
  jmp key_press

enter:
  pusha
  mov ah, 0x0e
  mov al, 0x0a
  int 0x10
  mov al, 0x0d
  int 0x10
  popa
  mov di, reserved
  mov bx, ping_command
  jmp loop1

loop1:
  cmp di, si
  je middle_loop1
  cmp byte [bx], 0
  je go_back_loop1
  mov ch, [di]
  cmp ch, [bx]
  jne go_back_loop1
;  pusha
;  mov ah, 0x0e
;  mov al, [di]
;  int 0x10
;  popa
  inc di
  inc bx
  jmp loop1

go_back_loop1:
  mov si, reserved
  mov bx, ping_command
  jmp key_press

middle_loop1:
  mov bx, pong
  pusha
  jmp print_ping

print_ping:
  cmp byte [bx], 0
  je go_back_print_ping
  mov ah, 0x0e
  mov al, [bx]
  int 0x10
  inc bx
  jmp print_ping

go_back_print_ping:
  mov al, 0x0a
  int 0x10
  mov al, 0x0d
  int 0x10
  popa
  jmp key_press

space:
  pusha
  mov ah, 0x0e
  mov al, ' '
  int 0x10
  popa
  mov byte [si], ' '
  inc si
  jmp key_press

backspace:
  pusha
  mov ah, 0x0e
  int 0x10
  mov al, ' '
  int 0x10
  mov al, 0x8
  int 0x10
  popa
  dec si
  jmp key_press

halt:
  hlt
  jmp halt

reserved resb 256
ping_command db "ping", 0
pong db "pong", 0
string db "Hello, World!", 0
times 510-($-$$) db 0
dw 0xAA55

Solution

  • In the function go_back_print_ping, jmp key_press should be changed to jmp go_back_loop1, so that it would set si back to reserved.

    This is the memory at `reserved` in the first/third/odd runs.
    
      di              si
    |---|---|---|---|---|-----
    | p | i | n | g | X | ...
    |---|---|---|---|---|-----
    

    X is just a garbage value, whatever happened to be there.

    si points directly to the first byte after the 'g' of 'ping', which is X.

    di will keep incrementing until it is equal to si. What would happen in that case? Well, we would have a je middle_loop1. If you follow the code sequence of middle_loop1, you'll notice that at no point in time is si set back to reserved.

    So in the second/fourth/even runs, this is the state of the memory region after reserved

      di                              si
    |---|---|---|---|---|---|---|---|---|-----
    | p | i | n | g | p | i | n | g | X | ...
    |---|---|---|---|---|---|---|---|---|-----
    

    si kept on incrementing, putting the second ping we write after the first one.

    Now again, di will keep incrementing, BUT we are in fact comparing the first ping, that was written the previous time, in this case, cmp byte [bx], 0 will be true before di is equal to si, making the program jump to go_back_loop1 this time setting si back to reserved BUT it never prints anything, and putting the program in a state where the bug will keep on happening in even runs.

    There are actually multiple ways this could be solved, other than what I suggested, there are also other problems. It's a really fun exercise to take a piece of paper and have the state of all registers and try to see what happens when, I think it's the best way to understand the problem. Good luck with your project.