Author Topic: Object-oriented programming (OOP) in Asmc  (Read 963 times)

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Object-oriented programming (OOP) in Asmc
« on: March 28, 2018, 04:00:32 AM »
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 {
   BEGIN_INTERFACE
    HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IShellFolder * _This, REFIID riid, void **ppvObject);
    ...
   END_INTERFACE
    } IShellFolderVtbl;

interface IShellFolder {
   CONST_VTBL struct IShellFolderVtbl *lpVtbl;
    };

The assembler version:
Code: [Select]
IShellFolderVtbl struct
STDMETHOD QueryInterface, :REFIID, :ptr
...
IShellFolderVtbl ends
IShellFolder struct
lpVtbl PVOID ?
IShellFolder ends
LPIShellFolder typedef ptr IShellFolder

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.

.classdef IShellFolder

    QueryInterface      proc :REFIID, :ptr
    AddRef              proc
    Release             proc

    .ends

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

.classdef IShellFolder
  * LPISHELLFOLDER typedef ptr IShellFolder
  * LPISHELLFOLDERVtbl typedef ptr IShellFolderVtbl
  * IShellFolder@IShellFolder proto :ptr IShellFolder
  * IShellFolder struct 8
  * lpVtbl LPISHELLFOLDERVtbl ?
  * 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 ?

    ...
    .ends
  * 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.

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #1 on: March 29, 2018, 06:17:15 AM »
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: [Select]
;
; Build: asmc -pe -win64 $*.asm
;
include stdio.inc

    .code

.classdef XPush :UINT

    base    QWORD ?
    buffer  OWORD 8 dup(?)

    Release proc

    .ends

    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

    .repeat

        .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
        .endif

        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
    ret

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)
    p.Release()
    printf("rsp: %p\n", rsp)
    ret

main endp

    end main

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #2 on: April 03, 2018, 10:44:18 PM »
.COMDEF
 
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 .CLASSDEF.

    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
  * Release P$000B ?
  * 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

MAXPROFILES equ 32

PROFILE     STRUC
string      LPSTR ?
begin       dq ?
result      dq ?
PROFILE     ENDS

.classdef CProfile

    table PROFILE MAXPROFILES dup(<>)

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

    .ends

Global header

.classdef CProfile

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

    .ends

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:

.classdef CHeap BufferSize:UINT

    heap_base   PVOID ?
    heap_free   PVOID ?

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

    .ends

.classdef CStack BufferSize:UINT, ReservedStack:UINT

    heap_base   PVOID ?
    heap_free   PVOID ?
    stack_ptr   SIZE_T ?

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

    .ends

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: [Select]
if 0 ; compare to LIBC.malloc()
include malloc.inc
else
malloc  equ <memory.Alloc>
free    equ <memory.Free>
endif

; global pointer

.classdef CStack :dword, :dword
    Alloc       proc :dword
    Free        proc :ptr
    .ends

    .data
    memory  LPCSTACK 0
    .code

; create a one million byte stack buffer

main proc

  local stk ; create a standard stack frame

    mov memory, CStack::CStack(NULL, 1000000, @ReservedStack)
    heap_loop()
    memory.Release()

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

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 )
            stosq
        .endf
        Profile.Stop(2)

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

            lodsq
            free(rax)
        .endf
        Profile.Stop(3)

        free(rdi)
        Profile.Stop(1)
    .endf
    Profile.Stop(0)
    ret

heap_loop endp

In this case the .Save() command append result to a text file in this format:
Code: [Select]
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

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

    [rcx].Method1()
    [rcx].Method2(rdx)
    [rcx].Release()
    ret

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


Greenhorn

  • Member
  • **
  • Posts: 98
Re: Object-oriented programming (OOP) in Asmc
« Reply #3 on: April 16, 2018, 06:02:10 AM »
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.

"class.inc"
Code: [Select]
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

    .ends


"class.asm"
Code: [Select]
include class.inc

    .data
    virtual_table PVOID 0

    .code

Class::Print proc

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

Class::Print endp

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

xor rax, rax
ret
Class::OpenFile endp

Class::Class proc String:LPSTR

    .repeat

        .if !rcx

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

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
    .endif
    ret
Install endp

.pragma init Install 50

    end

"test.asm"
Code: [Select]
include class.inc

    .code

