News:

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

Making 32/64 bit applications

Started by donkey, March 15, 2011, 04:00:14 PM

Previous topic - Next topic

donkey

Recently in another thread I posted an example of exploiting the DbgEng dll, I have updated it to be built as either a 32 bit or 64 bit application without any modification to the source. I am posting the project here so anyone who is interested in making flexible programs could take a look. I've included both a 32 and 64 bit batch file for building the project as well as a RadAsm 3 prra project file. This is my first foray into making a complex program source level switchable and it pointed out quite a few pitfalls, not the least of which was the problem with CoInvoke in X64 mode (its been corrected in the latest headers). Since the headers will select all equates and structures based on the WIN64 flag and also use the correct calling convention for CoInvoke much of the work is done behind the scenes. GoAsm in x86 compatibility mode will change Rxx registers to Exx and using the S type indicator allows the types to be switchable. If done with a bit of care you can have a program that can easily be built for both 32 and 64 bit.


If you have any questions post them here.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

For those 64 bit GoAsm developers who use RadAsm 3.x as their primary IDE I have posted a set of runtime debug macros in the RadAsm sub-forum. They allow you to get runtime data from your project as well as a few other useful functions such as code disassembly and symbol table dumps, single step variable spy, MMX and FPU contents. See the readme file for syntax. The library is loosely based on vKim's excellent macros for MASM and is pretty much all I use when debugging 64 bit apps because of the lack of 64 bit compatible tools (PEBrowse and WinDbg being the only ones I have). This is a work in progress since I add functions as I need them or someone asks for them so if you find it useful you might want to check in every once in a while to see if its been updated.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

Today I was adapting a library to be both 32 and 64 bit compatible at source level. I noticed a couple of more things that have to be worked out before you can develop true cross platform code. There are many API's from MSVCRT.DLL that require 64 bit parameters, even in 32 bit mode, this applies as well to DBGHELP.DLL and many others. GoAsm does not allow you to specify the size of a parameter when using the INVOKE statement, for example you can't do this:

invoke _i64toa,Q[tempq],rax,16

Where Q[tempq] would resolve to

,[tempq],[tempq+4]

in 32 bit mode. To handle this problem you will have to use conditional compilation. The X64 switch is defined automatically when you build for 64 bit so that can be used to select the correct coding:

#IF X64
invoke _i64toa,[tempq],rax,16
#ELSE
invoke _i64toa,[tempq],[tempq+4],eax,16
#ENDIF


Going back to modify code later is a pain, I know, I'm in the middle of it. It's much better to plan for these APIs in advance and deal with them at the start.

Next, the CDECL (C calling convention) functions like wsprintf will definitely need to be taken care of. Luckily there is a simple solution to this one buried in macros.h. Its the CInvoke macro. When using CInvoke in 32 bit mode the parameters are pushed onto the stack, the function is called then the stack is corrected on return. In 64 bit mode the function and parameters are simply passed to a normal invoke statement and GoAsm takes care of the FASTCALL convention for you. This eliminates the need to use ADD ESP,XXX after a C call in 32 bit mode and edit it out or use conditional compilation to skip it when building for 64 bit. For example:

CInvoke(wsprintf,RBX,"0x%0.16X: RAX = 0x",RAX)

More to come as I move through the conversion process and make my main applications both 32 and 64 bit compatible.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

jj2007

Edgar,

Compliments. You are building important bridges here. Maybe I'll never profit from this work myself (32-bit will be around for a while, and I am not the youngest here...), but you put a lot of effort in a project that has a future :U

donkey

Converting DWORDs to QWORDs and visa versa. A small problem that I had involved passing a DWORD value to a procedure. X64 and FASTCALL will only allow you to pass QWORD size parameters so you could end up with the following situation:

MyDword DD 0x1234
MyQword DQ 0x5678

invoke SomeProc, [MyDword]


Now the problem here is that the MyDword will be passed with your DWORD in the low order 32 bits of the QWord, and the low order 32 bits of MyQword in the high order 32 bits. There are a couple of commands you can use to correct this in your own local procedures:

CDQE (Convert Dword in EAX to Qword with sign extension result in RAX)
MOVSXD (Mov Dword to Qword with sign extension)

And oddly enough for zero extension:

mov Reg32, Reg32 (Since mov into a 32 bit register automatically zero the high order 32 bits this acts a MOVZXD)

And even more odd trying to mask the low order 32 bits is not allowed, the 0xFFFFFFFF will be extended to 0xFFFFFFFFFFFFFFFF and you will end up with exactly the same number in the register. This caused me a few headaches until I finally decided to fire up WinDbg and find out what was happening:

AND RAX,0xFFFFFFFF

assembles as
4883e0ff and rax,0FFFFFFFFFFFFFFFFh


Now you might be thinking that external (API) procedures will present a problem because you can't correct the D/Qword issue since you have no access to the external procedure. Well, I fretted over that as well and have found that all API's I've tried so far that require a DWORD parameter ignore the high order 32 bits of the QWORD passed so it's not something you need to adjust for.

More to come...
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

dedndave

AND RAX,0xFFFFFFFF

assembles as
4883e0ff and rax,0FFFFFFFFFFFFFFFFh


i would have to call that an assembler bug
it is assuming that 0xFFFFFFFF is -1, which is true in 32-bit code
is there a directive to tell the assembler that you want 64-bit interpretations ?
or - perhaps there is some syntax for specifying immediate operand size
AND RAX,0zFFFFFFFF
or
AND RAX,0FFFFFFFFz

:P

donkey

I'm withholding judgment as to whether it is an assembler bug or not, since the maximum mask size for the AND instruction is 32 bit it may be that the mask is sign extended to 64 bit. I'll have to check the instruction set reference.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

dedndave

you could always try...
QuoteAND RAX,0000000000000000000000000000000011111111111111111111111111111111b
lol
i dunno - maybe the 64-bit AND instruction does not allow anything larger than 32 bits in immediate operands

on a side note - it is odd to see that it allows that shortened form for RAX
for older processors, AX was excluded from these shortened signed byte/word instruction forms

donkey

Hi Dave,

The maximum operand size for AND is 32 bit. The AMD manual has this to say about 32 bit immediate operands:

Quote from: AMD instruction set reference Vol 3 Page 375Immediates and branch displacements are sign-extended to 64 bits.

So the behaviour is as expected. GoAsm optimizes the call to a BYTE parameter because the result would be the same but the encoding is smaller by 2 bytes, otherwise it would be:

4825ffffffff    and     rax,0FFFFFFFFFFFFFFFFh
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

donkey

Quote from: dedndave on March 27, 2011, 04:07:05 AM
on a side note - it is odd to see that it allows that shortened form for RAX
for older processors, AX was excluded from these shortened signed byte/word instruction forms

64 bit actually allows you more leeway with registers, for example in 32 bit the ebp, esp, edi, and esi registers are only addressable using EBP/BP, ESP/SP, ESI/SI and EDI/DI, there is no BYTE addressing mode. However in 64 bit you can use BPL, SPL, DIL and SIL which will directly address the low order byte of those registers. The same with the new registers, for example R8 can be addressed as R8, R8D, R8W or R8L. The AH, BH, CH and DH registers are not available with the REX prefix so the weird thing is that though you can use MOV AH,DL you cannot use MOV AH,SIL since the SIL register needs the REX prefix.
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable