4
\$\begingroup\$

I've been learning MASM64 over the last few days and written a simple demo, so I can get feedback on my understanding of x64 assembly programming.

It's really basic: it asks the user for their name, greets them, and then reverses the name string.

The point of this question is to get a confirmation I understand the basic stuff like calling convention, stack alignment, etc. correctly. It's surely not about performance.

The complete code:

extrn GetStdHandle : proc
extrn WriteConsoleA : proc
extrn ReadConsoleA : proc
.const
 STDIN_HANDLE_ID equ -10
 STDOUT_HANDLE_ID equ -11
 INVALID_HANDLE_VALUE equ -1
 NULL equ 0
 
.data
 promptText db "Enter your name: ", 0
 greetingText db "Hello, ", 0
 backwardsText db "Your name backwards is: ", 0
 
.code
; Counts characters in a null-terminated string.
; Parameters:
; 1. Pointer to string.
; Return value:
; -> string length (without the terminator).
;
stringLength proc
 ; No need for shadow space.
 mov rdx, rcx
 mov rcx, -1
 
 _nextChar:
 inc rcx
 mov al, byte ptr [rdx + rcx]
 test al, al
 jnz _nextChar
 
 mov rax, rcx
 ret
stringLength endp
 
; Displays a null-terminated string.
; Parameters:
; 1. Pointer to string to be displayed.
; 2. Number of characters to be displayed.
; Return value:
; -> On success: 0.
; -> On failure: -1.
;
stringPrint proc
 ; Reserve shadow space.
 sub rsp, 48h
 
 ; Save string address for later use.
 mov [rsp + 38h], rcx 
 
 call stringLength
 
 ; If (length == 0), just end the function.
 test rax, rax
 jz _stringPrintEnd
 
 ; Save string length for later use.
 mov [rsp + 40h], rax
 
 mov rcx, STDOUT_HANDLE_ID
 call GetStdHandle
 
 cmp rax, INVALID_HANDLE_VALUE
 je _stringPrintEnd
 mov qword ptr [rsp + 28h], NULL
 mov r9, NULL
 mov r8d, [rsp + 40h]
 mov rdx, [rsp + 38h]
 mov rcx, rax ; console handle
 call WriteConsoleA
 
 test rax, rax
 jz _stringPrintError
 xor rax, rax
 _stringPrintEnd:
 ; Free shadow space.
 add rsp, 48h
 ret
 _stringPrintError: 
 mov rax, -1
 jmp _stringPrintEnd
 
stringPrint endp
 
; Reads count-1 characters from the standard input.
; Parameters:
; 1. Pointer to buffer to store the characters.
; 2. Character count.
; Return value:
; -> On success: 0.
; -> On error: -1.
;
stringRead proc
 ; Reserve shadow space.
 sub rsp, 48h
 
 ; Save volatile registers for later use.
 mov [rsp + 38h], rcx
 mov [rsp + 40h], rdx
 
 mov rcx, STDIN_HANDLE_ID
 call GetStdHandle
 
 cmp rax, INVALID_HANDLE_VALUE
 je _stringReadError
 
 mov qword ptr [rsp + 20h], NULL
 lea r9, [rsp + 28h]
 mov r8, [rsp + 40h]
 mov rdx, [rsp + 38h]
 mov rcx, rax
 call ReadConsoleA
 
 test rax, rax
 jz _stringReadError
 
 xor rax, rax
 _stringReadError:
 
 ; Free shadow space.
 add rsp, 48h
 ret
stringRead endp
 
; Reverses a null-terminated string.
; Parameters:
; 1. Pointer to string to be reversed.
; 2. Pointer to buffer to store the output.
;
stringReverse proc
 ; Reserve shadow space.
 sub rsp, 38h
 
 ; Save volatile registers.
 mov [rsp + 28h], rcx ; src buffer
 mov [rsp + 30h], rdx ; dst buffer
 
 ; RCX is already set.
 call stringLength
 
 test rax, rax
 jz _stringReverseEnd
 
 mov rcx, [rsp + 28h] 
 dec rcx
 
 mov rdx, [rsp + 30h]
 add rdx, rax
 
 ; Terminate the dst buffer.
 mov byte ptr [rdx], 0 
 
 _reverseNextChar:
 inc rcx
 dec rdx
 mov al, byte ptr [rcx]
 
 ; Check for terminator in src buffer.
 test al, al 
 jz _stringReverseEnd
 
 mov byte ptr [rdx], al 
 jmp _reverseNextChar
 
 _stringReverseEnd:
 xor rax, rax
 ; Free shadow space.
 add rsp, 38h
 ret
stringReverse endp
 
