64 bit assembler > ASMC Development

Object-oriented programming (OOP) in Asmc

(1/3) > >>

This is based on the Component Object Model (COM) introduced by Microsoft in 1993. In order to access these objects some "unconventional" methods where added to the assembler. The way COM objects are accessed in this case are from the C implementation declared in the Microsoft header files. A typical class will then be like this:

typedef struct IShellFolderVtbl {
    HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IShellFolder * _This, REFIID riid, void **ppvObject);
    } IShellFolderVtbl;

interface IShellFolder {
   CONST_VTBL struct IShellFolderVtbl *lpVtbl;

The assembler version:

--- Code: ---IShellFolderVtbl struct
STDMETHOD QueryInterface, :REFIID, :ptr
IShellFolderVtbl ends
IShellFolder struct
lpVtbl PVOID ?
IShellFolder ends
LPIShellFolder typedef ptr IShellFolder

--- End code ---

The objects are normally instantiated with a CreateInstance() function that takes the class as argument and we end up with a pointer or just AX. The call to the method is then something like this:

    mov rcx,rax   ; load args
    mov rdx,riid
    mov r8,ppvObject
    mov rax,[rcx] ; make the call
    call [rax].IShellFolderVtbl.QueryInterface

The assembler needs to know the type of the base class in order to use these unconventional methods.

    local s:IShellFolder
    s.QueryInterface(riid, ppvObject)
  * lea rcx,s

    local p:LPIShellFolder
    p.QueryInterface(riid, ppvObject)
  * mov rcx,p

    assume rbx:ptr IShellFolder
    [rbx].QueryInterface(riid, ppvObject)
  * mov rcx,rbx

If PROC is used inside a structure (normally an error) Asmc assumes this to be a pointer to a function. A structure member with a placeholder for strcmp/stricmp may then be defined as follow:

    Compare proc :LPSTR, :LPSTR

  * T$0001 typedef proto :LPSTR, :LPSTR
  * P$0001 typedef ptr T$0001
  * Compare P$0001 ?

The next target is a simplified class definition based on the above logic including static/external callbacks. The .CLASSDEF <name> [args] directive creates two structures and add the Vtbl pointer to the base.

.class IShellFolder

    QueryInterface      proc :REFIID, :ptr
    AddRef              proc
    Release             proc


The first method closes the base and adds Vtbl to the name.

.class IShellFolder
  * LPISHELLFOLDER typedef ptr IShellFolder
  * LPISHELLFOLDERVtbl typedef ptr IShellFolderVtbl
  * IShellFolder::IShellFolder proto
  * IShellFolder struct
  * IShellFolder ends
  * IShellFolderVtbl struct 8
    QueryInterface      proc :REFIID, :ptr
  * T$000B typedef proto :ptr IShellFolder, :REFIID, :ptr
  * P$000B typedef ptr T$000B
  * QueryInterface P$000B ?
  * IShellFolderVtbl ends

To define the Compare pointer as in the structure above locally in the base class the keyword LOCAL may be used. Locally defined functions are called directly without the _this argument omitted.

    Compare proc local :LPSTR, :LPSTR

Keywords like public could be consider later I guess, or any keyword not normally used after PROC but this in turn creates a half-baked OOP framework to exploit.

Here's a class created on the stack. This one resets the stack on exit so the order in which they are deleted matters as oppose to delete everything in the end using LEAVE. Note that the start offset of the class (.base in this case) is off by 8 bytes hence the buffer is aligned 16.

--- Code: ---;
; Build: asmc -pe -win64 $*.asm
include stdio.inc


.class XPush :UINT

    base    QWORD ?
    buffer  OWORD 8 dup(?)

    Release proc


    assume rcx:LPXPUSH

    option win64:rsp nosave noauto

XPush::Release proc

    movaps xmm0,[rcx].buffer[0x00]
    movaps xmm1,[rcx].buffer[0x10]
    movaps xmm2,[rcx].buffer[0x20]
    movaps xmm3,[rcx].buffer[0x30]
    movaps xmm4,[rcx].buffer[0x40]
    movaps xmm5,[rcx].buffer[0x50]
    movaps xmm6,[rcx].buffer[0x60]
    movaps xmm7,[rcx].buffer[0x70]
    mov rax,[rsp]
    mov rsp,[rcx].base
    jmp rax

XPush::Release endp

XPush::XPush proc ReservedStack:UINT


