News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

INVOKE with 16-bit parameters in 32-bit programs

Started by Broken Sword, September 28, 2005, 02:02:58 PM

Previous topic - Next topic

Broken Sword

Hi guys. Here is my problem. I need to pass 16-bit parameters to function, e.g. wsprintf, via INVOKE.

invoke   wsprintf,                    \
   addr log_buf,              \
   addr template,            \
   sys_time.wHour,         \
   sys_time.wMinute,      \
   sys_time.wSecond,     \
   log_counter

where sys_time is SYSTEMTIME structure (members wHour, wMinute, wSecond are words actually).

Problem is that 16 bit parameters are translated to

6A 00                 push 0 ; string has been modified after MichaelW post, thx
66 XX XX XX XX    push word ptr [addr]

constructions.

After this each parameter uses 1.5 dword of stack space (dword 0 + word [addr]).
Prefix 66h is missed for push 0. Why?

MichaelW

The opcode 6Ah is actually PUSH imm8 (Push immediate byte), for which the processor subtracts 2 from ESP, the same as it does for a word operand. For PUSH 0 the byte operand would be 00h, not 60h. Effectively the code pushes a zero word before it pushes the 16-bit parameter (as a word), so the called function receives a DWORD with the 16-bit parameter in the low-order word and zero in the high-order word.

eschew obfuscation

Broken Sword

