1

I am trying to convert an integer to a string and then displaying it using the MessageBoxA function. It is successful if the number is an xero, but any other positive integer is displayed as a blank pop-up message.

default rel
global WinMain
extern ExitProcess
extern MessageBoxA
section .data
title:  db 'Melang64', 0
section .bss
buf:    resb 80
section .text
WinMain:
    sub rsp, 28h
    mov rax, buf
    mov rdi, 1
    call uitoa 
    mov rcx, 0
    lea rdx,[buf]
    lea r8,[title]
    mov r9d, 0
    call MessageBoxA
    mov  ecx,eax
    call ExitProcess
    add rsp, 28h
    hlt

uitoa:
    mov  rsi, rax
    mov  rax, rdi
    cmp  rax,0
    jnz  uitoa_convert_nrm
    mov  byte [rsi], 48
    inc esi
    mov  byte [rsi], 0
    jmp uitoa_end

uitoa_convert_nrm:
    mov r10, 10

    xor rcx, rcx
uitoa_loop:
    xor  rdx, rdx
    div  r10
    inc  ecx
    cmp  rax, 0
    jnz  uitoa_loop

    inc  ecx
    add  rsi, rcx

    mov  byte [rsi], 0
    mov  rax, rdi
    dec  ecx

uitoa_convert:
    xor rdx, rdx
    dec  rsi
    div  r10
    add  rdx, 48
    mov  byte [rsi], dl
    loopnz uitoa_convert

uitoa_end:
    ret

I am using the following program to run this code, it is a c++ program that just sends commands to the command prompt to create the obj file, link it and then run the exe:

#include <cstdlib>
#include <windows.h>
#include <iostream>
#include <string>

int main() {

    std::cout << "Hello\n";
    system("nasm -f win64 Hi.asm");

    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;

    const wchar_t* command = L"\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.38.33130\\bin\\Hostx64\\x64\\link.exe\" Hi.obj /subsystem:windows /entry:WinMain /libpath:\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.38.33130\\lib\\x64\" /nodefaultlib msvcrt.lib /libpath:\"C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.22621.0\\um\\x64\" /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no";

    std::wstring writableCommand(command);

    if (CreateProcess(NULL, &writableCommand[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        WaitForSingleObject(pi.hProcess, INFINITE);

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else {
        std::wcerr << L"Error: " << GetLastError() << std::endl;
    }
    system("Hi.exe");
    return 0;
}

The c++ program is not the problem, however the asm part of converting the integer to the string is, I believe.

I am using nasm and I am using a windows machine. I am writing for 64 bit.

My assembly knowledge is not the best, but I tried to make sure that all the registers are correct, as well as the function calls. The program runs perfectly, it just doesn't display the number it should be, and is displaying a blank message, or buffer, in context to the code.

1

1 Answer 1

1

Your methode with 2 series of divisions is inefficient, but it is correct except that you put the terminating zero one byte too far. As a consequence Windows sees an empty string at the buffer that you provide.

What you currently obtain from converting the number 1 is:

0, '1', 0
^
buf
uitoa:
    mov  rsi, rax
    mov  rax, rdi
    cmp  rax,0
    jnz  uitoa_convert_nrm
    mov  byte [rsi], 48
    inc esi
    mov  byte [rsi], 0
    jmp uitoa_end

uitoa_convert_nrm:
    mov r10, 10

    xor rcx, rcx
uitoa_loop:
    xor  rdx, rdx
    div  r10
    inc  ecx
    cmp  rax, 0
    jnz  uitoa_loop

    inc  ecx                   <<<<<<< Remove this line!
    add  rsi, rcx

    mov  byte [rsi], 0
    mov  rax, rdi
    dec  ecx                   <<<<<<< Remove this line too!

uitoa_convert:                 RCX is at least 1 at this point
    xor rdx, rdx
    dec  rsi
    div  r10
    add  rdx, 48
    mov  byte [rsi], dl
    loopnz uitoa_convert       <<<<<<< Just use LOOP (still inefficient)

uitoa_end:
    ret

An alternative conversion routine for signed 64-bit integers follows. If you want it unsigned then simply remove the five lines that I've marked with ||.

; IN (rcx,rdx) OUT (rax) MOD (rcx,r8,r9)
; RCX is the signed integer
; RDX is the buffer address
IntToStr:
  push  rdx              ; (1)
  mov   r9, rdx
  sub   rsp, 24          ; At most 19 digits
  mov   rax, rsp
  xchg  rax, rcx         ; -> RAX is 64-bit integer, RCX is temp buffer (stack)
  mov   r8d, 10          ; CONST R8 = 10

  test  rax, rax              ||
  jns   .more                 ||
  mov   byte [rdx], '-'       ||
  inc   r9                    ||
  neg   rax                   ||

.more:
  xor   edx, edx
  div   r8               ; RDX:RAX / R8
  add   edx, '0'
  mov   [rcx], dl
  inc   rcx
  test  rax, rax
  jnz   .more

.copy:
  dec   rcx
  movzx eax, byte [rcx]
  mov   [r9], al
  inc   r9
  cmp   rcx, rsp
  ja    .copy
  mov   byte [r9], 0     ; Zero-termination
  add   rsp, 24

  mov   rax, r9
  pop   rdx              ; (1)
  sub   rax, rdx         ; -> RAX is number of characters, but
  ret                    ;    not including the terminating zero
                         ;    Can be omitted
Sign up to request clarification or add additional context in comments.

1 Comment

As shown in How do I print an integer in Assembly Level Programming without printf from the c library? (itoa, integer to decimal ASCII string) , if you just want to pass a string to a system call, start from the end of a buffer. When you're done converting, you have a pointer to the start of the string somewhere in that buffer; pass that to the system call. (After uitoa_end returns that pointer as its return value. The caller can subtract pointers to get the length if it wants, or just use the pointer.)

Your Answer

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.