main proc
 ; Reserve shadow space: 20h for calls + 28h for buffers.
 sub rsp, 48h 
 
 ; Zero the buffers.
 mov qword ptr [rsp + 20h], 0
 mov qword ptr [rsp + 28h], 0
 mov qword ptr [rsp + 30h], 0
 mov qword ptr [rsp + 38h], 0
 mov qword ptr [rsp + 40h], 0
 
 lea rcx, promptText 
 call stringPrint
 
 mov rdx, 14h
 lea rcx, [rsp + 20h] ; reading buffer
 call stringRead
 
 lea rdx, [rsp + 34h] ; destination buffer
 lea rcx, [rsp + 20h] ; source buffer
 call stringReverse
 
 lea rcx, greetingText
 call stringPrint
 
 lea rcx, [rsp + 20h]
 call stringPrint
 
 lea rcx, backwardsText
 call stringPrint
 
 lea rcx, [rsp + 34h]
 call stringPrint
 
 xor rax, rax
 ; Free shadow space.
 add rsp, 48h
 ret
main endp
end

Thanks for the help.

asked Oct 18, 2020 at 12:28
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The stringLength leaf function (no prolog/epilog) could still be a bit simpler:

stringLength proc
 mov rax, -1
 _nextChar:
 inc rax
 cmp byte ptr [rcx + rax], 0
 jne _nextChar
 ret
stringLength endp

The comments on the stringPrint frame function suggest that a second parameter exists.

; Displays a null-terminated string.
; Parameters:
; 1. Pointer to string to be displayed.
; 2. Number of characters to be displayed.

This is not the case.

When the call to GetStdHandle fails, you jump to _stringPrintEnd where I think you could jump to _stringPrintError.

The 5th parameter for WriteConsoleA, the one that goes onto the stack, must go to [rsp + 20h] right above the register home area. You wrote [rsp + 28h].

A simpler exit is:

 test rax, rax
 mov rax, -1
 jz _stringPrintEnd
 xor rax, rax
_stringPrintEnd:
 add rsp, 48h
 ret

The exit from the stringRead frame function is not very useful. It will always return RAX=0. In other words: the test is redundant.

 test rax, rax
 jz _stringReadError
 xor rax, rax
_stringReadError:

In the stringReverse proc, it is easy to write the loop using a single conditional jump, omitting the second jump. I know that you're not seeking performance but then again this is assembly...

 mov al, 0 ; To terminate the dst buffer.
_reverseNextChar:
 inc rcx
 mov [rdx], al
 dec rdx
 mov al, [rcx]
 test al, al 
 jnz _reverseNextChar

Some ideas

  • Instead of determining the string length yourself, why don't you use the lpNumberOfCharsRead that you receive from invoking ReadConsoleA?

  • Why do you bother to zero your source and destination buffers. That's not a useful operation (unless you doubt that the input from ReadConsoleA will be zero-terminated by default).
    And if you insist on clearing these buffers then please try not to use that many bytes. Next is much smaller:

     xor eax, eax
     mov [rsp + 20h], rax
     mov [rsp + 28h], rax
     mov [rsp + 30h], rax
     mov [rsp + 38h], rax
     mov [rsp + 40h], rax
    
  • It will be much nicer if you introduced a couple of newlines in the output:

     greetingText db 13, 10, "Hello, ", 0
     backwardsText db 13, 10, "Your name backwards is: ", 0
    

The point of this question is to get a confirmation I understand the basic stuff like calling convention, stack alignment, etc. correctly.

Except for that 5th parameter on WriteConsoleA, this seems to be fine. Well done.

For maximum adherence to the conventions you could store the register parameters RCX and RDX in the shadow memory that the caller had to set aside for that purpose.
Below is how it changes stringRead (similar for stringPrint and stringReverse):

stringRead proc
 ; Save volatile registers in register home area for later use.
 mov [rsp + 8], rcx
 mov [rsp + 16], rdx
 ; Reserve shadow space, local storage and alignment
 sub rsp, 38h
 
 mov rcx, STDIN_HANDLE_ID
 call GetStdHandle
 
 ...
 
 mov qword ptr [rsp + 20h], NULL
 lea r9, [rsp + 28h]
 mov r8, [rsp + 38h + 16]
 mov rdx, [rsp + 38h + 8]
 mov rcx, rax
 call ReadConsoleA
 
 ...
 add rsp, 38h
 ret
stringRead endp
```
answered Oct 18, 2020 at 21:12
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for taking the time to read it and for your commentary. I can't believe I didn't spot at least some of those errors, haha. Also, the suggestion to use the caller-allocated shadow space was an eye-opener, made me realize I didn't fully understand the convention. Once again - thanks! \$\endgroup\$ Commented Oct 20, 2020 at 9:16
  • \$\begingroup\$ Since this is x86-64, the standard way to store a bunch of zeros is with SSE or SSE2. xorps xmm0,xmm0 / movaps [rsp + 20h], xmm0 etc. (With the last store being movlps [rsp+40h], xmm0 if you need to store an odd multiple of 8.) \$\endgroup\$ Commented Nov 19, 2020 at 20:00

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.