Author Topic: Function calls through registers  (Read 825 times)

Vortex

  • Member
  • *****
  • Posts: 1987
Function calls through registers
« on: April 23, 2019, 04:03:51 AM »
A method below to call a function through a register :

Code: [Select]
.386
.model flat,stdcall
option casemap:none

ExitProcess PROTO :DWORD
LoadLibraryA PROTO :DWORD
LoadLibrary EQU <LoadLibraryA>
FreeLibrary PROTO :DWORD
GetProcAddress PROTO :DWORD,:DWORD

includelib kernel32.lib

.data?

hModule dd ?

.code

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

; eax4 taking four parameters

eax4 PROC x1:DWORD,y1:DWORD,w1:DWORD,z1:DWORD

    jmp eax

eax4 ENDP

OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef

start:

    LoadLibrary("user32.dll")
    mov hModule,eax

    GetProcAddress(eax,"MessageBoxA")
    eax4(0,"This is a test.","Hello!",0)

    FreeLibrary(hModule)
    ExitProcess(0)

END start

nidud

  • Member
  • *****
  • Posts: 1717
    • https://github.com/nidud/asmc
Re: Function calls through registers
« Reply #1 on: April 23, 2019, 05:24:09 AM »
Haven't thought about this but it is actually possible to typedef a register to a proto type and call it directly using invoke:

proto_r macro reg, args:vararg
    local t, p
    t typedef proto args
    p typedef ptr t
    exitm<assume reg:ptr p>
    endm
...
  proto_r(eax, :dword, :ptr, :ptr, :dword) 
  invoke eax, 0, addr @CStr("This is a test"), addr @CStr("Hello!"), 0

Asmc only allow a call to a register -- eax() -- but it should be possible to extend this to invoke.

  eax(0, "This is a test", "Hello!", 0)


Vortex

  • Member
  • *****
  • Posts: 1987
Re: Function calls through registers
« Reply #2 on: April 23, 2019, 05:52:32 AM »
Hi nidud,

Thanks for your elegant and nice macro. It works fine. In the past, the same operation was carried with my own invoke macro using Hutch's prX macros found in windows.inc :

Code: [Select]
ArgCount MACRO number
  LOCAL txt
  txt equ <typedef PROTO :DWORD>
    REPEAT number - 1
      txt CATSTR txt,<,:DWORD>
    ENDM
  EXITM <txt>
ENDM

pr0  typedef PROTO
pr1  ArgCount(1)
pr2  ArgCount(2)
pr3  ArgCount(3)
pr4  ArgCount(4)
pr5  ArgCount(5)
pr6  ArgCount(6)
pr7  ArgCount(7)
pr8  ArgCount(8)
pr9  ArgCount(9)
pr10 ArgCount(10)
pr11 ArgCount(11)
pr12 ArgCount(12)
pr13 ArgCount(13)
pr14 ArgCount(14)
pr15 ArgCount(15)
pr16 ArgCount(16)
pr17 ArgCount(17)
pr18 ArgCount(18)
pr19 ArgCount(19)
pr20 ArgCount(20)
pr21 ArgCount(21)
pr22 ArgCount(22)
pr23 ArgCount(23)
pr24 ArgCount(24)
pr25 ArgCount(25)

_invoke MACRO funcname:REQ,args:VARARG

LOCAL counter

    counter=0

    FOR param,<args>

        counter=counter+1

    ENDM

    IF counter EQ 0

        call funcname

    ELSE

        invoke @CatStr(<pr>,%counter) PTR funcname,args

        IF ((OPATTR(funcname)) AND 11100000000y) EQ 00100000000y  ; Is it a C function?

            add esp,4*counter                                     ; Balance the stack

        ENDIF

    ENDIF

ENDM

Code: [Select]
_invoke eax,0,addr @CStr("This is a test"),addr @CStr("Hello!"),0
If it's possible, could you add this feature to your invoke macro? This notation could be possible :
Code: [Select]
eax(0, "This is a test", "Hello!", 0)

