News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Bug report: masm32 library's Alloc

Started by LimoDriver, March 29, 2007, 05:44:59 PM

Previous topic - Next topic

EduardoS

I'm not a COM with assembly specialist, so, i have a question...
Would it work?

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
align 16
Alloc proc public cb:DWORD
mov eax, Alloc_pIMalloc
test eax, eax
jnz haveptr
invoke CoGetMalloc,1 , ADDR Alloc_pIMalloc
test eax, eax
jnz saveptr
ret 8; if it jumps on eax != 0 eax must be zero here.
saveptr:
mov Alloc_pIMalloc, eax
haveptr:
mov ecx, [eax]
push [esp]; the return address
mov [esp+4], eax; pointer to the interface, a good pratice.
jmp [ecx]+12; let it do the rest
Alloc endp
OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF

LimoDriver

Accepting suggestions!


; #########################################################################

;    +=============================+=====++
;    |     Written by Ernie Murphy | '00 ||
;    \     Tweaked by Limo Driver  | '07 ||
;     ^============================+=====++

; #########################################################################

    .386
    .model flat, stdcall  ; 32 bit memory model
    option casemap : none ; case sensitive

; #########################################################################

    include     \masm32\include\ole32.inc
    includelib  \masm32\lib\ole32.lib

; #########################################################################

    IMalloc         PROTO
    Alloc           PROTO :DWORD
    Realloc         PROTO :DWORD, :DWORD
    GetSize         PROTO :DWORD
    Free            PROTO :DWORD
    DidAlloc        PROTO :DWORD
    HeapMinimize    PROTO

; #########################################################################
   
    IMallocProc MACRO procName, procOffset, paramCount
    ;{
        if paramCount eq 0
        ;{
            procName PROC public
        ;}
        elseif paramCount eq 1
        ;{
            procName PROC public p1 : DWORD
        ;}
        elseif paramCount eq 2
        ;{
            procName PROC public p1 : DWORD, p2 : DWORD
        ;}
        endif
        ;{
            invoke  IMalloc
           
            .IF eax
            ;{
                if paramCount gt 1
                ;{
                    push    DWORD PTR [ p2 ]
                ;}
                endif

                if paramCount gt 0
                ;{
                    push    DWORD PTR [ p1 ]
                ;}
                endif
           
                push    eax
                mov     eax, [ eax ]
                call    DWORD PTR [ eax + procOffset ]
            ;}
            .ENDIF
       
            ret
        ;}
        procName ENDP
    ;}
    ENDM
       
; #########################################################################

    .data

    Alloc_pIMalloc dd 0

    .code

; #########################################################################

    ;--------------------------------------------------------------------------
    ; IMalloc returns the interface pointer to the memory allocator.
    ; This interface pointer cannot be used from a remote process -
    ; each process must have its own allocator.
    ;
    ; If an error occured while fetching the interface pointer,
    ; IMalloc returns NULL.
    ;
    ; EXAMPLE:
    ; invoke IMalloc ; returns interface pointer in eax
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMalloc proc public
    ;{
        lea     ecx, [ Alloc_pIMalloc ]
        mov     eax, [ ecx ]

        .IF eax
        ;{
            ret
        ;}
        .ENDIF

    @@: invoke  CoGetMalloc, 1, ecx
        or      eax, eax
        jz      IMalloc
        ;{
            xor eax, eax
            ret
        ;}
    ;}
    IMalloc endp

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The Alloc function uses the default allocator to allocate a memory block
    ; in the same way that malloc does. The initial contents of the returned
    ; memory block are undefined – there is no guarantee that the block has
    ; been initialized. The allocated block may be larger than cb bytes because
    ; of the space required for alignment and for maintenance information.
    ;
    ; If cb is zero, Alloc allocates a zero-length item and returns a valid
    ; pointer to that item. If there is insufficient memory available, Alloc
    ; returns NULL.
    ;
    ; Applications should always check the return value from this method, even
    ; when requesting small amounts of memory, because there is no guarantee
    ; the memory will be allocated.
    ;
    ; EXAMPLE:
    ; invoke Alloc, 128 ; allocates 128 bytes, returns memory pointer in eax
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc Alloc,        12, 1

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The Realloc function changes the size of a memory block allocated by
    ; a previous call to Alloc or Realloc, in the same way that realloc does.
    ;
    ; The pv argument points to the beginning of the memory block.
    ; If pv is NULL, Realloc allocates a new memory block in the same way as
    ; the Alloc function. If pv is not NULL, it should be a pointer returned by
    ; a prior call to Alloc or Realloc.
    ;
    ; The cb argument specifies the size (in bytes) of the new block. The
    ; contents of the block are unchanged up to the shorter of the new and old
    ; sizes, although the new block can be in a different location. Because the
    ; new block can be in a different memory location, the pointer returned by
    ; Realloc is not guaranteed to be the pointer passed through the pv argument.
    ;
    ; If pv is not NULL and cb is zero, then the memory pointed to by pv is
    ; freed, with a return value of NULL.
    ;
    ; If pv is not NULL, cb is a valid size, and there was not enough memory
    ; available to expand the block to the given size, Realloc will return NULL,
    ; and the original block will remain unchanged.
    ;
    ; EXAMPLE:
    ; invoke Realloc, pv, 256 ; reallocates the memory at pv, pointer in eax
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc Realloc,      16, 2

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The Free function, using the default OLE allocator, frees a block of memory
    ; previously allocated through a call to the Alloc or Realloc function.
    ;
    ; The number of bytes freed equals the number of bytes that were originally
    ; allocated or reallocated. After the call, the memory block pointed to by
    ; pv is invalid and can no longer be used.
    ;
    ; The pv parameter can be NULL. If so, this method has no effect.
    ;
    ; EXAMPLE:
    ; invoke Free, pv ; frees the memory at pv, doesn't return a value (void)
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc Free,         20, 1

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The GetSize function returns the size of a memory block allocated by a
    ; previous call to Alloc or Realloc. If pv is a NULL pointer, the return
    ; value is -1.
    ;
    ; The size returned is the actual size of the allocation, which may be
    ; greater than the size requested when the allocation was made due to
    ; space required for alignment and for maintenance information.
    ;
    ; EXAMPLE:
    ; invoke GetSize, pv ; returns the memory block size (in bytes) in eax
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc GetSize,      24, 1

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The DidAlloc function is useful if a application is using multiple allocations and
    ; needs to know whether a previously allocated block of memory was allocated by a
    ; particular allocation.
    ;
    ; Return values can be 1, 0, or -1, signifying the following:
    ;
    ;  1 - The memory block was allocated by this IMalloc instance;
    ;  0 - The memory block was not allocated by this IMalloc instance;
    ; -1 - DidAlloc is unable to determine whether or not it allocated the memory block.
    ;
    ; If DidAlloc has been called with a NULL pointer, it will return -1.
    ;
    ; EXAMPLE:
    ; invoke DidAlloc, pv ; pv is a memory pointer, return value in eax
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc DidAlloc,     28, 1

