Author Topic: Win64 If invoke argument count is greater than four (RAX Glitch)  (Read 5538 times)

Offline encryptor256

  • Full Member
  • **
  • Posts: 250
  • Country: lv
  • Win64 .
    • On Youtube: encryptor256
Hello!

Win64 If invoke argument count is greater than four (RAX Glitch)

I think, i, somewhere see/saw/seen, that somebody said/written,
that, it is hard to catch RAX usage within invoke macro arguments.
Yes, this results in error, in NASMX invoke macro, RIGHT NOW.

Source code:
Code: [Select]
bits 64

%include "nasmx.inc"

IMPORT wsprintfA

NASMX_PRAGMA FASTCALL_STACK_PRELOAD, DISABLE

proc poo
locals none

invoke wsprintfA,0,0,0,0,[rax+0xAAAA],[rax+0xBBBB]

endproc

Compiled with: "nasm.exe -f win64 -o test.obj test.asm".
NDisasm with: "ndisasm.exe -b 64 test.obj", results in:
Code: [Select]
0000003C  55                push rbp
0000003D  4889E5            mov rbp,rsp
00000040  4883EC30          sub rsp,byte +0x30
00000044  4831C9            xor rcx,rcx
00000047  4831D2            xor rdx,rdx
0000004A  4D31C0            xor r8,r8
0000004D  4D31C9            xor r9,r9
00000050  488B80AAAA0000    mov rax,[rax+0xaaaa]
00000057  4889442420        mov [rsp+0x20],rax
0000005C  488B80BBBB0000    mov rax,[rax+0xbbbb]
00000063  4889442428        mov [rsp+0x28],rax
00000068  E800000000        call qword 0x6d
0000006D  4883C430          add rsp,byte +0x30
00000071  4889EC            mov rsp,rbp
00000074  5D                pop rbp
00000075  C3                ret

These lines results in runtime error:
Code: [Select]
...
00000050  488B80AAAA0000    mov rax,[rax+0xaaaa]
00000057  4889442420        mov [rsp+0x20],rax
0000005C  488B80BBBB0000    mov rax,[rax+0xbbbb]
00000063  4889442428        mov [rsp+0x28],rax
...

So, no need to cry now  :'( , i have a solution.   8)

How to fix, just a skecth of invoke macro:
  • Determine if one of the arguments use RAX
  • If use RAX, save RAX into some stack place
  • Now loop through each argument - prepare it for function call
  • If current argument consists of RAX, then restore it from the place, where it was saved before
  • And that's it, continue

How to determine if argument contains RAX usage:
So, here is a macro which determines, if an argument uses/contains RAX.

Code: [Select]
%macro isRAXUsed0 1

%define ___isRAXUsed 0

%defstr txt %[%1]
%strlen txtlen txt
%if txtlen==3
%ifidni txt,"rax"
%define ___isRAXUsed 1
%endif
%elif txtlen>3
%substr strfirst txt 1,1
%ifidni strfirst,'['
%assign repcounter 2
%rep (txtlen-4)
%substr strP txt repcounter-1,1
%substr strRAX txt repcounter,3
%substr strN txt repcounter+3,1
%assign repcounter repcounter+1
;%warning strP strRAX strN
%ifidni strRAX,"rax"
%assign iP strP
%assign iN strN
%if iP<'0'||iP>'9'&&iP<'A'||iP>'Z'&&iP<'a'||iP>'z'
%if iN<'0'||iN>'9'&&iN<'A'||iN>'Z'&&iN<'a'||iN>'z'
%define ___isRAXUsed 1
%endif
%endif

%endif
%endrep
%endif
%endif
%endmacro

If RAX is used, then "___isRAXUsed is 1", if not, then "___isRAXUsed is 0".
It works like this, "Determine if RAX is used and print message if used or not":
Code: [Select]
isRAXUsed0 [rax+0xAAAA]
%if ___isRAXUsed==1
%warning 0. RAX is used
%else
%warning 0. RAX is not used
%endif

isRAXUsed0 [rbx+rax*8]
%if ___isRAXUsed==1
%warning 1. RAX is used
%else
%warning 1. RAX is not used
%endif

isRAXUsed0 [rax]
%if ___isRAXUsed==1
%warning 2. RAX is used
%else
%warning 2. RAX is not used
%endif

isRAXUsed0 rax
%if ___isRAXUsed==1
%warning 3. RAX is used
%else
%warning 3. RAX is not used
%endif

isRAXUsed0 [0xAAAA+rax*8]
%if ___isRAXUsed==1
%warning 4. RAX is used
%else
%warning 4. RAX is not used
%endif

extern ptrRAX

isRAXUsed0 [ptrRAX+rdx*8]
%if ___isRAXUsed==1
%warning 5. RAX is used
%else
%warning 5. RAX is not used
%endif

