options.DebugHeap = 0
module "Heap manager"
include "%TargetOS%/heapmgr.asm"
ERR_FASMLIB = 0 ;Internal FASMLIB assertion failed. This is bug in FASMLIB. Please report
ERR_UNKNOWN = 1 ;Unknown error
ERR_MODULE_NOT_INITIALIZED = 3 ;(internal) Module not initialized
ERR_OUT_OF_MEM = 4 ;Out of memory
ERR_HEAP_CORRUPT = 5 ;(internal) Heap corrupt
ERR_MEMORY_LEAK = 6 ;(internal) Memory leak
ERR_INVALID_POINTER = 7 ;(internal) Invalid pointer
ERR_OUT_OF_RANGE = 12 ;Out of range
ERR_ZERO_SIZE = 14 ;(internal) Zero size
;interface of HEAPBLOCK and heap manager constants
struct HEAPBLOCK
.sign dw 'HB' ;signature of block. must be 'HB' (heap block)
.used db ? ;0 = block is unused, 1 = block is used
.last db ? ;0 = block isn't last, 1 = block is last
;only valid for used blocks
.size dd ? ;real size of this block (not including header)
.prev dd ? ;pointer to previous block. 0=this is first block
.asize dd ? ;allocated size of this block (can be smaller than real size)
ends
;heap size alignment (default = 4KB)
;it's value must be power of two!!!
match =HEAP_SIZE_ALIGN, HEAP_SIZE_ALIGN {
HEAP_SIZE_ALIGN equ 4096
}
;intial heap size (default = 16KB)
;must be multiple of HEAP_SIZE_ALIGN
match =HEAP_INIT_SIZE, HEAP_INIT_SIZE {
HEAP_INIT_SIZE equ 16384
}
;sizes of block will be multiples of this value (default = 8 bytes)
;any smaller free block will be left out as padding
;it's value must be power of two!!!
match =HEAP_BLOCK_ALIGN, HEAP_BLOCK_ALIGN {
HEAP_BLOCK_ALIGN equ 16
}
;minimal size of block, when it is split from block whose size shrinked
;must be multiple of HEAP_BLOCK_ALIGN
match =HEAP_MIN_SPLIT_SIZE, HEAP_MIN_SPLIT_SIZE {
HEAP_MIN_SPLIT_SIZE equ HEAP_BLOCK_ALIGN
}
iglobal
var mem.initialized = 0 ;0 = module not initialized, 1=initialized
endg
uglobal
var mem.heap.start = ? ;pointer to beginning of heap
var mem.heap.size = ? ;size of heap
var mem.alloc_count = ? ;number of allocations (to discover memory leak)
var mem.heap_corrupt = ? ;0 = okay, 1 = heap corrupt
if used mem.access_mutex
align 4
mem.access_mutex TMutex
end if
endg
if used GetMem
;============================================================================
; mem.init
; desc: initalizes mem module (and it's heap manager)
; args: none
; ret: CF set on error
; note: initialization is required for allocation
; error: ERR_UNKNOWN - platform-specific error
; +mem.alloc_heap (in platform-dependant part of mem module)
;============================================================================
initialize mem.init
begin
;if module is already initialized, just return
cmp [mem.initialized], 0
jne .rnc
; call platform-dependant heap allocation
stdcall mem.alloc_heap, HEAP_INIT_SIZE
jc .rc
; save heap info
mov [mem.heap.start], eax
mov [mem.heap.size], HEAP_INIT_SIZE
; create first heap block
mov [eax + HEAPBLOCK.sign], 'HB'
mov [eax + HEAPBLOCK.size], HEAP_INIT_SIZE - sizeof.HEAPBLOCK
mov [eax + HEAPBLOCK.prev], 0
mov [eax + HEAPBLOCK.last], 1
mov [eax + HEAPBLOCK.used], 0
; initialized
mov [mem.alloc_count], 0
mov [mem.heap_corrupt], 0
mov [mem.initialized], 1
if options.Threads
stdcall MutexCreate, 0, mem.access_mutex
stdcall MutexRelease, mem.access_mutex
end if
.rnc: clc
.r: return
.rc: stc
return
endp
;============================================================================
; mem.uninit
; desc: uninitalizes mem module (and it's heap manager)
; args: none
; ret: CF set on error
; error: ERR_UNKNOWN - platform-specific error
; ERR_MEMORY_LEAK - you didn't free some allocated block
; +mem.free_heap (in platform-dependant part of mem module)
;============================================================================
finalize mem.uninit
begin
if options.Threads
stdcall WaitForMutex, mem.access_mutex, -1
stdcall MutexDestroy, mem.access_mutex
end if
;is module initialized?
cmp [mem.initialized], 0
jz .error_not_initialized
cmp [mem.heap_corrupt],0
jne .dont_test_heap
; check heap
stdcall __mem.test_heap
jc .rc
.dont_test_heap:
; call platform-dependant free heap
stdcall mem.free_heap, [mem.heap.start], [mem.heap.size]
jc .rc
; uninitialized
mov [mem.initialized], 0
; return error if there is some memory leak
cmp [mem.alloc_count], 0
ja .error_memory_leak
clc
return
.rc: mov [mem.initialized], 0
stc
return
.error_memory_leak:
mov eax, ERR_MEMORY_LEAK
jmp .rc
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
endp
end if
uglobal
if used LogMemory
align 4
__LogMemoryMutex TMutex
end if
endg
if used LogMemory
initialize InitLogMemory
begin
stdcall MutexCreate, 0, __LogMemoryMutex
stdcall MutexRelease, __LogMemoryMutex
return
endp
finalize FreeLogMemory
begin
stdcall MutexDestroy, __LogMemoryMutex
return
endp
end if
proc LogMemory, .pmsg, .address, .argcount
begin
pushad
mov esi, [ebp] ; caller stack frame.
mov edi, [esi] ; one level back.
cmp edi, ebp
ja .ediok
mov edi, esi
.search:
add edi, 4
cmp [edi], esp
jb .search
.ediok:
stdcall WaitForMutex, __LogMemoryMutex, -1
stdcall Output, [.pmsg]
stdcall OutputNumber, [.address], 16, 8
stdcall Output, '; Call from: $'
stdcall OutputNumber, [esi+4], 16, 8
stdcall Output, <',',' $'>
stdcall OutputNumber, [edi+4], 16, 8
xor ecx, ecx
inc ecx
.argloop:
stdcall Output, '; Arg('
stdcall OutputNumber, ecx, 10, 2
stdcall Output, ') = $'
stdcall OutputNumber, [esi+4*ecx+4], 16, 8
inc ecx
cmp ecx, [.argcount]
jbe .argloop
stdcall Output, .cCRLF
stdcall MutexRelease, __LogMemoryMutex
popad
return
.cCRLF db 13, 10, 0
endp
body GetMem
begin
if options.Threads
stdcall WaitForMutex, mem.access_mutex, -1
jc .errmutex
end if
stdcall mem.alloc0, [.size]
if options.Threads
pushf
stdcall MutexRelease, mem.access_mutex
popf
end if
jc .error
if options.DebugHeap
stdcall LogMemory, 'Allocate:', eax, 1
clc
end if
return
.error:
if options.DebugHeap
stdcall LogMemory, 'Allocate error:', eax, 1
stc
end if
xor eax, eax
return
.errmutex:
if options.DebugHeap
stdcall LogMemory, 'Mutex timeout:', eax, 1
stc
end if
jmp .error
endp
body FreeMem
begin
push eax
if options.Threads
stdcall WaitForMutex, mem.access_mutex, -1
jc .finish
end if
stdcall mem.free, [.ptr]
if options.Threads
stdcall MutexRelease, mem.access_mutex
end if
if options.DebugHeap
jnc .ok
stdcall LogMemory, 'Free error:', [.ptr], 1
stc
jmp .finish
.ok:
stdcall LogMemory, 'Free:', [.ptr], 1
clc
end if
.finish:
pop eax
return
endp
body ResizeMem
begin
if options.DebugHeap
stdcall LogMemory, 'Reallocate attempt:', [.ptr], 2
end if
if options.Threads
stdcall WaitForMutex, mem.access_mutex, -1
jc .error
end if
stdcall mem.realloc0, [.ptr], [.newsize]
if options.Threads
pushf
stdcall MutexRelease, mem.access_mutex
popf
end if
jnc .finish
.error:
mov eax, [.ptr]
if options.DebugHeap
jnc .ok
stdcall LogMemory, 'Reallocate failed:', eax, 2
stc
jmp .finish
.ok:
stdcall LogMemory, 'Reallocate success:', eax, 2
clc
end if
.finish:
return
endp
;; @name mem.alloc0
;; @desc
;; Allocates block of memory, and fills it with zeroes.
;; @arg size
;; Size of memory to allocate
;; @ret
;; CF set on error, otherwise
;; EAX = pointer to beginning of allocated memory block
;; @err ERR_OUT_OF_MEM
;; Not enough memory
;; @err ERR_ZERO_SIZE
;; size = 0
;; @err ERR_OUT_OF_RANGE
;; size is negative (or larger than 7FFFFFFFh if viewed as unsigned)
;; @err ERR_HEAP_CORRUPT
;; {heap:Heap is corrupted}. You was writing to memory outside of allocated block.
;; @err ERR_MODULE_NOT_INITIALIZED
;; Module is not initialized. Initialize module with {mem.init} to use this procedure.
;; @err ERR_UNKNOWN
;; System returned error that wasn't translated to FASMLIB error.
;; You can {contact} author about this, and translation can be added.
;; @warn Don't forget to release allocated block with {mem.free}!
;; @note returned pointer must be always >= 10000h
;; @note Returned pointer is always >= 10000h to make it easily distinguishable from handles.
;; @note Maximal allowed size is 7FFFFFFFh, to catch errors with negative values.
;; @note Altough returned block may have bigger size than requested on some implementations,
;; don't use this additional memory.
;; It is not zeroes, an {mem.realloc} doesn't preserve this memory.
;; @note Memory management must be {mem.init:initialized} for this procedure to work.
;; @note This is platform-specific procedure
proc mem.alloc0, .size
begin
push ebx
;error if size=0
cmp [.size], 0
je .error_size_zero
;allocate
stdcall mem.alloc, [.size]
jc .rc
mov ebx, eax
;zero memory
stdcall mem.fill, ebx, [.size], 0
jc .rc
mov eax, ebx
.rnc: clc
.r: pop ebx
return
.rc: stc
int3
jmp .r
.error_size_zero:
mov eax, ERR_ZERO_SIZE
jmp .rc
endp
;============================================================================
;; @name mem.realloc0
;; @desc
;; Rellocates (resizes) block of memory.
;; Block can be moved during this process, so you need to update pointer(s) to it.
;; Contents of block are preserved.
;; If block is enlarging, contents of added space are filled with zeroes.
;; @arg blockptr
;; Pointer to block to relocate.
;; This must be same pointer as returned from allocation function, pointer into block is not enough.
;; @arg newsize
;; New size of block, in bytes
;; @ret
;; CF set on error, orherwise
;; EAX = pointer to reallocated memory block
;; @err ERR_INVALID_POINTER
;; Blockptr is not a valid allocated block pointer.
;; This could also mean {heap:heap corruption}, these two cases cannot be properly distinguished.
;; @err ERR_OUT_OF_MEM
;; Not enough memory
;; @err ERR_ZERO_SIZE
;; size = 0
;; @err ERR_OUT_OF_RANGE
;; size is negative (or larger than 7FFFFFFFh if viewed as unsigned)
;; @err ERR_HEAP_CORRUPT
;; {heap:Heap is corrupted}. You was writing to memory outside of allocated block.
;; @err ERR_MODULE_NOT_INITIALIZED
;; Module is not initialized. Initialize module with {mem.init} to use this procedure.
;; @err ERR_UNKNOWN
;; System returned error that wasn't translated to FASMLIB error.
;; You can {contact} author about this, and translation can be added.
;; @dep mem.realloc_heap
;; Only on platforms where FASMLIB heap manager is used.
;; @warn
;; Block can be moved during realocation! If you had any pointers to block,
;; they become invalid (so rather prefer using indexes to allocated blocks).
;; If you saved block pointer in variable, you need to rewrite it with
;; returned value.
;; @note Returned pointer is always >= 10000h to make it easily distinguishable from handles.
;; @note Maximal allowed newsize is 7FFFFFFFh, to catch errors with negative values.
;; @note Altough returned block may have bigger size than requested on some implementations,
;; don't use this additional memory. {mem.realloc} doesn't preserve this memory.
;; @note Memory management must be {mem.init:initialized} for this procedure to work.
;; @note This is platform-specific procedure
proc mem.realloc0, .blockptr, .newsize
begin
push ebx esi
;error if newsize=0
cmp [.newsize], 0
je .error_newsize_zero
;ESI = save old block size
stdcall GetMemSize, [.blockptr]
mov esi, eax
;reallocate
stdcall mem.realloc, [.blockptr], [.newsize]
jc .r
mov ebx, eax
;number of bytes to zero = newsize-oldsize
mov eax, [.newsize]
sub eax, esi
;zero new memory
pushd 0
pushd eax
lea eax, [ebx+esi]
pushd eax
call mem.fill
jc .r
mov eax, ebx
.rnc: clc
.r: pop esi ebx
return
.rc: stc
int3
jmp .r
.error_newsize_zero:
mov eax, ERR_ZERO_SIZE
jmp .rc
endp
;============================================================================
; mem.alloc
; desc: allocates <size> bytes of memory
; args: size - size of memory to allocate
; ret: CF set on error, otherwise
; eax = pointer to beginning of allocated memory block
; note: - returned pointer must be always >= 10000h
; - contents of allocated memory are unknown
; (use alloc0 to have block zeroed)
; - don't forget to mem.free allocated memory!
; - maximal allowed size if 7FFFFFFFh, to catch errors with negative value
; error: ERR_OUT_OF_MEM - out of memory
; ERR_ZERO_SIZE - size=0
; ERR_OUT_OF_RANGE - size >= 80000000h
; ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
; ERR_UNKNOWN
; ERR_MODULE_NOT_INITIALIZED
; mem.heap_alloc
;============================================================================
proc mem.alloc, .size
begin
push ebx ecx edx esi edi
;is module initialized?
cmp [mem.initialized], 0
je .error_not_initialized
;was heap corruption detected?
cmp [mem.heap_corrupt], 0
jne .error_heap_corrupt
;error if size=0
cmp [.size], 0
je .error_size_zero
jl .error_size_too_big
;ECX = size to allocate, aligned to HEAP_BLOCK_ALIGN
mov ecx, [.size]
add ecx, HEAP_BLOCK_ALIGN-1
and ecx, not (HEAP_BLOCK_ALIGN-1)
;EBX = pointer to first HEAP block
mov ebx, [mem.heap.start]
;find unused block with enough space
.find_block:
;check if block is okay
cmp [ebx + HEAPBLOCK.sign], 'HB'
jne .error_heap_corrupt
;is the block unused?
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
je .try_next_block
;is the block large enough?
cmp [ebx + HEAPBLOCK.size], ecx
jae .block_found
.try_next_block:
;is there some next block?
cmp [ebx + HEAPBLOCK.last], 1
ja .error_heap_corrupt
je .no_block_found
;go to next block
add ebx, [ebx + HEAPBLOCK.size]
add ebx, sizeof.HEAPBLOCK
jmp .find_block
;no suitable block found. we need to enlarge heap
;EBX = pointer to last block
.no_block_found:
.resize_heap:
; increasize heap size by needed size + header size aligned to HEAP_SIZE_ALIGN
mov edx, ecx
add edx, sizeof.HEAPBLOCK
add edx, HEAP_SIZE_ALIGN-1
and edx, not (HEAP_SIZE_ALIGN-1)
add edx, [mem.heap.size]
stdcall mem.realloc_heap, [mem.heap.start], edx, [mem.heap.size]
mov [mem.heap_corrupt], 1 ;if this call failed, we are screwed
jc .rc
mov [mem.heap_corrupt], 0
xchg [mem.heap.size], edx
;edx = size of added block
mov eax, [mem.heap.size]
sub eax, edx
mov edx, eax
;if last block was free, we append new space to it
cmp [ebx + HEAPBLOCK.used], 1
jne .add_new_space_to_last_block
;last block is used, but first we need to check if we can take enough unused space from last block
;if there is at least sizeof.HEAPBLOCK unused bytes after alignation, we take them
mov esi, [ebx + HEAPBLOCK.asize]
add esi, HEAP_BLOCK_ALIGN-1
and esi, not (HEAP_BLOCK_ALIGN-1)
mov eax, [ebx + HEAPBLOCK.size]
sub eax, esi
jc .create_block_in_added_space ;if size is not even aligned. shouldn't happen but anyway...
cmp eax, sizeof.HEAPBLOCK
jb .create_block_in_added_space
;EBX = last block, ESI = new size of EBX block
.take_space_from_last_block:
;set up EBX
mov [ebx + HEAPBLOCK.last], 0
mov [ebx + HEAPBLOCK.size], esi
;set up ESI
lea esi, [ebx + esi + sizeof.HEAPBLOCK]
mov [esi + HEAPBLOCK.sign], 'HB'
mov [esi + HEAPBLOCK.last], 1
mov [esi + HEAPBLOCK.prev], ebx
mov eax, [mem.heap.start]
add eax, [mem.heap.size]
sub eax, esi
sub eax, sizeof.HEAPBLOCK
mov [esi + HEAPBLOCK.size], eax ;size = up to end of block
mov [esi + HEAPBLOCK.used], 0
mov ebx, esi
jmp .block_found
.create_block_in_added_space:
;esi = new block
lea esi, [ebx + sizeof.HEAPBLOCK]
add esi, [ebx + HEAPBLOCK.size]
;set up the block
mov [ebx + HEAPBLOCK.last], 0
mov [esi + HEAPBLOCK.sign], 'HB'
mov [esi + HEAPBLOCK.last], 1
mov [esi + HEAPBLOCK.prev], ebx
sub edx, sizeof.HEAPBLOCK
mov [esi + HEAPBLOCK.size], edx
mov [esi + HEAPBLOCK.used], 0
mov ebx, esi
jmp .block_found
.add_new_space_to_last_block:
add [ebx + HEAPBLOCK.size], edx
;suitable block found, we will use it for allocation
;EBX = pointer to block, ecx=size to allocate
.block_found:
;EDI = beginning of new block aligned
lea edi, [ebx + sizeof.HEAPBLOCK + ecx]
;EDX = space left in block
mov edx, [ebx + HEAPBLOCK.size]
sub edx, ecx
jz .set_block ;if zero, we can skip it
;ESI = next block
cmp [ebx + HEAPBLOCK.last], 1
ja .error_heap_corrupt
je .is_last_block
lea esi, [ebx + sizeof.HEAPBLOCK]
add esi, [ebx + HEAPBLOCK.size]
jmp .not_last_block
.is_last_block:
sub edx, sizeof.HEAPBLOCK
;if following space is not big enough, leave it as padding
cmp edx, HEAP_BLOCK_ALIGN
jb .set_block
;there is enough space, create new block (EDI)
mov [ebx + HEAPBLOCK.last], 0
mov [ebx + HEAPBLOCK.size], ecx
mov [edi + HEAPBLOCK.sign], 'HB'
mov [edi + HEAPBLOCK.used], 0
mov [edi + HEAPBLOCK.last], 1
mov [edi + HEAPBLOCK.size], edx
mov [edi + HEAPBLOCK.prev], ebx
jmp .set_block
.not_last_block:
;if next block is unused, move it to rest of buffer append space
cmp [esi + HEAPBLOCK.used], 0
jne .next_block_used
.next_block_unused:
;no moving if gained space is <= sizeof.HEAPBLOCK (could case problems)
cmp edx, sizeof.HEAPBLOCK
jbe .set_block
;next block is unused, move it to EDI
mov [ebx + HEAPBLOCK.size], ecx
mov [esi + HEAPBLOCK.sign], 0 ;clean sign of the next block, to prevent identifying it as valid block in future
mov [edi + HEAPBLOCK.sign], 'HB'
mov [edi + HEAPBLOCK.used], 0 ;newblock.used = 0
mov al, [esi + HEAPBLOCK.last]
mov [edi + HEAPBLOCK.last], al
mov eax, [esi + HEAPBLOCK.size]
add eax, [edx - sizeof.HEAPBLOCK]
add [edi + HEAPBLOCK.size], eax ;newblock.size = newblock.size + nextblock.size - nextblock_header_size
mov [edi + HEAPBLOCK.prev], ebx ;newblock.prev = previous block (EBX)
mov [eax + HEAPBLOCK.prev], edi ;nextblock.next.prev = newblock
jmp .set_block
;following block (ESI) is used
.next_block_used:
sub edx, sizeof.HEAPBLOCK
;if remaining space is not big enough to create new block, leave it as padding
cmp edx, HEAP_BLOCK_ALIGN
jb .set_block
;it's big enough, so create new block
mov [ebx + HEAPBLOCK.size], ecx
mov [edi + HEAPBLOCK.sign], 'HB'
mov [edi + HEAPBLOCK.size], edx
mov [edi + HEAPBLOCK.prev], ebx
mov [edi + HEAPBLOCK.used], 0
mov [edi + HEAPBLOCK.last], 0
lea eax, [edi + edx + sizeof.HEAPBLOCK]
mov [eax + HEAPBLOCK.prev], edi ;ebx->next->prev = ebx->next
;set current block we are going to allocate
.set_block:
mov eax, [.size]
mov [ebx + HEAPBLOCK.asize], eax
mov [ebx + HEAPBLOCK.used], 1
;another block successfully allocated
inc [mem.alloc_count]
;return pointer to block
lea eax, [ebx + sizeof.HEAPBLOCK]
.rnc: clc
.r: pop edi esi edx ecx ebx
return
.rc:
DebugMsg "Error mem.alloc"
stc
jmp .r
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
.error_heap_corrupt:
int3
mov [mem.heap_corrupt], 1
mov eax, ERR_HEAP_CORRUPT
jmp .rc
.error_size_zero:
mov eax, ERR_ZERO_SIZE
jmp .rc
.error_size_too_big:
mov eax, ERR_OUT_OF_RANGE
jmp .rc
endp
;=============================================================================
; mem.free
; desc: deallocates memory block
; args: blockptr - pointer to memory block returned by mem.alloc
; ret: CF set on error
; note: after error, consider block as freed, don't use it anymore
; error: ERR_INVALID_POINTER - if blockptr isn't valid block pointer
; ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
; ERR_UNKNOWN
; ERR_MODULE_NOT_INITIALIZED
; ERR_FASMLIB
;=============================================================================
proc mem.free, .blockptr
begin
push eax ebx edi
;is module initialized?
cmp [mem.initialized], 0
je .error_not_initialized
;was heap corruption detected?
cmp [mem.heap_corrupt], 0
jne .error_heap_corrupt
;EBX = pointer to heap block
mov ebx, [.blockptr]
sub ebx, sizeof.HEAPBLOCK
;check if pointer lies inside heap
mov eax, ebx
sub eax, [mem.heap.start]
jb .error_bad_pointer
sub eax, [mem.heap.size]
jae .error_bad_pointer
;check block
cmp [ebx + HEAPBLOCK.sign], 'HB'
jne .error_bad_pointer
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
jb .error_bad_pointer
;free block
mov [ebx + HEAPBLOCK.used], 0
.check_following_block:
;EDI = following block (if any)
cmp [ebx + HEAPBLOCK.last], 1
ja .error_heap_corrupt
je .check_previous_block
lea edi, [ebx + sizeof.HEAPBLOCK]
add edi, [ebx + HEAPBLOCK.size]
;check following block
cmp [edi + HEAPBLOCK.sign], 'HB'
jne .error_heap_corrupt
cmp [edi + HEAPBLOCK.prev], ebx
jne .error_heap_corrupt
;if following block is unused, append it
cmp [edi + HEAPBLOCK.used], 1
ja .error_heap_corrupt
je .check_previous_block
;append block
mov [edi + HEAPBLOCK.sign], 0
mov eax, [edi + HEAPBLOCK.size]
add eax, sizeof.HEAPBLOCK
add [ebx + HEAPBLOCK.size], eax
mov al, [edi + HEAPBLOCK.last]
mov [ebx + HEAPBLOCK.last], al
.check_previous_block:
;EDI = previous block (if any)
mov edi,[ebx + HEAPBLOCK.prev]
test edi, edi
jz .done
;check EDI block
cmp [edi + HEAPBLOCK.sign], 'HB'
jne .error_heap_corrupt
cmp [edi + HEAPBLOCK.last], 0
jne .error_heap_corrupt
lea eax, [edi + sizeof.HEAPBLOCK]
add eax, [edi + HEAPBLOCK.size]
cmp eax, ebx
jne .error_heap_corrupt
;if EDI block is unused, append EBX block to to it
cmp [edi + HEAPBLOCK.used], 1
ja .error_heap_corrupt
je .done
;append block
mov [ebx + HEAPBLOCK.sign], 0
mov eax, [ebx + HEAPBLOCK.size]
add eax, sizeof.HEAPBLOCK
add [edi + HEAPBLOCK.size], eax
mov al, [ebx + HEAPBLOCK.last]
mov [edi + HEAPBLOCK.last], al
mov ebx, edi
.done:
;if this isn't last block, set prev of next block
cmp [ebx + HEAPBLOCK.last], 1
je @f
lea eax, [ebx + sizeof.HEAPBLOCK]
add eax, [ebx + HEAPBLOCK.size]
mov [eax + HEAPBLOCK.prev], ebx
@@:
;another block successfully released
dec [mem.alloc_count]
jc .error_alloc_count_overflow
;TODO - reduce heap size if ebx is last?
.rnc: clc
.r: pop edi ebx eax
return
.rc: stc
jmp .r
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
.error_heap_corrupt:
int3
mov [mem.heap_corrupt], 1
mov eax, ERR_HEAP_CORRUPT
jmp .rc
.error_bad_pointer:
mov eax, ERR_INVALID_POINTER
jmp .rc
.error_alloc_count_overflow:
mov eax, ERR_FASMLIB
jmp .rc
endp
;=============================================================================
; mem.realloc
; desc: reallocates memory block
; args: blockptr - pointer to memory block returned by mem.(re)alloc
; newsize - new size of memory block
; ret: CF set on error, orherwise
; eax = pointer to reallocated memory block
; note: returned pointer must be always >= 10000h
; - data in block are is modified (only data at the end of block is
; lost if newsize < previous size)
; - if block is enlarging, then contents of added memory are unknown
; (use realloc0 to have it zeroed)
; - block can move to other place in memory, so previous pointers into
; block become invalid (so rather use indexes in block than pointers)
; - maximal allowed size if 7FFFFFFFh, to catch errors with negative value
; - if you had pointer stored in variable, don't forget to restore it:
; libcall mem.realloc, [blockptr], [newsize]
; jc error
; mov [blockptr], eax ;without this, it's hard-to-find bug
; error: ERR_INVALID_POINTER - blockptr isn't a valid block pointer
; ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
; ERR_ZERO_SIZE - newsize = 0
; ERR_OUT_OF_RANGE - newsize >= 80000000h
; ERR_MODULE_NOT_INITIALIZED
; +mem.alloc
; +mem.free
;=============================================================================
proc mem.realloc, .blockptr, .newsize
begin
push ebx ecx edx esi edi
;is module initialized?
cmp [mem.initialized], 0
je .error_not_initialized
;was heap corruption detected?
cmp [mem.heap_corrupt], 0
jne .error_heap_corrupt
;error if newsize=0
cmp [.newsize], 0
je .error_newsize_zero
jl .error_newsize_too_big
;EBX = pointer to heap block
mov ebx, [.blockptr]
sub ebx, sizeof.HEAPBLOCK
;check if pointer lies inside heap
mov eax, ebx
sub eax, [mem.heap.start]
jb .error_bad_pointer
sub eax, [mem.heap.size]
jae .error_bad_pointer
;check block
cmp [ebx + HEAPBLOCK.sign], 'HB'
jne .error_bad_pointer
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
jb .error_bad_pointer
;if newsize fits into real size of block, just reset size
mov ecx, [.newsize]
cmp ecx, [ebx + HEAPBLOCK.size]
ja .current_block_too_small
mov [ebx + HEAPBLOCK.asize], ecx
jmp .done
.current_block_too_small:
;ECX = requested size of block aligned
add ecx, HEAP_BLOCK_ALIGN-1
and ecx, not (HEAP_BLOCK_ALIGN-1)
;EDX = required additional size
mov edx, ecx
sub edx, [ebx + HEAPBLOCK.size]
;if there is no following block, we must allocate
cmp [ebx + HEAPBLOCK.last], 1
je .allocate_new_block
ja .error_heap_corrupt
;ESI = following block
lea esi, [ebx + sizeof.HEAPBLOCK]
add esi, [ebx + HEAPBLOCK.size]
;check following block
cmp [esi + HEAPBLOCK.sign], 'HB'
jne .error_heap_corrupt
cmp [esi + HEAPBLOCK.prev], ebx
jne .error_heap_corrupt
;if following block is used, we must allocate
cmp [esi + HEAPBLOCK.used], 1
ja .error_heap_corrupt
je .allocate_new_block
;if following block is not big enough, we must allocate
mov eax, [esi + HEAPBLOCK.size]
add eax, sizeof.HEAPBLOCK
cmp edx, eax
ja .allocate_new_block
.append_following_block:
;EBX = our block, ESI=following block, EAX=size of new block (header included)
add [ebx + HEAPBLOCK.size], eax
mov eax, [.newsize]
mov [ebx + HEAPBLOCK.asize], eax
mov [esi + HEAPBLOCK.sign], 0 ;delete mark
;if next block was last, set this one as last
mov al, [esi + HEAPBLOCK.last]
mov [ebx + HEAPBLOCK.last], al
cmp al, 1
ja .error_heap_corrupt
je .block_created
;otherwise point nextblock.prev to ours block
add esi, [esi + HEAPBLOCK.size] ;move to next block
add esi, sizeof.HEAPBLOCK
mov [esi + HEAPBLOCK.prev], ebx
jmp .block_created
.allocate_new_block:
;allocate new block
mov ecx, [.newsize]
stdcall mem.alloc, ecx
jc .rc
;copy contents of old block into new block
mov edi, eax
lea esi, [ebx+sizeof.HEAPBLOCK]
stdcall mem.copy, edi, esi, [ebx+HEAPBLOCK.asize]
jc .rc
;free old block
stdcall mem.free, esi
jc .rc
;return new block
mov eax, edi
jmp .rnc
.block_created:
;now check whether to split unused part from created block
;ECX = aligned real size of block
mov ecx, [ebx + HEAPBLOCK.asize]
add ecx, HEAP_BLOCK_ALIGN-1
and ecx, not (HEAP_BLOCK_ALIGN-1)
;EDX = gained space
mov edx, [ebx + HEAPBLOCK.size]
sub edx, ecx
;if it's at least sizeof.HEAPBLOCK + HEAP_BLOCK_ALIGN, create new block
sub edx, sizeof.HEAPBLOCK
jb .done
cmp edx, HEAP_BLOCK_ALIGN
jb .done
;ESI = new block
mov [ebx + HEAPBLOCK.size], ecx
lea esi, [ebx + ecx + sizeof.HEAPBLOCK]
mov [esi + HEAPBLOCK.sign], 'HB'
mov [esi + HEAPBLOCK.used], 0
mov [esi + HEAPBLOCK.size], edx
mov [esi + HEAPBLOCK.prev], ebx
mov al, [ebx + HEAPBLOCK.last]
mov [ebx + HEAPBLOCK.last], 0
mov [esi + HEAPBLOCK.last], al
;if esi isn't last, set esi->next->prev = esi
cmp al, 1
ja .error_heap_corrupt
je @f
lea eax, [esi + edx + sizeof.HEAPBLOCK]
mov [eax + HEAPBLOCK.prev], esi
@@:
.done:
;return pointer
lea eax, [ebx + sizeof.HEAPBLOCK]
.rnc: clc
.r: pop edi esi edx ecx ebx
return
.rc: stc
jmp .r
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
.error_heap_corrupt:
int3
mov [mem.heap_corrupt], 1
mov eax, ERR_HEAP_CORRUPT
jmp .rc
.error_newsize_zero:
mov eax, ERR_ZERO_SIZE
jmp .rc
.error_newsize_too_big:
mov eax, ERR_OUT_OF_RANGE
jmp .rc
.error_bad_pointer:
mov eax, ERR_INVALID_POINTER
jmp .rc
endp
;=============================================================================
; mem.size
; desc: returns size of memory block
; args: blockptr - pointer to memory block returned by mem.alloc
; ret: CF set on error, orherwise
; eax = size of memory block
; error: ERR_INVALID_POINTER - blockptr isn't a valid block pointer
; ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
; ERR_UNKNOWN
; ERR_MODULE_NOT_INITIALIZED
;=============================================================================
proc GetMemSize, .blockptr
begin
push ebx
;is module initialized?
cmp [mem.initialized], 0
je .error_not_initialized
;was heap corruption detected?
cmp [mem.heap_corrupt], 0
jne .error_heap_corrupt
;EBX = pointer to heap block
mov ebx, [.blockptr]
sub ebx, sizeof.HEAPBLOCK
;check if pointer lies inside heap
mov eax, ebx
sub eax, [mem.heap.start]
jb .error_bad_pointer
sub eax, [mem.heap.size]
jae .error_bad_pointer
;check block
cmp [ebx + HEAPBLOCK.sign], 'HB'
jne .error_bad_pointer
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
jb .error_bad_pointer
cmp [ebx + HEAPBLOCK.last], 1
ja .error_heap_corrupt
;return size
mov eax, [ebx + HEAPBLOCK.asize]
cmp eax, [ebx + HEAPBLOCK.size]
ja .error_heap_corrupt
.rnc: clc
.r: pop ebx
return
.rc: stc
pop ebx
return
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
.error_heap_corrupt:
int3
mov [mem.heap_corrupt], 1
mov eax, ERR_HEAP_CORRUPT
jmp .rc
.error_bad_pointer:
mov eax, ERR_INVALID_POINTER
jmp .rc
endp
;_______________________________________________________________________________________________
;===================================================================
; internal - for testing heap
proc __mem.test_heap
begin
push ebx edx
;is module initialized?
cmp [mem.initialized], 0
je .error_not_initialized
;was heap corruption detected?
cmp [mem.heap_corrupt], 0
jne .error_heap_corrupt
mov ebx, [mem.heap.start]
mov edx, 0
.process_block:
;check if block lies in heap
mov eax, ebx
sub eax, [mem.heap.start]
jb .error_heap_corrupt
cmp eax, [mem.heap.size]
jae .error_heap_corrupt
;check block
cmp [ebx + HEAPBLOCK.sign], 'HB'
jne .error_heap_corrupt
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
cmp [ebx + HEAPBLOCK.prev], edx
jne .error_heap_corrupt
;if used, check 0 < asize <= size
cmp [ebx + HEAPBLOCK.used], 1
ja .error_heap_corrupt
jb @f
mov eax, [ebx + HEAPBLOCK.asize]
test eax, eax
je .error_heap_corrupt
cmp eax, [ebx + HEAPBLOCK.size]
ja .error_heap_corrupt
@@:
;check if last block fills entire heap
cmp [ebx + HEAPBLOCK.last], 1
ja .error_heap_corrupt
jb @f
lea eax, [ebx + sizeof.HEAPBLOCK]
add eax, [ebx + HEAPBLOCK.size]
sub eax, [mem.heap.start]
cmp eax, [mem.heap.size]
jne .error_heap_corrupt
@@:
;if this was last block, end
cmp [ebx + HEAPBLOCK.last], 1
je .done
;move to next block
mov edx, ebx
add ebx, [ebx + HEAPBLOCK.size]
add ebx, sizeof.HEAPBLOCK
jmp .process_block
.done:
;does last block fill entire heap?
lea eax, [ebx + sizeof.HEAPBLOCK]
add eax, [ebx + HEAPBLOCK.size]
sub eax, [mem.heap.start]
cmp eax, [mem.heap.size]
jne .error_heap_corrupt
.rnc: clc
.r: pop edx ebx
return
.rc: stc
jmp .r
.error_not_initialized:
mov eax, ERR_MODULE_NOT_INITIALIZED
jmp .rc
.error_heap_corrupt:
int3
mov [mem.heap_corrupt], 1
mov eax, ERR_HEAP_CORRUPT
jmp .rc
endp
;; @name mem.copy
;; @desc
;; Copies contents block of memory of given size to another place.
;; Supports overlapped copy (like "memmove" in C).
;; @arg dest
;; pointer to destination place, to where the block will be copied
;; @arg src
;; pointer to source place, from where the block will be copied
;; @arg size
;; number of bytes to copy
;; @ret
;; CF = 1 on error, otherwise
;; EAX = dest
;; @err ERR_INVALID_POINTER
;; some of pointers point to inaccessible area
;; @note Supports overlapped copy, eg. case when src < dest < src+size.
;; @note Does nothing when size=0
;; @note Memory management doesn't have to be initalized for this to work
proc mem.copy, .dest, .src, .size
begin
push ecx esi edi
pushf
mov edi, [.dest]
mov esi, [.src]
mov ecx, [.size]
;just return when copying 0 bytes
cmp ecx, 0
je .done
;check if dest area is overlapping src (when src < dest < src+size)
cmp esi, edi
je .done ;if dest=src, we don't need to move
ja .not_overlapping
lea eax, [esi+ecx]
cmp edi, eax
jae .not_overlapping
;if it's overlapping
.overlapping:
;start moving from end of buffers
add esi, ecx
add edi, ecx
;classical unoptimized unalgined move
std
dec esi
dec edi
shr ecx, 1
jnc @f
movsb
@@:
dec esi
dec edi
shr ecx, 1
jnc @f
movsw
@@: jecxz @f
sub esi, 2
sub edi, 2
rep movsd
@@:
jmp .done
.not_overlapping:
;classical unoptimized unalgined move
cld
shr ecx, 1
jnc @f
movsb
@@: shr ecx, 1
jnc @f
movsw
@@: jecxz @f
rep movsd
@@:
;everything is okay
.done: and byte [esp],not 1 ;clear CF in stack
mov eax,[.dest]
.r: popf
pop edi esi ecx
return
endp
;============================================================================
;; @name mem.fill
;; @desc
;; Fills block of memory with given dword pattern.
;; @arg dest
;; Pointer to block which we will fill.
;; @arg size
;; Size of block to fill.
;; If size isn't multiple of 4, remaining bytes will be filled with according
;; bytes of filler (little endian).
;; @arg value
;; Dword value with which the block is filled.
;; @ret
;; CF = 1 on error, otherwise
;; EAX = dest
;; @err ERR_INVALID_POINTER
;; dest points to inaccessible area
;; @note Does nothing when size=0
;; @note Memory management doesn't have to be initalized for this to work
proc mem.fill, .dest, .size, .value
begin
push ecx edi
pushf
cld
mov edi, [.dest]
mov ecx, [.size]
mov eax, [.value]
;just return if size=0
cmp ecx,0
je .done
;align filling pointer to dword
test edi, 1b
jz @f
stosb
ror eax, 8
dec ecx
@@: test edi, 10b
jz @f
stosw
ror eax, 16
dec ecx
dec ecx
@@:
;do the main fill
push ecx
shr ecx, 2
rep stosd
pop ecx
;fill rest
shr ecx, 1
jnc @f
stosb
ror eax, 8
@@: shr ecx, 1
jnc @f
stosw
@@:
;everything is okay
.done: and byte [esp],not 1 ;clear CF in stack
mov eax, [.dest]
.r: popf
pop edi ecx
return
endp
endmodule