Add Comments to x86 Assembly Language Program
Assembly Programming
Example 1:
Do not say that a function “computes the result of a1*b1 and then adds to it the result of
a2*b2”. Instead say that the function “computes the vector product of A =[a1 a2] and B=[b1 b2]T”.
Example 2:
Do not say that “EAX is XORed with itself”. Instead say that “EAX is zeroed efficiently”.
With that said, you are encouraged to try to restore the [REDACTED] comments on the code. Not allof them were particularly informative initially, but obviously some of them shed light into non-obviousmanipulations. If you can understand said manipulations, restoring the comment will speed up yourunderstanding of the full code.
With the exception of lol, top, kek, and magic, all labels are “meaningful”, well, to me, at least!
Nothing in the source code is false nor deliberately misleading.
Jorge_TTT_sanitized.asm
%define cmpb cmp byte
%define decb dec byte
%define incb inc byte
%define xorb xor byte
%define movb mov byte
%define movq mov qword
section .bss ; Uninitialized data
buffer resb 4
pfunc resq 1
section .data ; Initialized data
mdbg db “Do you want to enable debug information?”, 0xA
mdbgl equ $ – mdbg
mPXO db “Player X”
mPXOl equ $ – mPXO
mplay2 db “, choose your location (0-8):”, 0xA, “Current board:”, 0xA
mplay2l equ $ – mplay2
mwin2 db ” wins!”, 0xA
mwin2l equ $ – mwin2
spot equ 7 ; [REDACTED]
magic equ 0x17 ; Magic! =D
mtie db “It’s a draw (tie)!”, 0xA
mtiel equ $ – mtie
mfinal db “Final board:”, 0xA
mfinall equ $ – mfinal
merr db “That location is out of range or already taken.”, 0xA
merrl equ $ – merr
pbd1 db ” | | “, 0xA
pbd1l equ $ – pbd1
pbd2 db “—–“, 0xA
pbd2l equ $ – pbd2
dbd1 db ” 012345678 “, 0xA, “[”
dbd1l equ $ – dbd1
board db ” ” ; 3×3 linearized game board
bdl equ $ – board
dbd2 db “]”, 0xA
dbd2l equ $ – dbd2
ebd1 db “Current board (hex):”, 0xA, ” 0 1 2 3 4 5 6 7 8 “, 0xA, “[”
ebd1l equ $ – ebd1
eboard db “20 58 20 4F 20 58 20 20 4F” ; [REDACTED]
el equ $ – eboard
ebd2 db “]”, 0xA
ebd2l equ $ – ebd2
hexdigits db ‘0123456789ABCDEF’
mbd1 db “Current board (mem):”, 0xA, “&board = 0x”
mbd1l equ $ – mbd1
mboard db “FFFFFFFFFFFFFFFF”
ml equ $ – mboard
mbd2 db 0xA, “+offset / hex / ASCII”, 0xA
mbd2l equ $ – mbd2
mbd3 db “0x0/: 58h X”, 0xA ; [REDACTED]
mbd3l equ $ – mbd3
lol db 2,1,6,3,8,4,9,9, \
2,0,7,4,9,9,9,9, \
1,0,8,5,6,4,9,9, \
5,4,6,0,9,9,9,9, \
5,3,7,5,8,0,6,2, \
4,3,8,2,9,9,9,9, \
8,7,3,0,4,2,9,9, \
8,6,5,4,9,9,9,9, \
7,6,5,2,4,0,9,9
top equ lol+7
kek db 5,3,5,3,7,3,5,3,5
section .text ; Code
global _start ; Export entry point
print_int: ;ecx: const char* msg, edx: size_tmsgl
mov eax,4 ; System call number (sys_write)
mov ebx,1 ; First argument: file descriptor (stdout == 1)
int 0x80 ; Call kernel
ret
read_int: ;ecx: char* msg, ; edx: size_tmsgl
mov eax,3 ; System call number (sys_read)
xor ebx,ebx ; First argument: file descriptor (stdin == 0)
int 0x80 ; Call kernel
ret
check_line: ; [REDACTED]
mov bl,[mPXO+spot] ; [REDACTED]
add bl,bl ; [REDACTED]
sub bl,[board+esi] ; [REDACTED]
sub bl,[board+edi] ; [REDACTED]
jz win
ret
tie: ; No return, (it’s a tie)
mov ecx,mtie ; Second argument: pointer to message to write
mov edx,mtiel+mfinall ; Third argument: message length
call print_int
jmp pfinalb
win: ; No return, (someone won)
mov ecx,mPXO ; Second argument: pointer to message to write
mov edx,mPXOl ; Third argument: message length
call print_int
mov ecx,mwin2 ; Second argument: pointer to message to write
mov edx,mwin2l ; Third argument: message length
call print_int
mov ecx,mfinal ; Second argument: pointer to message to write
mov edx,mfinall ; Third argument: message length
call print_int
; Fallthrough
pfinalb: ; No return, (print final board and exit)
call [pfunc]
mov eax,1 ; System call number (sys_exit)
xor ebx,ebx ; First syscall argument: exit code
int 0x80 ; Call kernel
; No ret
debug_board:
mov ecx,dbd1 ; Second argument: pointer to message to write
mov edx,dbd1l+bdl+dbd2l ; Third argument: message length
call print_int
mov ecx,8 ; Locations
hexboard:
mov bl,[board+ecx]
mov dx,’20’
cmp bl,’ ‘
cmove ax,dx
mov dx,’58’
cmp bl,’X’
cmove ax,dx
mov dx,’4F’
cmp bl,’O’
cmove ax,dx
mov [eboard+2*ecx+ecx],al
mov [eboard+2*ecx+ecx+1],ah
dec ecx
jns hexboard
mov ecx,ebd1 ; Second argument: pointer to message to write
mov edx,ebd1l+el+ebd2l ; Third argument: message length
call print_int
mov ecx,mbd1 ; Second argument: pointer to message to write
mov edx,mbd1l+ml+mbd2l ; Third argument: message length
call print_int
mov rsi,board
mov rdi,mbd3
memboard:
incb [rdi+3] ; [REDACTED]
mov bl,[rsi] ; [REDACTED]
mov [rdi+10],bl
mov dx,’20’
cmp bl,’ ‘
cmove ax,dx
mov dx,’58’
cmp bl,’X’
cmove ax,dx
mov dx,’4F’
cmp bl,’O’
cmove ax,dx
mov [rdi+6],ax ; [REDACTED]
mov ecx,mbd3 ; Second argument: pointer to message to write
mov edx,mbd3l ; Third argument: message length
call print_int
inc rsi
cmp rsi,board+9
jne memboard
movb [rdi+3],’/’ ; [REDACTED]
ret
print_board:
xor esi,esi ; [REDACTED]
nrow:
mov edi,2 ; [REDACTED]
ncol:
mov dl,[board+esi+edi] ; [REDACTED]
mov [pbd1+edi*2],dl ; [REDACTED]
dec edi
jns ncol ; [REDACTED]
mov ecx,pbd1 ; Second argument: pointer to message to write
add esi,3 ; [REDACTED]
cmp esi,9 ; [REDACTED]
je pdone
mov edx,pbd1l+pbd2l ; Third argument: message length
call print_int
jmp nrow
pdone:
mov edx,pbd1l ; Third argument: message length
call print_int
ret
_start:
; Enable debug?
mov ecx,mdbg ; Second argument: pointer to message to write
mov edx,mdbgl ; Third argument: message length
call print_int
; Read answer
mov ecx,buffer ; Store input at location ‘buffer’
mov edx,2 ; Read these many bytes
call read_int
; [REDACTED]
cmpb [buffer],’Y’
je do_debug
cmpb [buffer],’y’
je do_debug
cmpb [buffer],’D’
je do_debug
cmpb [buffer],’d’
je do_debug
; [REDACTED]
movq [pfunc],print_board
jmp play
do_debug:
movq [pfunc],debug_board
mov ecx,15 ; [REDACTED]
mov rdx, board ; [REDACTED]
mov rbx, hexdigits ; [REDACTED]
memheader:
mov rax,rdx ; [REDACTED]
and rax,0x000000000000000f
xlatb ; [REDACTED]
mov byte [mboard+ecx],al
dec ecx
mov rax,rdx ; [REDACTED]
and rax,0x00000000000000f0
shr rax,4 ; [REDACTED]
xlatb ; [REDACTED]
mov byte [mboard+ecx],al
shr rdx,8 ; [REDACTED]
dec ecx
jns memheader
jmp play
invalid:
mov ecx,merr ; Second argument: pointer to message to write
mov edx,merrl ; Third argument: message length
call print_int
; Fallthrough
play:
; Print messages and board
mov ecx,mPXO ; Second argument: pointer to message to write
mov edx,mPXOl+mplay2l ; Third argument: message length
call print_int
call [pfunc]
; Read input
mov ecx,buffer ; Store input at location ‘buffer’
mov edx,2; ; Read these many bytes
call read_int
; Validate
movzx eax, byte [buffer]
sub al,’0′ ; [REDACTED]
cmp al,8
ja invalid
; Range is valid
cmpb [board+eax],’ ‘ ; Is empty?
jne invalid
; Move is fully valid
mov bl,[mPXO + spot]
mov [board+eax],bl ; [REDACTED]
; [REDACTED]
movzx ecx, byte [kek+eax] ; [REDACTED]
pair:
movzx esi, byte [lol+eax*8+ecx]
dec ecx
movzx edi, byte [lol+eax*8+ecx]
call check_line
dec ecx
jns pair ; [REDACTED]
decb [top] ; [REDACTED]
jz tie
xorb [mPXO+spot],magic ; [REDACTED]
jmp play
Solution
Jorge_TTT_sanitized.asm
%define cmpb cmp byte
%define decb dec byte
%define incb inc byte
%define xorb xor byte
%define movb mov byte
%define movq mov qword
section .bss ; Uninitialized data
buffer resb 4
pfunc resq 1
section .data ; Initialized data
mdbg db “Do you want to enable debug information?”, 0xA
mdbgl equ $ – mdbg
mPXO db “Player X”
mPXOl equ $ – mPXO
mplay2 db “, choose your location (0-8):”, 0xA, “Current board:”, 0xA
mplay2l equ $ – mplay2
mwin2 db ” wins!”, 0xA
mwin2l equ $ – mwin2
spot equ 7 ; position of the player mark in the mPXO message
magic equ 0x17 ; Magic! =D
mtie db “It’s a draw (tie)!”, 0xA
mtiel equ $ – mtie
mfinal db “Final board:”, 0xA
mfinall equ $ – mfinal
merr db “That location is out of range or already taken.”, 0xA
merrl equ $ – merr
pbd1 db ” | | “, 0xA
pbd1l equ $ – pbd1
pbd2 db “—–“, 0xA
pbd2l equ $ – pbd2
dbd1 db ” 012345678 “, 0xA, “[”
dbd1l equ $ – dbd1
board db ” ” ; 3×3 linearized game board
bdl equ $ – board
dbd2 db “]”, 0xA
dbd2l equ $ – dbd2
ebd1 db “Current board (hex):”, 0xA, ” 0 1 2 3 4 5 6 7 8 “, 0xA, “[”
ebd1l equ $ – ebd1
eboard db “20 58 20 4F 20 58 20 20 4F” ; template used to display the linear board in hexadecimal
el equ $ – eboard
ebd2 db “]”, 0xA
ebd2l equ $ – ebd2
hexdigits db ‘0123456789ABCDEF’
mbd1 db “Current board (mem):”, 0xA, “&board = 0x”
mbd1l equ $ – mbd1
mboard db “FFFFFFFFFFFFFFFF”
ml equ $ – mboard
mbd2 db 0xA, “+offset / hex / ASCII”, 0xA
mbd2l equ $ – mbd2
mbd3 db “0x0/: 58h X”, 0xA ; template used for printing board positions and hex contents one line at a time
; position initialized to ‘/’ so an increment will bring it to ‘0’
mbd3l equ $ – mbd3
lol db 2,1,6,3,8,4,9,9, \
2,0,7,4,9,9,9,9, \
1,0,8,5,6,4,9,9, \
5,4,6,0,9,9,9,9, \
5,3,7,5,8,0,6,2, \
4,3,8,2,9,9,9,9, \
8,7,3,0,4,2,9,9, \
8,6,5,4,9,9,9,9, \
7,6,5,2,4,0,9,9
top equ lol+7
kek db 5,3,5,3,7,3,5,3,5
section .text ; Code
global _start ; Export entry point
print_int: ;ecx: const char* msg, edx: size_tmsgl
mov eax,4 ; System call number (sys_write)
mov ebx,1 ; First argument: file descriptor (stdout == 1)
int 0x80 ; Call kernel
ret
read_int: ;ecx: char* msg, ; edx: size_tmsgl
mov eax,3 ; System call number (sys_read)
xor ebx,ebx ; First argument: file descriptor (stdin == 0)
int 0x80 ; Call kernel
ret
check_line: ; check if the two positions edi and esi in the board contain the current player mark
mov bl,[mPXO+spot] ; load the current player’s mark in bl
add bl,bl ; put 2 times the current mark in bl
sub bl,[board+esi] ; subtract the character in position esi in the board from bl
sub bl,[board+edi] ; subtract the character in position edi in the board from bl
jz win
ret
tie: ; No return, (it’s a tie)
mov ecx,mtie ; Second argument: pointer to message to write
mov edx,mtiel+mfinall ; Third argument: message length
call print_int
jmp pfinalb
win: ; No return, (someone won)
mov ecx,mPXO ; Second argument: pointer to message to write
mov edx,mPXOl ; Third argument: message length
call print_int
mov ecx,mwin2 ; Second argument: pointer to message to write
mov edx,mwin2l ; Third argument: message length
call print_int
mov ecx,mfinal ; Second argument: pointer to message to write
mov edx,mfinall ; Third argument: message length
call print_int
; Fallthrough
pfinalb: ; No return, (print final board and exit)
call [pfunc]
mov eax,1 ; System call number (sys_exit)
xor ebx,ebx ; First syscall argument: exit code
int 0x80 ; Call kernel
; No ret
debug_board:
mov ecx,dbd1 ; Second argument: pointer to message to write
mov edx,dbd1l+bdl+dbd2l ; Third argument: message length
call print_int
mov ecx,8 ; Locations
hexboard:
mov bl,[board+ecx]
mov dx,’20’
cmp bl,’ ‘
cmove ax,dx
mov dx,’58’
cmp bl,’X’
cmove ax,dx
mov dx,’4F’
cmp bl,’O’
cmove ax,dx
mov [eboard+2*ecx+ecx],al
mov [eboard+2*ecx+ecx+1],ah
dec ecx
jns hexboard
mov ecx,ebd1 ; Second argument: pointer to message to write
mov edx,ebd1l+el+ebd2l ; Third argument: message length
call print_int
mov ecx,mbd1 ; Second argument: pointer to message to write
mov edx,mbd1l+ml+mbd2l ; Third argument: message length
call print_int
mov rsi,board
mov rdi,mbd3
memboard:
incb [rdi+3] ; increment the character representing the position
mov bl,[rsi] ; load the current char from the board and update template with it
mov [rdi+10],bl
mov dx,’20’
cmp bl,’ ‘
cmove ax,dx
mov dx,’58’
cmp bl,’X’
cmove ax,dx
mov dx,’4F’
cmp bl,’O’
cmove ax,dx
mov [rdi+6],ax ; save board character translation to hex in the template
mov ecx,mbd3 ; Second argument: pointer to message to write
mov edx,mbd3l ; Third argument: message length
call print_int
inc rsi
cmp rsi,board+9
jne memboard
movb [rdi+3],’/’ ; restore the position in the template to ‘/’
ret
print_board:
xor esi,esi ; initialize esi to zero
nrow:
mov edi,2 ; we will fill the 3 chars in a single row on the board template (0 to 2)
ncol:
mov dl,[board+esi+edi] ; load character from the board at the current position
mov [pbd1+edi*2],dl ; save the character in the board line template
dec edi
jns ncol ; repeat while edi is not negative
mov ecx,pbd1 ; Second argument: pointer to message to write
add esi,3 ; advance to next row in the board by adding 3
cmp esi,9 ; if we get to position 9 we have printed all the board
je pdone
mov edx,pbd1l+pbd2l ; Third argument: message length
call print_int
jmp nrow
pdone:
mov edx,pbd1l ; Third argument: message length
call print_int
ret
_start:
; Enable debug?
mov ecx,mdbg ; Second argument: pointer to message to write
mov edx,mdbgl ; Third argument: message length
call print_int
; Read answer
mov ecx,buffer ; Store input at location ‘buffer’
mov edx,2 ; Read these many bytes
call read_int
; determine if the user entered Y, y, D or d
cmpb [buffer],’Y’
je do_debug
cmpb [buffer],’y’
je do_debug
cmpb [buffer],’D’
je do_debug
cmpb [buffer],’d’
je do_debug
; by default, use the print board function
movq [pfunc],print_board
jmp play
do_debug:
movq [pfunc],debug_board
mov ecx,15 ; we will use it to loop 16 times
mov rdx, board ; load the board address in rdx
mov rbx, hexdigits ; load the hexadecimal table for translating digits
memheader:
mov rax,rdx ; load the current address into rax
and rax,0x000000000000000f
xlatb ; translate the lowest nibble to ascii using the hex table
mov byte [mboard+ecx],al
dec ecx
mov rax,rdx ; restore the board address in rax
and rax,0x00000000000000f0
shr rax,4 ; move the upper nibble of the first byte to the bottom
xlatb ; translate the second nibble to ascii using the hex table
mov byte [mboard+ecx],al
shr rdx,8 ; update the address to print the next byte
dec ecx
jns memheader
jmp play
invalid:
mov ecx,merr ; Second argument: pointer to message to write
mov edx,merrl ; Third argument: message length
call print_int
; Fallthrough
play:
; Print messages and board
mov ecx,mPXO ; Second argument: pointer to message to write
mov edx,mPXOl+mplay2l ; Third argument: message length
call print_int
call [pfunc]
; Read input
mov ecx,buffer ; Store input at location ‘buffer’
mov edx,2; ; Read these many bytes
call read_int
; Validate
movzx eax, byte [buffer]
sub al,’0′ ; convert the read ascii char to an integer
cmp al,8
ja invalid
; Range is valid
cmpb [board+eax],’ ‘ ; Is empty?
jne invalid
; Move is fully valid
mov bl,[mPXO + spot]
mov [board+eax],bl ; save the current player mark to the selected position in the board
; check the board for a possible winner or tie
movzx ecx, byte [kek+eax] ; get the number of positions to compare into ecx
pair:
movzx esi, byte [lol+eax*8+ecx]
dec ecx
movzx edi, byte [lol+eax*8+ecx]
call check_line
dec ecx
jns pair ; repeat while ecx>=0
decb [top] ; decrement number of free positions
jz tie
xorb [mPXO+spot],magic ; change the player by xoring with ‘X’^’O’ = 0x17
jmp play
Report
The code Jorge_TTT has several clever tricks to avoid using memory and innecessary loops. However, the readability of it is decreased since there are not much procedures and the code relies on updating the messages every time they are used.
The program proceeds as one would expect. The first step is reading the user selection for printing the boards. In my case, the code simply updates a flag that is used afterwards to decide the subroutine to use. In the Jorge_TTT however, a more convenient way to do it is presented. The actual print function to use is saved in a variable, thus we don’t need to do any comparison afterwards, but simply call the saved function pointer. Another trick is the use of templates for printing. In my code, all print_board and debug_board messages are generated by the code printing each part at a time (message, char, other message, etc.). In the Jorge_TTT code templates are used and the code in both print routines only updates certain positions in it and prints the entire template as a single string. Another difference between our codes is the way to display hexadecimal numbers. In my code, a set of subroutines to print a digit in hex, print a byte in hex and print a 32 bit number in hex are used for displaying the debug_board output. In the Jorge_TTT code however, this process is quite simplified. The translation of a 4 bit digit to ascii is simply done using a table, a byte translation is done by looking up the upper and lower nibble in the table. The board address is displayed by going through each byte in the qword and saving it in a template that is reused everytime the debug_board is displayed. In my case the address is translated everytime, which is a time consuming process. Another clever trick is the way in which the board position is displayed when the debug_board is printed one position per line. In my case, the position was first translated to hex and then displayed, and then it was incremented in a loop. In Jorge_TTT code this process is completely simplified by modifying a simple template with the format ‘0x0/’, this can be done because the position is always between 0 and 8 so only one digit must be updated and, since the update implies only an increment, it can be initialized to ‘/’ so the first time it’s incremented, the char changes to ‘0’ since the ascii value for ‘/’ is 47 and for ‘0’ is 48.
Another simplification I found is avoiding the translation of the board characters to hexadecimal. It’s now obvious that the board characters don’t need to be translated to hexadecimal using the ordinary method. Since they are only 3 possible chars (‘X’, ‘O’ and ‘ ‘), we only need to display ‘58’, ‘4f’ or ‘20’, which is the way that Jorge_TTT uses, it only makes a series of comparison and conditional moves to update the template with the hex representation of the board characters.
The game loop is done in the usual way, asking for input, displaying the board, validating, checking for win or tie and then changing the player turn. The input is handled with the sys_readsyscall as expected and the display process was described earlier. Another optimization is found in the win and tie check part. The win check is based on a table and two pieces of code with the labels pair and check_line. The steps are as follows, the current move is first saved on the board using the current player mark. Then, the table is used to get the positions that are affected by the change, that is, which positions could result in a win if the current position is filled. Since the current position is known to have the current player mark we only need to check two positions at a time against the current player, that is what the “pair” code does in a loop, checking every two consecutive positions in the lol table until “kek” positions are checked. The check_line simply compares the two positions with one another and if they have the same char as the current player mark it goes to the win part and ends the game. The tie check is done by a surprisingly simple update of a variable; it simply counts the number of times a char has been saved on the board. The variable is initialized to 9 so if it reaches zero, then the whole board is full and the game results in a tie.
The player turn changed by yet another clever trick which consists in xoring the current player mark with 0x17. The value 0x17 result of xoring the characters ‘X’ and ‘O’. That way, if any of the chars is xored with 0x17, it will result in the other since A^(A^B) = B.