main proc

  local p:LPCLASS, s:Class

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

        mov p,rax

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

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

        s.Print()
    .endif
    ret

main endp

    end

Output
Code: [Select]
test.asm(14) : error A2008: syntax error : p
This is because asmc assumes in the call function OpenFile from winbase.inc.


Regards
Greenhorn

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #4 on: April 16, 2018, 09:03:53 PM »
there seems to be a name conflict when creating methods with names matching from protos in other includes, e.g. windows header files.

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.

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #5 on: June 25, 2018, 02:11:59 AM »
Added a more portable implementation for class::member

Instead of using '@' to separate class from member functions an underscore is now used. In addition to this regular types may also be used where *this is declared directly as oppose to a pointer.

Some examples using types.

    sbyte::add proto :sbyte
    oword::add proto :oword
 * sbyte_add proto :sbyte, :sbyte
 * oword_add proto :oword, :oword


The assembler will in this case convert oword to pointers but pass bytes as value.

foo proc

  local c1:sbyte, c2:sbyte
  local o1:oword, o2:oword

    sbyte::add( c1, c2 )
    oword::add( o1, o2 )
    ret

foo endp
 20a:   55                      push   rbp
 20b:   48 8b ec                mov    rbp,rsp
 20e:   48 83 ec 50             sub    rsp,0x50
 212:   8a 4d ff                mov    cl,BYTE PTR [rbp-0x1]
 215:   8a 55 fe                mov    dl,BYTE PTR [rbp-0x2]
 218:   e8 e3 ff ff ff          call   0x200
 21d:   48 8d 4d e8             lea    rcx,[rbp-0x18]
 221:   48 8d 55 d8             lea    rdx,[rbp-0x28]
 225:   e8 e0 ff ff ff          call   0x20a
 22a:   c9                      leave
 22b:   c3                      ret


Using struct's and floats.

xword struc
    l dq ?
    h dq ?
xword ends

xword::add proto :xword
real16::add proto :real16

foo proc

  local x1:xword, x2:xword
  local r1:real16, r2:real16

    xword::add( x1, x2 )
    real16::add( r1, r2 )
    ret

foo endp
 214:   55                      push   rbp
 215:   48 8b ec                mov    rbp,rsp
 218:   48 83 ec 60             sub    rsp,0x60
 21c:   48 8d 4d f0             lea    rcx,[rbp-0x10]
 220:   48 8d 55 e0             lea    rdx,[rbp-0x20]
 224:   e8 e1 ff ff ff          call   0x20a
 229:   0f 28 45 d0             movaps xmm0,XMMWORD PTR [rbp-0x30]
 22d:   0f 28 4d c0             movaps xmm1,XMMWORD PTR [rbp-0x40]
 231:   e8 ca ff ff ff          call   0x200
 236:   c9                      leave
 237:   c3                      ret

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Re: Object-oriented programming (OOP) in Asmc
« Reply #6 on: August 09, 2018, 10:18:47 PM »
Added some more samples to classdef directory.

Using RCX as mention above cleans up very well. In the TConsole sample RCX is used for all calls to the class. This is a static class auto installed using .pragma(init()), so the only external to the module is the pointer (Console):
Code: [Select]
include tdialog.inc

    .code

    assume rcx: ptr TConsole

main proc

  local cursor:CURSOR

    mov rcx,Console
    [rcx].CursorGet(&cursor)
    [rcx].Maxconsole()
    [rcx].Getch()
    [rcx].Moveconsole(100, 100)
    [rcx].Setconsole(50, 8)
    [rcx].Setattrib(0x1B)
    [rcx].Clrconsole()
    [rcx].CPrintf(10, 2, "TConsole::CPrintf()\n TConsole::CPuta()")
    [rcx].CPuta(10, 3, 19, 0x70)
    [rcx].Update()
    [rcx].CursorOff()
    [rcx].Getch()
    [rcx].Setattrib(0x07)
    [rcx].Clrconsole()
    [rcx].Update()
    [rcx].CursorSet(&cursor)
    [rcx].Setconsole(80, 25)
    ret

main endp

    end

nidud

  • Member
  • *****
  • Posts: 1614
    • https://github.com/nidud/asmc
Using System V methods in a class
« Reply #7 on: August 29, 2018, 02:42:42 AM »
The SYSCALL directive in 64-bit (in addition to the instruction) refers to the System V AMD64 calling convention. The argument passing in the AMD64 ABI uses six registers as oppose to four in the Windows ABI.

RDI RSI RDX RCX R8 R9 – syscall
        RCX RDX R8 R9 – fastcall


This is convenient for class members given RDI/RSI is preserved through Windows API calls if the class pointer resides in a register (RDI in this case). You also get additional two registers for arguments so this is a good mix. However, the :vararg logic differ in System V so this can not be used with the Windows ABI functions unless the function called is local.

A sample class using syscall methods. This from https://msdn.microsoft.com/en-us/library/hskdteyh.aspx
Code: [Select]
.classdef InstructionSet syscall

    nIds            SINT ?
    nExIds          SINT ?
    vendor          LPSTR ?
    brand           LPSTR ?
    isIntel         SINT ?
    isAMD           SINT ?
    f_1_ECX         dd ?
    f_1_EDX         dd ?
    f_7_EBX         dd ?
    f_7_ECX         dd ?
    f_81_ECX        dd ?
    f_81_EDX        dd ?

    GetVendor       proc
    GetBrand        proc
    GetSSE3         proc
    GetPCLMULQDQ    proc
    GetMONITOR      proc
    GetSSSE3        proc
    GetFMA          proc
    GetCMPXCHG16B   proc
    GetSSE41        proc
    GetSSE42        proc
    GetMOVBE        proc
    GetPOPCNT       proc
    GetAES          proc
    GetXSAVE        proc
    GetOSXSAVE      proc
    GetAVX          proc
    GetF16C         proc
    GetRDRAND       proc
    GetMSR          proc
    GetCX8          proc
    GetSEP          proc
    GetCMOV         proc
    GetCLFSH        proc
    GetMMX          proc
    GetFXSR         proc
    GetSSE          proc
    GetSSE2         proc
    GetFSGSBASE     proc
    GetBMI1         proc
    GetHLE          proc
    GetAVX2         proc
    GetBMI2         proc
    GetERMS         proc
    GetINVPCID      proc
    GetRTM          proc
    GetAVX512F      proc
    GetRDSEED       proc
    GetADX          proc
    GetAVX512PF     proc
    GetAVX512ER     proc
    GetAVX512CD     proc
    GetSHA          proc
    GetPREFETCHWT1  proc
    GetLAHF         proc
    GetLZCNT        proc
    GetABM          proc
    GetSSE4a        proc
    GetXOP          proc
    GetTBM          proc
    GetSYSCALL      proc
    GetMMXEXT       proc
    GetRDTSCP       proc
    Get3DNOWEXT     proc
    Get3DNOW        proc

    .ends
It's possible to override the calling convention for the member functions but the default constructor and the Release() function follows the class calling convention if set.

    Get3DNOW proc fastcall

As for the implementation of methods they need to be declared if the calling convention differ from the global default.
Code: [Select]
    .code

    option win64:noauto
    assume rdi:LPINSTRUCTIONSET

InstructionSet::GetVendor proc syscall
    mov rax,[rdi].vendor
    ret
InstructionSet::GetVendor endp

InstructionSet::GetBrand proc syscall
    mov rax,[rdi].brand
    ret
InstructionSet::GetBrand endp

__declget macro method, reg, bit
InstructionSet::Get&method& proc syscall
    mov eax,[rdi].reg
    and eax,1 shl bit
    ret
InstructionSet::Get&method& endp
    endm

__declge2 macro method, reg, bit, is
InstructionSet::Get&method& proc syscall
    bt [rdi].is,0
    sbb eax,eax
    and eax,[rdi].reg
    and eax,1 shl bit
    ret
InstructionSet::Get&method& endp
    endm

__declget SSE3,         f_1_ECX, 0
__declget PCLMULQDQ,    f_1_ECX, 1
__declget MONITOR,      f_1_ECX, 3
...

Given there's no inherent shadow space logic and thus no placeholder for arguments the names refer to the corresponding register:

    mov rax,[_this].vendor

The default constructor and Release():
Code: [Select]
    option win64:rbp nosave