        .if !rcx

            mov r8,rsp
            lea rax,[r8+rdx]        ; stack base
            lea rcx,[rdx+8*16+16]   ; alloc size
            sub rax,rcx
            and al,-16
            test [rax],al           ; probe..
            mov rcx,[r8]            ; return addr
            mov rsp,rax             ; new stack
            mov [rax],rcx
            and dl,-16              ; shadow space
            lea rcx,[rax+rdx+16]
            add r8,8
            mov [rcx].base,r8

        movaps [rcx].buffer[0x00],xmm0
        movaps [rcx].buffer[0x10],xmm1
        movaps [rcx].buffer[0x20],xmm2
        movaps [rcx].buffer[0x30],xmm3
        movaps [rcx].buffer[0x40],xmm4
        movaps [rcx].buffer[0x50],xmm5
        movaps [rcx].buffer[0x60],xmm6
        movaps [rcx].buffer[0x70],xmm7

        lea rdx,[rax+0x80]
        mov [rcx],rdx
        lea rax,XPush@Release
        mov [rdx],rax
        mov rax,rcx
    .until 1

XPush::XPush endp

    option win64:rbp auto

main proc

  local p:LPXPUSH

    printf("rsp: %p\n", rsp)
    mov p,XPush::XPush( NULL, @ReservedStack )
    printf("rsp: %p\n", rsp)
    printf("rsp: %p\n", rsp)

main endp

    end main

--- End code ---

Added a separate directive for COM without any types added. This is now used in the windows include files. In addition to this a default method (Release()) is added to .CLASS.

    Method1 proc local  ; static
    Method2 proc :ptr   ; first virtual function
  * Class ends
  * ClassVtbl struct
  * T$000B typedef proto :ptr Class
  * P$000B typedef ptr T$000B
  * Method2 P$000B ?

This didn't work (fixed): externdef Profile:ptr CProfile

This is indeed a good way of bundle up software where objects could be created on the fly or used as global pointers. Data may not be needed or accessed using methods so the public information may not be the same as in the local implementation.

Local definition


string      LPSTR ?
begin       dq ?
result      dq ?

.class CProfile

    table PROFILE MAXPROFILES dup(<>)

    Release proc
    Start proc :UINT, :LPSTR
    Stop  proc :UINT
    Save  proc :UINT, :LPSTR
    Clear proc


Global header

.class CProfile

    Release proc
    Start proc :dword, :ptr
    Stop  proc :dword
    Save  proc :dword, :ptr
    Clear proc


externdef Profile:LPCPROFILE

Guess you may see this as protected or private members of the class. As for sheared members this may be done using pointers (as in COM) or inheritance for code reuse.

Two classes using the same methods:

.class CHeap BufferSize:UINT

    heap_base   PVOID ?
    heap_free   PVOID ?

    Release     proc
    Alloc       proc :UINT
    Free        proc :PVOID
    Realloc     proc :PVOID, :UINT
    Aligned     proc :UINT, :UINT
    NewString   proc :LPSTR
    Coreleft    proc


.class CStack BufferSize:UINT, ReservedStack:UINT

    heap_base   PVOID ?
    heap_free   PVOID ?
    stack_ptr   SIZE_T ?

    Release     proc
    Alloc       proc :UINT
    Free        proc :PVOID
    Realloc     proc :PVOID, :UINT
    Aligned     proc :UINT, :UINT
    NewString   proc :LPSTR
    Coreleft    proc


The linkage of these modules is based on usage so the instance of Profile is auto created and ready to go in the same way as the __argv table. The stack buffer (CStack) however needs to be created in a stack frame so this have to be dynamically created.

The heap functions may then be profiled as follow:

--- Code: ---if 0 ; compare to LIBC.malloc()
include malloc.inc
malloc  equ <memory.Alloc>
free    equ <memory.Free>

; global pointer

.class CStack :dword, :dword
    Release     proc
    Alloc       proc :dword
    Free        proc :ptr

    memory  LPCSTACK 0

; create a one million byte stack buffer

main proc

  local stk ; create a standard stack frame

    mov memory, CStack::CStack(NULL, 1000000, @ReservedStack)