nidud

  • Member
  • *****
  • Posts: 1717
    • https://github.com/nidud/asmc
Re: Function calls through registers
« Reply #3 on: April 23, 2019, 07:40:03 AM »
The reg(...) invoke is now extended to include arguments. This include eax..edi in 32-bit and rax..r15 in 64-bit.

Can't think of any legal cases where a register may follow by an open bracket except st(0) so it should (hopefully) be safe. A test may be added if something pops up thought.

HSE

  • Member
  • *****
  • Posts: 1079
  • <AMD>< 7-32>
Re: Function calls through registers
« Reply #4 on: April 23, 2019, 09:50:51 AM »
@Nidud:

With /Zne:
Code: [Select]
d:\masm32\ObjAsm\Code\Inc\guiddef.inc(21) : warning A8005: IF[n]DEF expects a plain symbol as argument : ifndef FAR
 \masm32\ObjAsm\Code\Inc\guiddef.inc(21): Included by
  \masm32\ObjAsm\Code\Inc\winnt.inc(724): Included by
   \masm32\ObjAsm\Code\Inc\minwindef.inc(123): Included by
    \masm32\ObjAsm\Code\Inc\windef.inc(15): Included by
     \masm32\ObjAsm\Code\Inc\Windows.inc(69): Included by
        SysSetup(85)[Model.inc]: Macro called from

nidud

  • Member
  • *****
  • Posts: 1717
    • https://github.com/nidud/asmc
Re: Function calls through registers
« Reply #5 on: April 23, 2019, 10:51:00 AM »
test case: ml -c test.asm
Code: [Select]
.386
.model flat, c

ifdef DWORD
echo <DWORD defined>
else
echo <DWORD not defined>
endif
ifdef NEAR
echo <NEAR defined>
else
echo <NEAR not defined>
endif
ifdef FAR
echo <FAR defined>
else
echo <FAR not defined>
endif

.code

    mov eax,DWORD
    mov eax,FAR
    mov eax,NEAR

    end
result: (no error)
<DWORD not defined>
<NEAR not defined>
<FAR not defined>

problem is: IF[n]DEF expects a plain symbol as argument

Vortex

  • Member
  • *****
  • Posts: 1987
Re: Function calls through registers
« Reply #6 on: April 23, 2019, 08:26:36 PM »
Hi nidud,

Many thanks. The quick example below calls the undocumented API function SetConsoleIcon. I also used your proto_r macro.

Code: [Select]
include SetConsoleIcon.inc

proto_r MACRO reg,args:VARARG

LOCAL t,p

    t TYPEDEF PROTO args
    p TYPEDEF PTR t
    exitm<ASSUME reg:PTR p>

ENDM

.data?

hIcon   dd ?

.code

start:

    GetModuleHandle(0)
    LoadIcon(eax,200)
    mov hIcon,eax

    GetModuleHandle("kernel32.dll")
    GetProcAddress(eax,"SetConsoleIcon")

    proto_r(eax,:DWORD) 
    eax(hIcon)

    MessageBox(0,"Console icon changed","SetConsoleIcon test",0)
    ExitProcess(0)

END start

HSE

  • Member
  • *****
  • Posts: 1079
  • <AMD>< 7-32>
Re: Function calls through registers
« Reply #7 on: April 23, 2019, 08:32:32 PM »
Thanks Nidud  :t

nidud

  • Member
  • *****
  • Posts: 1717
    • https://github.com/nidud/asmc
Re: Function calls through registers
« Reply #8 on: April 24, 2019, 12:01:36 AM »
 :biggrin:

Given registers no take arguments they could also be serialized in the same way as functions so this opens up some interesting possibilities. I added this feature to invoke so now this is possible:

    mov hIcon,LoadIcon(GetModuleHandle(0),200)

    proto_r(eax,:DWORD)
    GetProcAddress(GetModuleHandle("kernel32.dll"),"SetConsoleIcon")(hIcon)
    assume eax:nothing

Note the assumption there may cause some problems down the line in case the register is used to access a type, so this should probably be removed.