InstructionSet::InstructionSet proc syscall uses rbx

  local cpui[4]:SINT
  local vendor[0x20]:SBYTE
  local brand[0x40]:SBYTE
  local cpustring[512]:SBYTE

    .if malloc( sizeof(InstructionSet) + sizeof(InstructionSetVtbl) )

        assume rsi:ptr InstructionSet

        mov rsi,rax
        mov rdi,rax
        xor eax,eax
        mov ecx,sizeof(InstructionSet)
        rep stosb
        mov [rsi],rdi
        lea rax,InstructionSet_Release
        stosq
        for q,<Vendor,Brand,SSE3,PCLMULQDQ,MONITOR,SSSE3,FMA,CMPXCHG16B,SSE41,SSE42,MOVBE,POPCNT,\
            AES,XSAVE,OSXSAVE,AVX,F16C,RDRAND,MSR,CX8,SEP,CMOV,CLFSH,MMX,FXSR,SSE,SSE2,FSGSBASE,BMI1,\
            HLE,AVX2,BMI2,ERMS,INVPCID,RTM,AVX512F,RDSEED,ADX,AVX512PF,AVX512ER,AVX512CD,SHA,\
            PREFETCHWT1,LAHF,LZCNT,ABM,SSE4a,XOP,TBM,SYSCALL,MMXEXT,RDTSCP,3DNOWEXT,3DNOW>
            lea rax,InstructionSet_Get&q&
            stosq
            endm

        ;;
        ;; Calling __cpuid with 0x0 as the function_id argument
        ;; gets the number of the highest valid function ID.
        ;;
        mov [rsi].nIds,__cpuid(&cpui, 0)

        .for (rdi = &cpustring, ebx = 0: ebx <= [rsi].nIds: ++ebx)

            __cpuidex(&cpui, ebx, 0)
            stosd
            mov eax,cpui[4]
            stosd
            mov eax,ecx
            stosd
            mov eax,edx
            stosd
        .endf
        ;;
        ;; Capture vendor string
        ;;
        lea rdi,brand
        xor eax,eax
        mov ecx,sizeof(brand) + sizeof(vendor)
        rep stosb
        lea rax,vendor
        mov ecx,dword ptr cpustring[0x04]
        mov [rax],ecx
        mov ecx,dword ptr cpustring[0x0C]
        mov [rax+4],ecx
        mov ecx,dword ptr cpustring[0x08]
        mov [rax+8],ecx
        mov [rsi].vendor,_strdup(rax)

        .if ( !strcmp(rax, "GenuineIntel") )
            mov [rsi].isIntel,TRUE
        .elseif ( !strcmp([rsi].vendor, "AuthenticAMD") )
            mov [rsi].isAMD,TRUE
        .endif
        ;;
        ;; load bitset with flags for function 0x00000001
        ;;
        .if ([rsi].nIds >= 1)

            mov eax,dword ptr cpustring[0x10][0x8]
            mov edx,dword ptr cpustring[0x10][0xC]
            mov [rsi].f_1_ECX,eax
            mov [rsi].f_1_EDX,edx
        .endif
        ;;
        ;; load bitset with flags for function 0x00000007
        ;;
        .if ([rsi].nIds >= 7)

            mov eax,dword ptr cpustring[0x70][0x4]
            mov edx,dword ptr cpustring[0x70][0x8]
            mov [rsi].f_7_EBX,eax
            mov [rsi].f_7_ECX,edx
        .endif
        ;;
        ;; Calling __cpuid with 0x80000000 as the function_id argument
        ;; gets the number of the highest valid extended ID.
        ;;
        mov [rsi].nExIds,__cpuid(&cpui, 0x80000000)

        .fors (rdi = &cpustring, ebx = 0x80000000: ebx <= [rsi].nExIds: ++ebx)

            __cpuidex(&cpui, ebx, 0)
            stosd
            mov eax,cpui[4]
            stosd
            mov eax,ecx
            stosd
            mov eax,edx
            stosd
        .endf

        ;;
        ;; load bitset with flags for function 0x80000001
        ;;
        .if ([rsi].nExIds >= 0x80000001)

            mov eax,dword ptr cpustring[0x10][0x8]
            mov edx,dword ptr cpustring[0x10][0xC]
            mov [rsi].f_81_ECX,eax
            mov [rsi].f_81_EDX,edx
        .endif

        ;;
        ;; Interpret CPU brand string if reported
        ;;

        .if ([rsi].nExIds >= 0x80000004)

            mov rdx,rsi
            lea rdi,brand
            lea rsi,cpustring[0x20]
            mov ecx,(sizeof(cpui) * 3) / 8
            rep movsq
            mov rsi,rdx
            mov [rsi].brand,_strdup(&brand)
        .endif

        assume rsi:nothing
        mov rax,rsi
    .endif
    ret

