flat assembler
Message board for the users of flat assembler.

Index > Macroinstructions > [fasmg] A simple macro for Win64 fastcall

Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8434
Location: Kraków, Poland
Tomasz Grysztar 04 Apr 2017, 19:12
I decided to give it a try and make a fasmg version of fasm's "fastcall" macro. The original one is quite convoluted, so instead of converting it I wrote a new one from scratch (but trying to preserve the behavior). This is what I got:
Code:
macro fastcall? proc*,args&
        local offset,framesize,type,value
        if framesize
                sub rsp,framesize
        end if
        offset = 0
        match any, args
                iterate arg, args
                        match =float? val, arg
                                type = 'f'
                                redefine value val
                                SSE.parse_operand @src,val
                        else match =addr? val, arg
                                type = 'a'
                                redefine value val
                                x86.parse_operand @src,[val]
                        else
                                type = 0
                                redefine value arg
                                SSE.parse_operand @src,arg
                        end match
                        if % < 5
                                if type = 'f'
                                        repeat 1, i:%-1
                                                if @src.size = 8 | ~ @src.size | @src.type = 'mmreg'
                                                        if @src.type = 'imm'
                                                                mov rax,value
                                                                movq xmm#i,rax
                                                        else
                                                                movq xmm#i,value
                                                        end if
                                                else if @src.size = 4
                                                        if @src.type = 'imm'
                                                                mov eax,value
                                                                movd xmm#i,eax
                                                        else
                                                                movd xmm#i,value
                                                        end if
                                                else
                                                        err 'invalid argument ',`arg
                                                end if
                                        end repeat
                                else
                                        iterate <rX,rXd,rXw,rXb>, rcx,ecx,cx,cl, rdx,edx,dx,dl, r8,r8d,r8w,r8b, r9,r9d,r9w,r9b
                                                indx 1+offset/8
                                                if type = 'a'
                                                        if @src.size = 8 | ~ @src.size
                                                                lea rX,[value]
                                                        else
                                                                err 'invalid argument ',`arg
                                                        end if
                                                else
                                                        if @src.size = 8 | ~ @src.size
                                                                mov rX,value
                                                        else if @src.size = 4
                                                                mov rXd,value
                                                        else if @src.size = 2
                                                                mov rXw,value
                                                        else if @src.size = 1
                                                                mov rXb,value
                                                        else
                                                                err 'invalid argument ',`arg
                                                        end if
                                                end if
                                                break
                                        end iterate
                                end if
                        else
                                if @src.type = 'reg'
                                        mov [rsp+offset],value
                                else if @src.type = 'mem'
                                        if type = 'a'
                                                if @src.size = 8 | ~ @src.size
                                                        lea rax,[value]
                                                        mov [rsp+offset],rax
                                                else
                                                        err 'invalid argument ',`arg
                                                end if
                                        else
                                                if @src.size = 8 | ~ @src.size
                                                        mov rax,value
                                                        mov [rsp+offset],rax
                                                else if @src.size = 4
                                                        mov eax,value
                                                        mov [rsp+offset],eax
                                                else if @src.size = 2
                                                        mov ax,value
                                                        mov [rsp+offset],ax
                                                else if @src.size = 1
                                                        mov al,value
                                                        mov [rsp+offset],al
                                                else
                                                        err 'invalid argument ',`arg
                                                end if
                                        end if
                                else if @src.type = 'imm'
                                        if @src.size = 8 | ~ @src.size
                                                mov qword [rsp+offset],value
                                        else if @src.size = 4
                                                mov dword [rsp+offset],value
                                        else if @src.size = 2
                                                mov word [rsp+offset],value
                                        else if @src.size = 1
                                                mov byte [rsp+offset],value
                                        else
                                                err 'invalid argument ',`arg
                                        end if
                                else if type = 'f' & @src.type = 'mmreg' & @src.size = 16
                                        movq [rsp+offset],value
                                else
                                        err 'invalid argument ',`arg
                                end if
                        end if
                        offset = offset + 8
                end iterate
        end match
        framesize = offset + offset and 8
        call proc
        if framesize
                add rsp,framesize
        end if
end macro

macro invoke? proc*,args&
        fastcall [proc],args
end macro

macro cinvoke? proc*,args&
        fastcall [proc],args
end macro    
I have not tested it much, so it may still have some bugs. It does not implement the "frame"/"endf" the original had, but it should be an easy addition.

Note that macro uses the "parse_operand" from my x86 instruction set package to detect types of arguments, it therefore has to be used with these macro. If you have x64 instruction set implemented with some other macros, you may need to change this macro accordingly.

Also the macro could become a bit faster if repeated parsing of the operands was avoided by calling the "x86.store_instruction" directly for at least some of the generated instructions. But this would make it a bit more complex and less clear.
Post 04 Apr 2017, 19:12
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8434
Location: Kraków, Poland
Tomasz Grysztar 06 Apr 2017, 14:04
Another variant which is even cleaner thanks to the use of enumerated variables for fastcall register names:
Code:
define fastcall?

fastcall.r1 equ rcx
fastcall.rd1 equ ecx
fastcall.rw1 equ cx
fastcall.rb1 equ cl
fastcall.rf1 equ xmm0

fastcall.r2 equ rdx
fastcall.rd2 equ edx
fastcall.rw2 equ dx
fastcall.rb2 equ dl
fastcall.rf2 equ xmm1

fastcall.r3 equ r8
fastcall.rd3 equ r8d
fastcall.rw3 equ r8w
fastcall.rb3 equ r8b
fastcall.rf3 equ xmm2

fastcall.r4 equ r9
fastcall.rd4 equ r9d
fastcall.rw4 equ r9w
fastcall.rb4 equ r9b
fastcall.rf4 equ xmm3

macro fastcall? proc*,args&
        local offset,framesize,type,value
        if framesize
                sub rsp,framesize
        end if
        offset = 0
        match any, args
                iterate arg, args
                        match =float? val, arg
                                type = 'f'
                                redefine value val
                                SSE.parse_operand @src,val
                        else match =addr? val, arg
                                type = 'a'
                                redefine value val
                                x86.parse_operand @src,[val]
                        else
                                type = 0
                                redefine value arg
                                SSE.parse_operand @src,arg
                        end match
                        if % < 5
                                if type = 'f'
                                        if @src.size = 8 | ~ @src.size | @src.type = 'mmreg'
                                                if @src.type = 'imm'
                                                        mov rax,value
                                                        movq fastcall.rf#%,rax
                                                else
                                                        movq fastcall.rf#%,value
                                                end if
                                        else if @src.size = 4
                                                if @src.type = 'imm'
                                                        mov eax,value
                                                        movd fastcall.rf#%,eax
                                                else
                                                        movd fastcall.rf#%,value
                                                end if
                                        else
                                                err 'invalid argument ',`arg
                                        end if
                                else
                                        if type = 'a'
                                                lea fastcall.r#%,[value]
                                        else
                                                if @src.size = 8 | ~ @src.size
                                                        redefine target fastcall.r#%
                                                        if @src.type <> 'reg' | ~ @src.imm eq fastcall.r#%
                                                                mov fastcall.r#%,value
                                                        end if
                                                else if @src.size = 4
                                                        redefine target fastcall.rd#%
                                                        if @src.type <> 'reg' | ~ @src.imm eq fastcall.rd#%
                                                                mov fastcall.rd#%,value
                                                        end if
                                                else if @src.size = 2
                                                        redefine target fastcall.r#%
                                                        if @src.type <> 'reg' | ~ @src.imm eq fastcall.rw#%
                                                                mov fastcall.rw#%,value
                                                        end if
                                                else if @src.size = 1
                                                        redefine target fastcall.rb#%
                                                        if @src.type <> 'reg' | ~ @src.imm eq fastcall.rb#%
                                                        end if
                                                else
                                                        err 'invalid argument ',`arg
                                                end if
                                        end if
                                end if
                        else
                                if @src.type = 'reg'
                                        mov [rsp+offset],value
                                else if @src.type = 'mem'
                                        if type = 'a'
                                                lea rax,[value]
                                                mov [rsp+offset],rax
                                        else
                                                if @src.size = 8 | ~ @src.size
                                                        mov rax,value
                                                        mov [rsp+offset],rax
                                                else if @src.size = 4
                                                        mov eax,value
                                                        mov [rsp+offset],eax
                                                else if @src.size = 2
                                                        mov ax,value
                                                        mov [rsp+offset],ax
                                                else if @src.size = 1
                                                        mov al,value
                                                        mov [rsp+offset],al
                                                else
                                                        err 'invalid argument ',`arg
                                                end if
                                        end if
                                else if @src.type = 'imm'
                                        if @src.size = 8 | ~ @src.size
                                                mov qword [rsp+offset],value
                                        else if @src.size = 4
                                                mov dword [rsp+offset],value
                                        else if @src.size = 2
                                                mov word [rsp+offset],value
                                        else if @src.size = 1
                                                mov byte [rsp+offset],value
                                        else
                                                err 'invalid argument ',`arg
                                        end if
                                else if type = 'f' & @src.type = 'mmreg' & @src.size = 16
                                        movq [rsp+offset],value
                                else
                                        err 'invalid argument ',`arg
                                end if
                        end if
                        offset = offset + 8
                end iterate
        end match
        framesize = offset + offset and 8
        call proc
        if framesize
                add rsp,framesize 
        end if 