extern RAXzzz

isRAXUsed0 [RAXzzz+rdx*8]
%if ___isRAXUsed==1
%warning 6. RAX is used
%else
%warning 6. RAX is not used
%endif

Output:

Code: [Select]
test.asm:10: warning: 0. RAX is used
test.asm:17: warning: 1. RAX is used
test.asm:24: warning: 2. RAX is used
test.asm:31: warning: 3. RAX is used
test.asm:38: warning: 4. RAX is used
test.asm:49: warning: 5. RAX is not used
test.asm:58: warning: 6. RAX is not used


Im using my own set of macros and
macro "isRAXUsed0" came from my arsenal,
so, you, can have it,
it is tested and so far, no glitches for me.

For example, in my invoke macro, if RAX was used, i saved it into unallocated stack space [rsp-8].

Bye,
Encryptor256.
Encryptor256's Investigation \ Research Department.

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 430
  • Country: us
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #1 on: March 01, 2014, 05:17:47 PM »
Very good find, encryptor256!  Yes, we already do check for RAX usage as an argument because we have to use some register to move the argument contents to.  We've had some internal discussions regarding this behavior and RAX was chosen by default to handle this chore.  However, the check currently does not look for RAX if we encounter a memory reference using RAX as the base register thus leading to the bug you found.

For example, if your code looked like this instead:
Code: [Select]
bits 64

%include "nasmx.inc"

IMPORT wsprintfA

NASMX_PRAGMA FASTCALL_STACK_PRELOAD, DISABLE

proc poo
locals none

invoke wsprintfA,0,0,0,0,[rax+0xAAAA],rax   ; <-- note last rax argument

endproc

You would get a warning message regarding possible incorrect rax usage.  I'll modify the invoke macro and add this additional check and the save/restore of RAX as well.  I'm wondering if we still want to print out a warning that this leads to unoptimized code since we now have to save/restore rax due to multiple rax arguments.  Probably not necessary if we document the behavior.  Your thoughts?
Thanks, again!

Offline encryptor256

  • Full Member
  • **
  • Posts: 250
  • Country: lv
  • Win64 .
    • On Youtube: encryptor256
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #2 on: March 01, 2014, 05:35:14 PM »
You would get a warning message regarding possible incorrect rax usage.

Confirm, there was a warning message:
Code: [Select]
test.asm:12: warning: (INVOKE:409) use of _AX as arg resulted in inconsistent logic state

I'm wondering if we still want to print out a warning that this leads to unoptimized code since we now have to save/restore rax due to multiple rax arguments.  Probably not necessary if we document the behavior.

I agree, "Probably not necessary if we document the behavior".

Bye,
Encryptor256.
Encryptor256's Investigation \ Research Department.

Offline encryptor256

  • Full Member
  • **
  • Posts: 250
  • Country: lv
  • Win64 .
    • On Youtube: encryptor256
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #3 on: March 01, 2014, 06:16:05 PM »
Well, i think, it is not right to call it "unoptimized code".

MINGW64-GCC Tell's the same story: it uses rax register to store arguments.

So, it is not unompimized, it is just the way it is. :)

In directory: "C:\Program Files\mingw-builds\x64-4.8.1-posix-seh-rev5\mingw64\bin"
I compile this C file with: "gcc -Wall -c main.c"
Code: [Select]
#include <stdio.h>

#define type long long

void helloworld(type a,type b,type c,type d,type e,type f,type g,type h,type i,type j)
{
printf("\r\nHello world: %I64d, %I64d, %I64d, %I64d, %I64d, %I64d, %I64d, %I64d, %I64d, %I64d",
a,b,c,d,e,f,g,h,i,j);
};

int main()
{
helloworld(1,2,3,4,5,6,7,8,9,10);
return 0;
};

Then i debug with: "objdump -D -m i386:x86-64 main.obj > out.txt"
Output assembly syntax is a bit alienish, but can be understood well.
Content's of main.c - main procedure:
Code: [Select]
0000000000000080 <main>:
  80: 48 83 ec 58          sub    $0x58,%rsp
  84: b8 0a 00 00 00        mov    $0xa,%eax
  89: 48 89 44 24 48        mov    %rax,0x48(%rsp)
  8e: b8 09 00 00 00        mov    $0x9,%eax
  93: 48 89 44 24 40        mov    %rax,0x40(%rsp)
  98: b8 08 00 00 00        mov    $0x8,%eax
  9d: 48 89 44 24 38        mov    %rax,0x38(%rsp)
  a2: b8 07 00 00 00        mov    $0x7,%eax
  a7: 48 89 44 24 30        mov    %rax,0x30(%rsp)
  ac: b8 06 00 00 00        mov    $0x6,%eax
  b1: 48 89 44 24 28        mov    %rax,0x28(%rsp)
  b6: b8 05 00 00 00        mov    $0x5,%eax
  bb: 48 89 44 24 20        mov    %rax,0x20(%rsp)
  c0: 41 b9 04 00 00 00    mov    $0x4,%r9d
  c6: 41 b8 03 00 00 00    mov    $0x3,%r8d
  cc: ba 02 00 00 00        mov    $0x2,%edx
  d1: b9 01 00 00 00        mov    $0x1,%ecx
  d6: e8 25 ff ff ff        callq  0 <helloworld>
  db: 31 c0                xor    %eax,%eax
  dd: 48 83 c4 58          add    $0x58,%rsp
  e1: c3                    retq   