    Profile.Save(4, "CStack::CStack()")

main endp

; allocate some memory

heap_loop proc uses rsi rdi rbx r12

    Profile.Start(0, "main")

    .for (r12d=1000 : r12d : r12d-- )

        Profile.Start(1, "loop")

        .break .if !malloc( 1000 * size_t )
        mov rsi,rax

        Profile.Start(2, "malloc")
        .for ( rdi=rsi, ebx=0 : ebx < 1000 : ebx++ )

            malloc( 512 )

        Profile.Start(3, "free")
        .for ( rdi=rsi, ebx=0 : ebx < 1000 : ebx++ )



heap_loop endp

--- End code ---

In this case the .Save() command append result to a text file in this format:

--- Code: ---CStack::CStack() - 2018-4-2 21:13:58:
 0:       52290 main
 1:       46500 loop
 2:        9524 malloc
 3:        8708 free
LIBC.malloc() - 2018-4-2 21:14:58:
 0:       71304 main
 1:       65418 loop
 2:       26041 malloc
 3:       16665 free

--- End code ---

As for a more sophisticated implementation of OOP this I think will be rather clunky in assembler. It will also demand a lot of compiling in addition to all the bloat produced. The 64-bit code seems to clean up very nicely using this approach, having RCX pinned to the class. Making a habit of also returning RCX may simplify even further.

foo proc

  local p:LPCLASS


foo endp

Code produced:

   0: 55            push   rbp
   1: 48 8b ec      mov    rbp,rsp
   4: 48 83 ec 30   sub    rsp,0x30
   8: ff 51 10      call   QWORD PTR [rcx+0x10]
   b: 48 8b 01      mov    rax,QWORD PTR [rcx]
   e: ff 50 08      call   QWORD PTR [rax+0x8]
  11: 48 8b 01      mov    rax,QWORD PTR [rcx]
  14: ff 10         call   QWORD PTR [rax]
  16: c9            leave
  17: c3            ret

Hi nidud,

there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.

For example, if you create a method named OpenFile or CreateFile the call to it will throw an error.


--- Code: ---include windows.inc ;// OpenFile declared in winbase.inc
include stdio.inc
include malloc.inc

.classdef Class :LPSTR

    string LPSTR ?

    Print proc
    OpenFile proc :LPTSTR ;// Class::OpenFile ( ) declaration


--- End code ---


--- Code: ---include class.inc

    virtual_table PVOID 0


Class::Print proc

    printf( "%s\n", [rcx].Class.string )

Class::Print endp

;//  Class::OpenFile procedure/method definition
Class::OpenFile proc szPath:LPTSTR

xor rax, rax
Class::OpenFile endp

Class::Class proc String:LPSTR


        .if !rcx

            .break .if !malloc( sizeof(Class) )
            mov rcx,rax
            mov rdx,String
        mov [rcx].Class.string,rdx
        mov rax,virtual_table
        mov [rcx].Class.lpVtbl,rax
        mov rax,rcx
    .until 1

Class::Class endp

Install proc private

    .if malloc( sizeof(ClassVtbl) )

        mov virtual_table,rax
        lea rcx,free
        mov [rax].ClassVtbl.Release,rcx
        lea rcx,Class@Print
        mov [rax].ClassVtbl.Print,rcx
Install endp

.pragma init Install 50


--- End code ---


--- Code: ---include class.inc


main proc

  local p:LPCLASS, s:Class

    .if Class::Class( NULL, "String" )

        mov p,rax

        p.OpenFile("test.inc") ;// error A2008: syntax error : p

    .if Class::Class( &s, "String2" )


main endp


--- End code ---

--- Code: ---test.asm(14) : error A2008: syntax error : p
--- End code ---

This is because asmc assumes in the call function OpenFile from winbase.inc.



--- Quote from: Greenhorn on April 16, 2018, 06:02:10 AM ---there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.

--- End quote ---

Yes. It follows the same logic as a struct members. I usually add an underscore in front of these names.

I guess it would be possible to add some exception to this but that will destroy the simplicity of it all. As of now the .COMDEF and .CLASSDEF directives only creates two structures and add members accordingly, so this will also work without these directives by simply declaring a COM object as done in C.


[0] Message Index

[#] Next page

Go to full version