end macro

macro invoke? proc*,args&
        fastcall [proc],args 
end macro 

macro cinvoke? proc*,args& 
        fastcall [proc],args 
end macro    
Note that with the x86/x64 macros from fasmg package "=" would work just as well as "equ" for the creation of register aliases.
Post 06 Apr 2017, 14:04
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8434
Location: Kraków, Poland
Tomasz Grysztar 06 Apr 2017, 14:13
The above ones still miss another feature of fasm's macro: the inline strings support. This can be added with a small insertion like this:
Code:
                        if @src.type = 'imm' & @src.size = 0
                                if value eqtype '' & ~ type = 'f'
                                        inline_string value
                                        type = 'a'
                                end if
                        end if     
just before the "if % < 5" block. This requires an additional "inline_string" macro which may look like:
Code:
macro inline_string var
        local data,continue
        jmp continue
        data TCHAR var,0
        redefine var data
        continue:
end macro    
The above variant inserts the string data in the middle of code just like fasm's original macro does, however having it as a separate macro allows to define alternatives, for example another implementation of "inline_string" could insert the string into data section, and perhaps even check for duplicates.
Post 06 Apr 2017, 14:13
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8434
Location: Kraków, Poland
Tomasz Grysztar 12 Apr 2017, 18:38
This macro with added "frame"/"endf" feature is now part of the fasm-like Windows headers package for fasmg.
Post 12 Apr 2017, 18:38
View user's profile Send private message Visit poster's website Reply with quote
VEG