MichaelW, I'm sorry, but it's not a true - the amount the stack pointer is decremented (2 bytes or 4 bytes) depends only on operand-size attribute of current code segment (it's 32-bit in windows) that's why for all forms of push (push imm8, push imm16, push imm32) esp will decrement by 4. If you don't trust me - look in debugger :).

Hutch, I think it's a bug in INVOKE implementation... or my debugger gone crazy...

Petroizki

Yes it is a bug in MASM. I noticed this same behaviour when i was making my own invoke macros.

To push a word value in my macros i use:
movzx eax, word ptr [...]
push eax

hutch--

Robert,

Try something as simple as WORD PTR before the 16 bit value and see if the encoding is correct. It is probably something like a C calling convention with the invoke syntax has no way of knowing the sizes so it assumes DWORD. I would also be inclined to test it with manual push / call syntax to see what the encoding is as well. Just make sure you do the stack corection after the C call has returned if you code it manualy. ADD ESP, BYTECOUNT_OF_ARGUMENTS
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Ratch

Robert,
     Assuming you are in 32-bit mode, it's best to just PUSH DWORDS onto the stack, so you avoid pesky prefixes that take extra memory and CPU time.  If you have to PUSH AX, just PUSH EAX and POP EAX later.  The value in AX will be the same as before.  Notice the explicit PUSHW and PUSHD instructions that can override the operand designations.  Ratch


00000000 0002         TAG WORD 2
00000002 00000003     BAG DWORD 3

00000000                 .CODE
00000000                  MAIN:
00000000  66| 50            PUSH AX
00000002  50                PUSH EAX
00000003  66| FF 35         PUSH [TAG]
      00000000 R
0000000A  FF 35 00000000 R  PUSHD [TAG]
00000010  FF 35 00000002 R  PUSH [BAG]
00000016  66| 6A 03         PUSHW 3

Gustav


this works with MASM invoke:



invoke   wsprintf,                    \
   addr log_buf,              \
   addr template,            \
   sword ptr sys_time.wHour,         \
   sword ptr sys_time.wMinute,      \
   sword ptr sys_time.wSecond,     \
   log_counter



but cannot be done generally because the 16bit value will be sign-extended.


Broken Sword

hutch,

WORD PTR produces the same encoding sequence :(


Gustav,

ya, it works, but you right - it uses movsx EAX,...

Is there something like "ZWORD PTR"  :8) like in Petroizki post?

Ratch

Robert,
     You are right in your observation that INVOKE uses 1.5 DWORDs to PUSH a word parameter onto the stack.  This is not only clunky and wasteful, but it skews the stack so that wsprintf does not output the correct strings.  Nevertheless, you must make a DWORD out of a WORD parameter.  wsprintf expects sequential contiguous DWORD parameters in order to work correctly.  INVOKE just does not hack it for this application.  One correct way is to use the MOVZX (not MOVSX) and then PUSH the register.  Another way is shown in the attached code.  The parameters are PUSHed directly, but a zero WORD must be PUSHed first because of the bass ackward little endian format of INTEL.  MASM knows they are word parameters because they are referenced by a word element STRUC.    PUSH AX  is one byte shorter than PUSHW 0 as you can observe from the code.  As you can see from executing the program, it gives correct results.  Ratch

[attachment deleted by admin]

Broken Sword

Ratch, really thanks, but I know hundred ways how to solve this problem without INVOKE.
There is problem in 'INVOKE' macros, i don't think it "wastes" stack space or it's cluncky or smth else - it's just a bug.
There must be "66 6A 00" sequence instead of "6A 00". That's it.

P.S. Generally I think that "66 6A 00" nither other tricks leads to the solvation. Really, there is no functions with 16-bit parameters in 32-bit environment. That's why IMO the only one correct way is displaying error message and stop asemblyling on such INVOKE-s.

hutch--

Robert,

It sounds like manually coding the call is the reliable way to do it but I wonder if it can be prototyped in another way. It depends on if you are using a variable number of arguments or not and this is probably what the problem is with invoke. It would seem that when it does not have a prototype that specifies each argument size, it assumes DWORD as the native addressing mode size.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

Quote from: Robert on September 28, 2005, 05:42:36 PM
MichaelW, I'm sorry, but it's not a true - the amount the stack pointer is decremented (2 bytes or 4 bytes) depends only on operand-size attribute of current code segment (it's 32-bit in windows) that's why for all forms of push (push imm8, push imm16, push imm32) esp will decrement by 4. If you don't trust me - look in debugger :).

Yes, on my Windows 2000 system the PUSH instruction is definitely adjusting ESP by 4, regardless of the instruction form I use. So I'm basically still stuck in the DOS era, and for a Windows app my explanation is not correct.

But I have doubts about the "depends only on" part. Per the Intel documentation the operand-size prefix can override the default operand size.

From a recent Intel manual:
Quote
The D flag in the current code segment's segment descriptor (with prefixes), determines
the operand-size attribute and the address-size attribute of the source operand. Pushing a
16-bit operand when the stack address-size attribute is 32 can result in a misaligned stack pointer
(a stack pointer that is not be aligned on a doubleword boundary).
Quote
The operand-size override prefix allows a program to switch between 16- and 32-bit operand
sizes. Either size can be the default; use of the prefix selects the non-default size.

From an older manual (the 486 Programmer's Reference Manual):
Quote
The internal coding of an instruction can include two byte-long prefixes: the address-size prefix, 67H, and the operand-size prefix, 66H.
...
These prefixes override the default segment attributes for the instruction that follow.
And for the PUSH instruction:
Quote
The PUSH instruction decrements the stack pointer by 2 if the operand-size attribute of the instruction is 16 bits...

In a 16-bit DOS app the operand-size prefix works just as described. If the operand is an imm8 or r/m16, without the prefix PUSH decrements SP by 2, and with the prefix PUSH decrements SP by 4. I have not been able to find any explanation of why the prefix has no effect in a Windows app.

BTW, I did some experiments with invoke in a USE32 segment in a DOS app. Here is the generated code from the listing:

    invoke test32, _dword, _word, _byte
00000000  67& A0 00C3 R   *     mov    al, _byte
00000004  50    *     push   eax
00000005  83 EC 02    *     sub    esp, 002h
00000008  67& 66| FF 36
   00C1 R    *     push   _word
0000000E  67& FF 36 00BD R *     push   _dword
00000013  0E E8 00000004  *     call   test32
00000019  83 C4 0C    *     add    esp, 00000000Ch


eschew obfuscation

MazeGen

Quote from: MichaelW on September 30, 2005, 06:10:01 AM
Quote from: Robert on September 28, 2005, 05:42:36 PM
MichaelW, I'm sorry, but it's not a true - the amount the stack pointer is decremented (2 bytes or 4 bytes) depends only on operand-size attribute of current code segment (it's 32-bit in windows) that's why for all forms of push (push imm8, push imm16, push imm32) esp will decrement by 4. If you don't trust me - look in debugger :).

Yes, on my Windows 2000 system the PUSH instruction is definitely adjusting ESP by 4, regardless of the instruction form I use. So I'm basically still stuck in the DOS era, and for a Windows app my explanation is not correct.

...

I have not been able to find any explanation of why the prefix has no effect in a Windows app.

The prefix has to have the expected effect in a Windows app, as it has the effect in a DOS app. Try it again ;)

MichaelW

Yes, I don't know what I was looking at, and now I can't find the code I tested with. Hutch has 2 years on me, but I'll borrow his "senile decay" excuse anyway (what do I have to lose, the young ladies have been addressing me as "sir" for a long time :bg).


; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    .data
        buffer  dd 5 dup(0)
        _dword  dd 12345678h
        _word1  dw 1234h
        _word2  dw 5678h
    .code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    mov   ebp, esp
    mov   ebx, OFFSET buffer

    mov   [ebx], esp

    push  0
    mov   [ebx+4], esp

    db    66h
    push  0
    mov   [ebx+8], esp

    push  ax
    mov   [ebx+12], esp

    push  _word1
    mov   [ebx+16], esp

    mov   esp, ebp

    print "initial ESP                        "
    print ustr$([ebx]),13,10
    print "after PUSH 0 (6A00)                "
    print ustr$([ebx+4]),13,10
    print "after db 66h PUSH 0 (666A00)       "
    print ustr$([ebx+8]),13,10
    print "after PUSH ax (6650)               "
    print ustr$([ebx+12]),13,10
    print "after PUSH _word1 (66FF3518304000) "
    print ustr$([ebx+16]),13,10,13,10

    ; Simulate invoke with the missing prefixes.
    nop
    nop
    nop
    db    66h
    push  0
    push  _word2
    db    66h
    push  0
    push  _word1
    push  _dword
    call  _test

    mov   eax, input(13,10,"Press enter to exit...")
    exit
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
_test proc _dwordarg:DWORD, _wordarg1:WORD, _wordarg2:WORD
    print uhex$(_dwordarg),13,10
    movzx eax, _wordarg1          ; avoid problem with invoke
    print uhex$(eax),13,10
    movzx eax, _wordarg2          ; avoid problem with invoke
    print uhex$(eax),13,10
    ret
_test endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start


initial ESP                        1245124
after PUSH 0 (6A00)                1245120
after db 66h PUSH 0 (666A00)       1245118
after PUSH ax (6650)               1245116
after PUSH _word1 (66FF3518304000) 1245114

12345678
00001234
00005678


eschew obfuscation

Broken Sword

hutch, here is a prototype of wsprintf:

wsprintfA PROTO C :DWORD,:VARARG

I think that produceing 1,5 dword on stack isn't a best way for VARARG.

Even if we call:

invoke       wsprintf, \
                sys_time.wHour, \
                sys_time.wMinute, \
                sys_time.wSecond


first parameter (which is declared as DWORD) will copile to

6A 00
66 XX XX XX


form too.

I can conclude, that each arg, prototyped as vararg or DWORD by default is translated as DWORD, but because of bug in INVOKE it translates in 1,5 dword.

MichaelW,
Ya, shure, operand-size prefix can override the default operand size, I've noticed it in my first post ("Prefix 66h is missed for push 0. Why?"). "depends only on" phrase was my little mistake :)