Search code examples
assemblyx86printfmasmmasm32

Outputing numbers in several data types to MessageBox creates a mess


I am trying to output decimals (int and floats) in almost all datatypes from byte to real10 line by line in MB, but for some reason almost none of them are displayed correctly (image below), most of them are broken, can someone explain why is that happening and what is my mistake?

.386
.model flat, stdcall
option casemap: none

include \masm32\include\masm32rt.inc

.data
titletext db  'Title',0
frmt db  'A (Byte) = %d',10
     db  '-A (Byte) = %d',10
     db  'A (Word) = %d',10
     db  'B (Word) = %d',10
     db  '-A (Word) = %d',10
     db  '-B (Word) = %d',10
     db  'A (ShortInt) = %d',10
     db  'B (ShortInt) = %d',10
     db  'C (ShortInt) = %d',10
     db  '-A (ShortInt) = %d',10
     db  '-B (ShortInt) = %d',10
     db  '-C (ShortInt) = %d',10
     db  'A (LongInt) = %d',10
     db  'B (LongInt) = %d',10
     db  'C (LongInt) = %d',10
     db  '-A (LongInt) = %d',10
     db  '-B (LongInt) = %d',10
     db  '-C (LongInt) = %d',10
     db  'D (Single) = %g',10
     db  '-D (Single) = %g',10
     db  'E (Double) = %g',10
     db  '-E (Double) = %g',10
     db  'F (Extended) = %g',10
     db  '-F (Extended) = %g',0

buff db 1024 dup (?)

Abyte db 6
nAbyte db -6
Aword dw 6
Bword dw 603
nAword dw -6
nBword dw -603
Ashort dd 6
Bshort dd 603
Cshort dd 6032000
nAshort dd -6
nBshort dd -603
nCshort dd -6032000
Along dq 6
Blong dq 603
Clong dq 6032000
nAlong dq -6
nBlong dq -603
nClong dq -6032000
Dsingle real4 0.001
nDsingle real4 -0.001
Edouble real8 0.074
nEdouble real8 -0.074
Fextended real10 735.430
nFextended real10 -735.430

.code
start:
   invoke  crt_sprintf, addr buff, addr frmt,
      Abyte, nAbyte,
      Aword, Bword, nAword, nBword,
      Ashort, Bshort, Cshort, nAshort,nBshort, nCshort,
      Along, Blong, Clong, nAlong, nBlong, nClong,
      Dsingle, nDsingle,
      Edouble, nEdouble,
      Fextended, nFextended
   invoke  MessageBox, 0, addr buff, addr titletext, MB_OK
   invoke  ExitProcess, 0
end start

current result:

current result


Solution

  • Your format string doesn't match what you're getting invoke to pass.

    You're using a %d conversion for narrow args that you aren't promoting to int. %hhd and %hd respectively are the conversions you want for int8_t (signed char as an integer) and int16_t (short). ISO C doesn't specify that short = int16_t, but that is the case on 32-bit Windows.

    Anyway, the high bytes of those 4-byte stack slots for passing those narrow integers might contain high garbage if MASM's invoke doesn't magically sign- or zero-extend them for you.

    Also, short is normally a 16-bit byte but you're using dd. That shouldn't matter in this case since 32-bit code always uses 32-bit stack slots at minimum for arg-passing.

    To further debug this yourself: Look at how that invoke directive or macro actually assembles to real machine instructions. i.e. disassemble the resulting code, to see what it's really doing. (Or use a debugger with disassembly view to single-step). Also use a debugger to look at stack memory after each step, before the call, to see if your args are being passed correctly.

    If you aren't sure what the right sequence of asm instructions you need is, look at compiler output from a C or C++ compiler for equivalent code that calls sprintf with C global variables of the same types you want. (e.g. on https://godbolt.org/ which has 32-bit x86 MSVC available. Its calling convention and type widths should be the ones crt_sprintf is expecting, that you're linking via MASM32.)


    Also, the floats are all broken because you forgot to apply the C default promotions of float to double that always happens for args to the ... part of a variadic function. How to print a single-precision float with printf So your 4-byte floats skew everything later.

    And similarly, you have some dq 64-bit integers but you're still telling sprintf they're %d (int). So you actually get the high and low halves of a dq being read by two separate %d conversions, leading to the wrong conversion reading the wrong bytes.


    I've changed all byte, word and dword to signed types and it worked well, but I am having trouble with qword and real4. I changed %d for %ld for qword, but it still puts 0 to the next entry, shifting all numbers after it.

    In 32-bit ABIs, long is still a 32-bit type. You need long long and the conversion for that is %lld for dq.

    And about floats, as I understood from the link to the other thread you posted - it is impossible to print out a float, I need to convert it to different type somehow?

    That's correct. You simply can't use invoke with a real4 for a printf-like functions that take their arg via a ... part of a C variadic function.

    Look at compiler-generated code for the equivalent C.

    You'll have to convert your real4 to double and push them manually, before invoke. (Invoke pushes in right-to-left order, last args pushed first, so you can do those pushes and then use invoke I guess?)

    Also your libc probably uses long double = double (see comments on that answer), not the 10-byte x87 type. So there's probably no way to get that sprintf to handle a tbyte and you'll have to convert it to double as well. Or just don't bother using real10 unless you find a C library that can handle it.

    (Oh, it was you that asked that question. It's still the same problem.)