; #########################################################################

    ;--------------------------------------------------------------------------
    ; The HeapMinimize function minimizes the heap as much as possible by
    ; releasing unused memory to the operating system, coalescing adjacent
    ; free blocks and committing free pages.
    ;
    ; Calling HeapMinimize is useful when an application has been running for
    ; some time and the heap may be fragmented.
    ;
    ; EXAMPLE:
    ; invoke HeapMinimize ; releases unused memory to the operating system
    ;
    ; Uses: eax, ecx, edx
    ;--------------------------------------------------------------------------

    IMallocProc HeapMinimize, 32, 0

; #########################################################################

end

LimoDriver

You have an error in you code...

Quote from: EduardoS on March 31, 2007, 11:57:18 PM

invoke CoGetMalloc,1 , ADDR Alloc_pIMalloc
test eax, eax
jz saveptr ; was jnz


Otherwise it *should* work, but it's highly fragile.
Stack frames are your friend, especially when evoking god knows who's API :P

LimoDriver

This part just looks ugly:


        if paramCount eq 0
        ;{
            procName PROC public
        ;}
        elseif paramCount eq 1
        ;{
            procName PROC public p1 : DWORD
        ;}
        elseif paramCount eq 2
        ;{
            procName PROC public p1 : DWORD, p2 : DWORD
        ;}
        endif


But since I never wrote macros before, I don't know how to set it up properly...
I tried something like


        procName PROC public       

        if paramCount gt 0
            p1 : DWORD
        endif

        if paramCount gt 1
            , p2 : DWORD
        endif