Joined: 06 Feb 2013
Posts: 80
VEG 28 Apr 2017, 19:42
Tomasz Grysztar wrote:
for example another implementation of "inline_string" could insert the string into data section, and perhaps even check for duplicates.
Oh, nice idea! I think such technique will be very useful in different situations. Will be nice to have an example how to add some contents to a virtual container, and then insert all these contents in specific place of the binary. It could be useful not just for strings, but even for pieces of code, for example. Or you can add resources to the .rsrc section from different places (near the code which uses these resources) and in one special command all resources will be placed in the proper place.
Post 28 Apr 2017, 19:42
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8434
Location: Kraków, Poland
Tomasz Grysztar 28 Apr 2017, 20:18
VEG wrote:
Oh, nice idea! I think such technique will be very useful in different situations. Will be nice to have an example how to add some contents to a virtual container, and then insert all these contents in specific place of the binary. It could be useful not just for strings, but even for pieces of code, for example. Or you can add resources to the .rsrc section from different places (near the code which uses these resources) and in one special command all resources will be placed in the proper place.
There are actually many ways to do such things and I do not know yet which one might be the most efficient. But I made something quickly for you as an example:
Code:
string.buffer = ''

postpone
        string.data := string.buffer
end postpone

macro fastcall?.inline_string var
        local data
        virtual at fastcall?.strings
                db string.buffer
                data TCHAR var,0
                load string.buffer:$-$$ from $$
        end virtual
        redefine var data
end macro


; Put this line in your data section:

fastcall?.strings db string.data    
(Note that I define "fastcall?.inline_string" name instead of "inline_string" because this is the naming convention that I used in the later version of the "fastcall" macro for the Windows header package for fasmg.)

This is a plain and simple variant, it does not try to reuse strings. It is, however, completely order-independent, you can put the data section first or last and it is going to work correctly in all cases.

As for the string reuse, there are also multiple variants that come to my mind. One could be to use Boyer-Moore-Hornspool search like in a similar set of macros I made for fasm 1. This would have an advantage that it would be able to find reusable substrings. The other option could be to use EVAL to define symbols with names derived from string (something like base64 encoding) in a special namespace and this way utilize fasmg's symbol lookup to quickly check if a given string has already been placed somewhere and at what address.
Post 28 Apr 2017, 20:18
View user's profile Send private message Visit poster's website Reply with quote
VEG



Joined: 06 Feb 2013
Posts: 80
VEG 28 Apr 2017, 20:48
Tomasz Grysztar, oh, thank you for this example. I will definitely use it. It is a thing that I was almost dreaming about while using FASM1 Smile
Post 28 Apr 2017, 20:48
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.