InstructionSet::InstructionSet endp

InstructionSet::Release proc syscall
    free([rdi].vendor)
    free([rdi].brand)
    free(rdi)
    ret
InstructionSet::Release endp

Create the class and print out some information using RDI as pointer.
Code: [Select]
main proc

    mov rdi,InstructionSet::InstructionSet(NULL)
    support_message macro isa_feature, is_supported
        mov edx,' '
        .if is_supported
            mov edx,'x'
        .endif
        printf( "[%c] %s\n", edx, isa_feature )
        retm<>
        endm

    printf( "%s\n", [rdi].GetVendor() )
    printf( "%s\n", [rdi].GetBrand() )

    support_message("3DNOW",       [rdi].Get3DNOW())
    support_message("3DNOWEXT",    [rdi].Get3DNOWEXT())
    support_message("ABM",         [rdi].GetABM())
    support_message("ADX",         [rdi].GetADX())
    support_message("AES",         [rdi].GetAES())
    support_message("AVX",         [rdi].GetAVX())
    support_message("AVX2",        [rdi].GetAVX2())
    support_message("AVX512CD",    [rdi].GetAVX512CD())
    support_message("AVX512ER",    [rdi].GetAVX512ER())
    support_message("AVX512F",     [rdi].GetAVX512F())
    support_message("AVX512PF",    [rdi].GetAVX512PF())
    support_message("BMI1",        [rdi].GetBMI1())
    support_message("BMI2",        [rdi].GetBMI2())
    support_message("CLFSH",       [rdi].GetCLFSH())
    support_message("CMPXCHG16B",  [rdi].GetCMPXCHG16B())
    support_message("CX8",         [rdi].GetCX8())
    support_message("ERMS",        [rdi].GetERMS())
    support_message("F16C",        [rdi].GetF16C())
    support_message("FMA",         [rdi].GetFMA())
    support_message("FSGSBASE",    [rdi].GetFSGSBASE())
    support_message("FXSR",        [rdi].GetFXSR())
    support_message("HLE",         [rdi].GetHLE())
    support_message("INVPCID",     [rdi].GetINVPCID())
    support_message("LAHF",        [rdi].GetLAHF())
    support_message("LZCNT",       [rdi].GetLZCNT())
    support_message("MMX",         [rdi].GetMMX())
    support_message("MMXEXT",      [rdi].GetMMXEXT())
    support_message("MONITOR",     [rdi].GetMONITOR())
    support_message("MOVBE",       [rdi].GetMOVBE())
    support_message("MSR",         [rdi].GetMSR())
    support_message("OSXSAVE",     [rdi].GetOSXSAVE())
    support_message("PCLMULQDQ",   [rdi].GetPCLMULQDQ())
    support_message("POPCNT",      [rdi].GetPOPCNT())
    support_message("PREFETCHWT1", [rdi].GetPREFETCHWT1())
    support_message("RDRAND",      [rdi].GetRDRAND())
    support_message("RDSEED",      [rdi].GetRDSEED())
    support_message("RDTSCP",      [rdi].GetRDTSCP())
    support_message("RTM",         [rdi].GetRTM())
    support_message("SEP",         [rdi].GetSEP())
    support_message("SHA",         [rdi].GetSHA())
    support_message("SSE",         [rdi].GetSSE())
    support_message("SSE2",        [rdi].GetSSE2())
    support_message("SSE3",        [rdi].GetSSE3())
    support_message("SSE4.1",      [rdi].GetSSE41())
    support_message("SSE4.2",      [rdi].GetSSE42())
    support_message("SSE4a",       [rdi].GetSSE4a())
    support_message("SSSE3",       [rdi].GetSSSE3())
    support_message("SYSCALL",     [rdi].GetSYSCALL())
    support_message("TBM",         [rdi].GetTBM())
    support_message("XOP",         [rdi].GetXOP())
    support_message("XSAVE",       [rdi].GetXSAVE())
    [rdi].Release()
    ret

main endp

    end main