The parser chips away calls recursively right to left (inside brackets) so the last return is invoke eax which is discarded.

    GetModuleHandle("kernel32.dll")
        push    offset FLAT:?_001
        call    _GetModuleHandleA@4
    GetProcAddress(eax,"SetConsoleIcon")(hIcon)
        push    offset FLAT:?_002
        push    eax
        call    _GetProcAddress@8
    eax(hIcon)
        push    dword ptr [?_005]
        call    eax

The register call returns a register:

    eax(0)(1)(2)

        push    0
        call    eax
        push    1 
        call    eax
        push    2 
        call    eax

...

    proto_r(eax, :ptr)
    proto_r(edx, :ptr)
    proto_r(ebx, :ptr)
    proto_r(esi, :ptr)
    proto_r(edi, :ptr)

    ebx(esi(edi(edx(eax("Hello")))))("world!")

        push    offset _DATA:?_001
        call    eax               
        push    eax               
        call    edx               
        push    eax               
        call    edi               
        push    eax               
        call    esi               
        push    eax               
        call    ebx               
        push    offset _DATA:?_002
        call    eax               

Well, I attache the latest build here but there are some version conflict and loose ends in the source so I need to update this later.

Vortex

  • Member
  • *****
  • Posts: 1987
Re: Function calls through registers
« Reply #9 on: May 04, 2019, 07:48:47 PM »
Hi nidud,

Another idea is to use variables as callable functions. Those variables can be used multiple times in the source code. The problem is that the variable must be prototype at the same time. Do you have some suggestions?

The code below will produce an error while assembling, that's normal because of the missing prototype issue.

Code: [Select]
include SetConsoleIcon.inc

.data?

hIcon   dd ?
func    dd ?

.code

start:

    GetModuleHandle(0)
    LoadIcon(eax,200)
    mov hIcon,eax

    GetModuleHandle("kernel32.dll")
    GetProcAddress(eax,"SetConsoleIcon")
    mov func,eax

    func(hIcon) ; func could be used again later

    MessageBox(0,"Console icon changed","SetConsoleIcon test",0)
    ExitProcess(0)

END start

nidud

  • Member
  • *****
  • Posts: 1717
    • https://github.com/nidud/asmc
Re: Function calls through registers
« Reply #10 on: May 05, 2019, 06:57:55 AM »
The norm is to use a pointer from a type or a callback definition.
Code: [Select]
type_t macro args:vararg
  local t
    t typedef proto args
    exitm<t>
    endm

callback_t macro args:vararg
  local t, p
    t typedef proto args
    p typedef ptr t
    exitm<p>
    endm

foo proc

  local p:ptr type_t(:byte)
  local q:callback_t(:byte, :ptr)

    p(2)
    q(2, eax)

One solution would be to combine all possible types into a UNION. Currently updating the COM interface with regards to prototypes declarations but this should work provided declared in a segment.
Code: [Select]
proc_t union
  p proc :byte
  q proc :byte, :ptr
proc_t ends

  local x:proc_t

    x.p(2)
    x.q(2, eax)


Vortex

  • Member
  • *****
  • Posts: 1987
Re: Function calls through registers
« Reply #11 on: May 05, 2019, 06:54:27 PM »
Hi nidud,

Thanks for the macros. Here is the modified version of my example :
Code: [Select]
include SetConsoleIcon.inc

type_t macro args:vararg
  local t
    t typedef proto args
    exitm<t>
    endm

callback_t macro args:vararg
  local t, p
    t typedef proto args
    p typedef ptr t
    exitm<p>
    endm

.data?

hIcon   dd ?

.code

start PROC

local func:ptr type_t(:dword)

    GetModuleHandle(0)
    LoadIcon(eax,200)
    mov hIcon,eax

    GetModuleHandle("kernel32.dll")
    GetProcAddress(eax,"SetConsoleIcon")
    mov func,eax

    func(hIcon)

    MessageBox(0,"Console icon changed","SetConsoleIcon test",0)
    ExitProcess(0)

start ENDP

END start