...and tons of other variations, but all failed... :(

EduardoS

Quote from: LimoDriver on April 01, 2007, 12:46:29 AM
You have an error in you code...
I was expecting receiving an address as result or null if fail, but this funcion returns a HRESULT and store the value in retval. It needs more changes than the jnz/jz.
Thank you for pointing the wrong line.
I hate COM.

The fix...

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
align 16
Alloc proc public cb:DWORD
mov eax, Alloc_pIMalloc
test eax, eax
jnz haveptr
invoke CoGetMalloc,1 , ADDR Alloc_pIMalloc
test eax, eax
jz saveptr
xor eax, eax; not zero...
mov Alloc_pIMalloc, eax; can i trust the previous function call will leave it zero?
ret 8
saveptr:
mov eax, Alloc_pIMalloc; its already saved, now get it...
haveptr:
mov ecx, [eax]
push [esp]; the return address
mov [esp+4], eax; pointer to the interface, a good pratice.
jmp [ecx]+12; let it do the rest
Alloc endp
OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF

MichaelW

It would appear that the method used here is more complex than necessary.

MSDN2: CoTaskMemAlloc
eschew obfuscation

LimoDriver

I disagree... The code I posted on the 1st of April :P, although a bit more complex (it's what... the macro expands to...  6 lines of assemler?), implements each of these functions; GetIMallocInterface, Alloc, Realloc, GetSize, Free, DidAlloc, HeapMinimize...

And you can even add more functions, like the increase/decrease reference count, which I didn't implement, but could be done in as little as just one line call to the macro!

The difference between these two implementations is that the program will have to retrieve the procedure address for CoTaskMemAlloc, CoTaskMemFree, and so on for each of these functions, while the implementation I posted only needs one: the retrieval of the CoGetMalloc interface pointer. This means less DLL imports and smaller binary size - while retaining the same speed and functionality.

MichaelW

To clarify, the CoGetMalloc method is more complex than necessary to implement the functions that Ernie Murphy originally implemented. Had he chosen to use CoTaskMemAlloc, etc, the bug probably would not have existed.

eschew obfuscation

LimoDriver

Quote
Had he chosen to use CoTaskMemAlloc, etc, the bug probably would not have existed.

Well, that is true, but think about it - if Ernie used CoTaskMemAlloc instead of going through the CoGetMalloc bit - what would be the point of placing that single call into the masm32 library :lol

MichaelW

QuoteWell, that is true, but think about it - if Ernie used CoTaskMemAlloc instead of going through the CoGetMalloc bit - what would be the point of placing that single call into the masm32 library

I don't understand. The functions are not in the masm32 library, but in the ole32.dll import library (or at least they are in the current version). Calling CoTaskMemAlloc, CoTaskMemFree, or CoTaskMemRealloc would involve no significant overhead beyond that required for CoGetMalloc. Perhaps I should add that my post was in no way meant as a criticism, what you have ended up with here is very good. I just happened to come across the CoTask_ functions, and thought it was interesting that a simpler method is available, and has been since Windows 95/NT 3.1.
eschew obfuscation

LimoDriver

Quote from: MichaelW
I don't understand.

Lemme try to rephrase - when I said "what would be the point of placing that single call into the masm32 library", I meant that there isn't a point to create a new set of Alloc/Free/Realloc and so on if you're just going to be using them to call another procedure which does the exact same thing, like

Alloc proc public cb : DWORD
;{
    invoke CoTaskMemAlloc, cb
    ret
;}
Alloc endp

Because of this, we can safely conclude that Ernie would not have created the above mentioned procedures, because they would be redundant - and wouldn't be worth the effort, as it wouldn't introduce anything new to the community.

Quote from: MichaelW
Calling CoTaskMemAlloc, CoTaskMemFree, or CoTaskMemRealloc would involve no significant overhead beyond that required for CoGetMalloc.

Of, course, I wouldn't call it "significant", but it is true - each and every one of these "CoTaskMemAlloc', 'CoTaskMemFree', or 'CoTaskMemRealloc' will need to be imported from ole32.dll, and thus the release application will need to have links to each and every of these in the imports section ... As I said this isn't 'significant' but it's always nicer to have the exact same functionality by importing just one of these functions - 'CoGetMalloc'.

BTW, although you said it isn't -  I'm never afraid of criticism, especially if it's something I can improve on.
Thanx for the kind words.

EduardoS

Not contesting the drawback of having 2 more links int the import section but...
The IMalloc interface isn't there for some polymorphism?
example: When creating a ISPI Filter you can use a special function to alocate memory, the address this function is passed as an item of parameter pfc of the HttpFilterProc. A COM library can do something like this, receiving a IMalloc as pointer wich may point to the "normal" interface or to a custom one.

Sorry for my bad english...

hutch--

Guys,

I have moved this topic to the Laboratory as it seems to more about algo design than a simple fix of a faulty algo. I would appreciate knowing what the end result will be as Limo has shown there is a problem with Ernie's original and I need a complete working replacement that has the same interface so it can be slotted back into the masm32 library without breaking existing code.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

zooba

Quote from: EduardoS on April 07, 2007, 01:34:07 AM
The IMalloc interface isn't there for some polymorphism?

Mostly correct. It's there so some objects can provide their own memory allocation routines.

Since the Malloc function only uses the default allocator, CoTaskMemAlloc and CoGetMalloc->Alloc will give identical results.

I agree that there is little point in having a function which simply calls another function, though the benefit in this case is backwards compatibility. New code could entirely avoid the call to the MASM library and call CoTaskMemAlloc directly, but removing the function will break old code that uses it.

AFAICT, this is the most efficient replacement:


Malloc PROC cb:DWORD
    jmp CoTaskMemAlloc
Malloc ENDP


Alternatively, remove the function and use Malloc EQU CoTaskMemAlloc, though if someone's code expects the actual function to exist (to get a pointer to it) this one may not work.

Cheers,

Zooba :U

MichaelW

The added functions GetSize, HeapMinimize, etc are worthwhile IMO, and since AFAIK there are no CoTask_ equivalents, and assuming the CoGetMalloc interface is in fact now working correctly, I think it should be retained.
eschew obfuscation