NASM - The Netwide Assembler
Related Projects => NASMX => Topic started by: encryptor256 on March 01, 2014, 09:01:22 AM
-
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:
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:
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:
...
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.
%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":
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:
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.
-
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:
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!
-
You would get a warning message regarding possible incorrect rax usage.
Confirm, there was a warning message:
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.
-
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"
#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:
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.
-
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
-
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.
-
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 (http://sourceforge.net/p/nasmx/code/HEAD/tree/trunk/) 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!
-
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:
demo2.exe
First Name: Old
Last Name: Grandpa
Age: 102
Sorry kid, you're only 10!
Some Old Grandpa might get upset about this.