There we can see, that it uses rax to store arguments.

Bye.
Encryptor256's Investigation \ Research Department.

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 430
  • Country: us
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #4 on: March 01, 2014, 08:00:33 PM »
There we can see, that it uses rax to store arguments.

Yes, but in that C code example you're not passing RAX, which already contains a pointer or some value, as an argument.  Add some assembler code in the mix and watch what happens.  :P

Offline encryptor256

  • Full Member
  • **
  • Posts: 250
  • Country: lv
  • Win64 .
    • On Youtube: encryptor256
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #5 on: March 02, 2014, 08:34:11 AM »
There we can see, that it uses rax to store arguments.

Yes, but in that C code example you're not passing RAX, which already contains a pointer or some value, as an argument.  Add some assembler code in the mix and watch what happens.  :P

What i was thinking by looking behind, C curtains?. :D
Well, yes, i agree,
Okay, forget about C.

NASMX deals with raw power and is quite faster and smarter than C anyway.  :)

It is what it is,
if somebody would like to pass more than four arguments,
then, under certain circumstances, there is a need for save/restore - by sacrificing one register to do the whole dirty job.
Or, that somebody, can use linux and have six register arguments. :D

But still anyway, functions like, on Windows - CreateWindowEx, takes twelve arguments.
- Yes, but CreateWindowEx is not built for speed, GUI is not for speed.
So, non optimized code, while it is still faster than human eye, is permitted.

OS Windows users should be thank full,
that function CreateWindowEx, at the end, creates THAT requested window,
rather than, complaining how long it takes. :D

- Programmer calls CreateWindowEx.
- Os returns error -1.
- Programmer calls GetLastErrorDescription.
- Os returns: "Sorry, i have better things to do right now! See ya later!!!" :D

Bye.
Encryptor256's Investigation \ Research Department.

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 430
  • Country: us
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #6 on: March 03, 2014, 12:23:00 AM »
I've added support to NASM-X which now prevents this from occurring.

Also, I've modified the x64 fastcall behavior of register spills for the INVOKE and PROC macros.  While it works correctly for the int/ptr type args it does not currently support float type args.

Please download a snapshot and test it thoroughly.  Let me know your results - good or bad.

I'd like to start thinking about pushing out another release soon since there are quite a few enhancements and fixes made in the latest code base.

Thanks!


Offline encryptor256

  • Full Member
  • **
  • Posts: 250
  • Country: lv
  • Win64 .
    • On Youtube: encryptor256
Re: Win64 If invoke argument count is greater than four (RAX Glitch)
« Reply #7 on: March 03, 2014, 06:41:07 AM »
I did some x64 testing and didn't experienced any abnormal activity,
disassembled some files and my eye didn't caught anything suspicious,
also, compile and run, some of demos. (Didn't tried floating points.)

I liked what i see/saw/seen - i think, code, now is generated,
nearly like it should be, with one exception - callstack.
Each time invoke macro is used, it allocates a stack space,
if call stack pragma option is not assigned.

Maybe there is a need to assign some default callstack,
if invoke takes too much arguments, then give developer a warning that he should increase callstack manually or turn it off.
Let's say default callstack for 24 arguments (Which i did in my own set of macros).

It is easy to test, if somebody have a large size NASMX program (I Don't have any),
then, this individual can find mistakes fast, because - one mistake could affect the whole program.
Especially if that large program does some scientific calculations.

Well, i think, everything should be fine,
energy and time has been spent, so,
if you put energy and time into improvements then,
the only thing that can happen is to get better. or get a bunch of fatal errors, more problems, abandon the project, run, flee away:D

I think this is offtopic:
Only thing i found, that "win64\DEMO2" was a bit disrespectful,
here is a run time of demo2 via command console - cmd:
Code: [Select]
demo2.exe
First Name: Old
Last Name: Grandpa
Age: 102

Sorry kid, you're only 10!

Some Old Grandpa might get upset about this.
Encryptor256's Investigation \ Research Department.