;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2016. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Checks whether the given file is already loaded as PE. ; If so, returns pointer to PEDESCR which can be used for map_pe_usermode. ; out: eax = 0, ebx undefined ; out: esi -> PEDESCR ; If not, loads the given file; if it is PE, creates PEDESCR; ; out: eax -> file data, ebx = file size ; out: esi -> PEDESCR ; if it is not PE, returns loaded file as is. ; out: eax -> file data, ebx = file size ; out: esi = 0 ; On error: ; out: eax = negative error code, ebx = 0 ; out: esi = 0 proc load_file_maybe_pe stdcall uses edi, file_name:dword locals fileinfo rb 40 filedata dd ? defaultbase dd ? entry dd ? stacksize dd ? endl and [filedata], 0 ; 1. Lock the common mutex for PE list. mov ecx, pe_list_mutex call mutex_lock ; 2. Check whether this PE file is already mapped somewhere. ; 2a. Get the timestamp. If failed, pass filesystem error to the caller. lea eax, [fileinfo] stdcall get_fileinfo, [file_name], eax mov edi, eax neg edi jnz .fail_unlock ; 2b. Scan the list of PE files, for each file compare timestamp and name. ; If all parameters match, go to 6. Otherwide, proceed to 3. mov esi, [pe_list.fd] .scan_existing: cmp esi, pe_list jz .load_new mov eax, dword [fileinfo+24]; last modified time mov edx, dword [fileinfo+28]; last modified date cmp dword [esi+PEDESCR.timestamp], eax jnz .continue_scan cmp dword [esi+PEDESCR.timestamp+4], edx jnz .continue_scan stdcall strncmp, [esi+PEDESCR.name], [file_name], -1 test eax, eax jz .already_loaded .continue_scan: mov esi, [esi+PEDESCR.fd] jmp .scan_existing ; Either this file is not PE, or it has not been mapped yet. .load_new: ; 3. Load and unpack file data. ; If failed, return -5 "file not found". stdcall load_file, [file_name] movi edi, -5 test eax, eax jz .fail_unlock mov [filedata], eax mov dword [fileinfo+32], ebx ; 4. Check that the file is valid PE, has image data and is not too large. ; If not, pass the loaded file to the caller as is. cmp ebx, 40h jb .not_pe cmp word [eax], 'MZ' jz .check_mz cmp [eax+STRIPPED_PE_HEADER.Signature], STRIPPED_PE_SIGNATURE jnz .not_pe mov ecx, [eax+STRIPPED_PE_HEADER.SizeOfStackReserve] mov [stacksize], ecx mov ecx, [eax+STRIPPED_PE_HEADER.ImageBase] mov esi, [eax+STRIPPED_PE_HEADER.SizeOfImage] mov edi, [eax+STRIPPED_PE_HEADER.AddressOfEntryPoint] jmp .pe .check_mz: mov ecx, [eax+3Ch] add eax, ecx add ecx, IMAGE_NT_HEADERS.OptionalHeader jc .not_pe cmp ecx, ebx ja .not_pe cmp [eax+IMAGE_NT_HEADERS.Signature], 'PE' jnz .not_pe movzx edx, [eax+IMAGE_NT_HEADERS.FileHeader.SizeOfOptionalHeader] cmp edx, IMAGE_OPTIONAL_HEADER32.DataDirectory jb .not_pe add ecx, edx jc .not_pe cmp ecx, ebx ja .not_pe cmp [eax+IMAGE_NT_HEADERS.OptionalHeader.Magic], 10Bh jnz .not_pe mov ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfStackReserve] mov [stacksize], ecx mov ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.ImageBase] mov esi, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage] mov edi, [eax+IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint] .pe: test esi, esi jz .not_pe cmp esi, 16*1024*1024 ja .not_pe mov [defaultbase], ecx mov [entry], edi add esi, 0xFFF shr esi, 12 ; 5. Allocate and initialize PEDESCR structure. ; 5a. Calculate structure size: sizeof.PEDESCR + dword per image page + size of PE name. mov edi, [file_name] mov ecx, -1 xor eax, eax repnz scasb not ecx lea eax, [ecx+esi*4+sizeof.PEDESCR] ; 5b. Allocate memory. ; If failed, return -30 "no memory". push ecx call malloc pop ecx movi edi, -30 test eax, eax jz .fail_and_free_data ; 5c. Initialize PEDESCR structure. mov [eax+PEDESCR.size], esi lea edi, [eax+esi*4+sizeof.PEDESCR] mov esi, [file_name] mov [eax+PEDESCR.name], edi rep movsb mov esi, eax lea edi, [eax+sizeof.PEDESCR] mov ecx, [eax+PEDESCR.size] xor eax, eax rep stosd mov eax, dword [fileinfo+24] mov dword [esi+PEDESCR.timestamp], eax mov eax, dword [fileinfo+28] mov dword [esi+PEDESCR.timestamp+4], eax mov eax, [defaultbase] mov [esi+PEDESCR.defaultbase], eax mov eax, [entry] mov [esi+PEDESCR.entry], eax mov eax, [stacksize] mov [esi+PEDESCR.stacksize], eax and dword [esi+PEDESCR.refcount], 0; no SMAPs yet; later it will be incremented lea ecx, [esi+PEDESCR.page_array_lock] call mutex_init ; 5d. Insert PEDESCR structure in tail of the common list. mov [esi+PEDESCR.fd], pe_list mov eax, [pe_list.bk] mov [pe_list.bk], esi mov [esi+PEDESCR.bk], eax mov [eax+PEDESCR.fd], esi .already_loaded: ; We have got the PEDESCR structure, ; either already-existed from step 2 or created at step 5. ; In the last case we have also got the file data, ; in the first case [filedata] is still zero. ; 6. Increment reference counter in PEDESCR structure. inc [esi+PEDESCR.refcount] ; 7. Release the common mutex for PE list. ; We have got a new reference to our PEDESCR, it will not go away unexpectedly. mov ecx, pe_list_mutex call mutex_unlock mov eax, [filedata] mov ebx, dword [fileinfo+32] ret .fail_and_free_data: stdcall kernel_free, [filedata] .fail_unlock: mov ecx, pe_list_mutex call mutex_unlock .fail: mov eax, edi xor ebx, ebx xor esi, esi ret .not_pe: mov ecx, pe_list_mutex call mutex_unlock mov eax, [filedata] xor esi, esi ret endp proc map_pe_usermode stdcall uses ebx esi edi, descr:dword, filedata:dword, filesize:dword locals img_base dd ? peheader dd ? header_size dd ? num_sections dd ? sections dd ? section_idx dd ? page_index dd ? page_offset dd ? cur_page dd ? cur_access db ? rb 3 pages dd ? num_allocated_pages dd ? endl ; 1. Basic preparations. ; 1a. Check that the process heap has been initialized. ; Return -30 "no memory" if not. mov esi, [descr] movi edi, -30 mov eax, [current_process] cmp [eax+PROC.heap_top], 0 jz .fail_dereference ; 1b. If filedata is passed, fill the required fields from header now. mov eax, [filedata] mov ebx, [filesize] dec edi ; -30 -> -31 test eax, eax jz @f call .validate_header jc .fail_dereference @@: ; 2. Generate array of pages for mapping in address space in the process. ; It is possible to join this step with step 13, avoiding temporary allocation ; and putting the result directly in the page table, but that would require ; doing potentially long operations like loading/unpacking the file ; while holding address space lock, which could block other threads ; that just want to lazy-allocate their zero-only pages not related to PE. ; So, keep generating and mapping separate. ; 2a. Allocate memory. inc edi ; -31 -> -30 mov eax, [esi+PEDESCR.size] shl eax, 2 call malloc test eax, eax jz .fail_dereference mov [pages], eax ; 2b. Acquire the lock. lea ecx, [esi+PEDESCR.page_array_lock] call mutex_lock ; 2c. Prepare for loop over pages: set page index and page offset to zero. xor ecx, ecx mov [page_index], ecx mov [page_offset], ecx mov [num_allocated_pages], ecx .fill_pages: ; 2d. For each page, test whether we need to regenerate it. ; Pages that need to be regenerated are marked as zero in pages array. mov eax, [esi+sizeof.PEDESCR+ecx*4] test eax, eax jz .create_page ; 2e. For each page that we do not need to regenerate, ; increment reference counter if it is less than 0xFF ; and go to 2t. lea edx, [eax+1] test edx, 0xFF jz .page_created mov [esi+sizeof.PEDESCR+ecx*4], edx jmp .page_created .create_page: ; 2f. If the file has not been already loaded/unpacked, ; do it now, validating the content. cmp [filedata], 0 jnz @f stdcall load_file, [esi+PEDESCR.name] test eax, eax jz .fail_free_pages call .validate_header jc .fail_free_pages @@: ; 2h. Initialize for generating a page: ; do not allocate a page until we will be sure that data are present, ; there are no access rights yet. and [cur_page], 0 mov [cur_access], 0 ; 2i. Check whether the page overlaps file header. ; If not, go to 2m. mov eax, [page_offset] cmp eax, [header_size] jae .no_header ; 2j. Allocate the page data. stdcall kernel_alloc, 0x1000 test eax, eax jz .fail_free_pages mov [cur_page], eax ; 2k. Set access rights for header: readonly; copy header data. mov [cur_access], IMAGE_SCN_MEM_READ shr 28 mov esi, [filedata] mov edi, eax add esi, [page_offset] mov ecx, [header_size] sub ecx, [page_offset] cmp ecx, 0x1000 jb @f mov ecx, 0x1000 @@: mov edx, ecx shr ecx, 2 rep movsd mov ecx, edx and ecx, 3 rep movsb ; 2l. Fill the rest of the page with zeroes. mov ecx, 0x1000 sub ecx, edx mov edx, ecx shr ecx, 2 and edx, 3 xor eax, eax rep stosd mov ecx, edx rep stosb .no_header: ; 2m. Prepare for loop over sections. ; Skip the loop if there are no sections. mov eax, [num_sections] mov ebx, [sections] test eax, eax jz .no_sections mov [section_idx], eax .sections_loop: ; 2n. For every section, check whether it has data overlapped with ; the current page; if so, allocate the page if not yet, copy the data ; and fill rest of page with zeroes. ; If data are present, there can be two cases: ; - the current page has data from the beginning, ; - first byte of the current page is not covered by the section. ; The first case is typical, the second case is rare. ; If the page has not been allocated yet, we can optimize by storing zeroes ; only in areas that are not covered by the current section. ; However, this becomes twisted in the second case, ; so don't bother to optimize the rare case. cmp [ebx+COFF_SECTION.SizeOfRawData], 0 jz .section_data_done mov esi, [page_offset] sub esi, [ebx+COFF_SECTION.VirtualAddress] cmp esi, [ebx+COFF_SECTION.SizeOfRawData] jb .beginning_inside add esi, 1000h jnc .section_data_done jz .section_data_done ; Rare case: there is an overlap, but the first byte is not covered. ; If the page has not been allocated, allocate it now and store 4K zeroes. cmp [cur_page], 0 jnz @f stdcall kernel_alloc, 0x1000 test eax, eax jz .fail_free_pages mov [cur_page], eax mov edi, eax xor eax, eax mov ecx, 0x1000/4 rep stosd @@: mov edi, [ebx+COFF_SECTION.VirtualAddress] and edi, 0xFFF xor esi, esi .copy_data: mov eax, [ebx+COFF_SECTION.SizeOfRawData] sub eax, esi mov ecx, 0x1000 sub ecx, edi add edi, [cur_page] cmp ecx, eax jb @f mov ecx, eax @@: add esi, [filedata] add esi, [ebx+COFF_SECTION.PtrRawData] mov edx, ecx shr ecx, 2 and edx, 3 rep movsd mov ecx, edx rep movsb jmp .section_data_done .beginning_inside: ; Normal case: do not store zeroes which will be immediately overwritten. xor edi, edi cmp [cur_page], edi jnz .copy_data stdcall kernel_alloc, 0x1000 test eax, eax jz .fail_free_pages mov [cur_page], eax mov edi, eax mov ecx, [ebx+COFF_SECTION.SizeOfRawData] sub ecx, esi cmp ecx, 0x1000 jb @f mov ecx, 0x1000 @@: add esi, [filedata] add esi, [ebx+COFF_SECTION.PtrRawData] mov edx, ecx shr ecx, 2 rep movsd mov ecx, edx and ecx, 3 rep movsb mov ecx, 0x1000 sub ecx, edx mov edx, ecx shr ecx, 2 and edx, 3 xor eax, eax rep stosd mov ecx, edx rep stosb .section_data_done: ; 2o. Get size of the section header. ; Characteristics is the last dword in both ; COFF_SECTION and STRIPPED_PE_SECTION, so this helps to access it. movi ecx, sizeof.STRIPPED_PE_SECTION cmp [peheader], 0 jz @f mov cl, sizeof.COFF_SECTION @@: ; 2p. If the current page intersects virtual address range of the section, ; update access rights using section access rights. cmp [ebx+COFF_SECTION.VirtualSize], 0 jz .section_access_done mov esi, [page_offset] sub esi, [ebx+COFF_SECTION.VirtualAddress] cmp esi, [ebx+COFF_SECTION.VirtualSize] jb @f add esi, 0x1000 jnc .section_access_done jz .section_access_done @@: mov eax, [ebx+ecx-4] shr eax, 28 or [cur_access], al .section_access_done: ; 2q. Advance to the next section, while there are sections left. add ebx, ecx dec [section_idx] jnz .sections_loop .no_sections: ; 2r. Shareable pages can not be lazy-allocated ; even if they only contain uninitialized data. ; If the page is shareable and has not been allocated yet, do it now. test [cur_access], IMAGE_SCN_MEM_SHARED shr 28 jz @f cmp [cur_page], 0 jnz @f stdcall kernel_alloc, 0x1000 test eax, eax jz .fail_free_pages mov [cur_page], eax mov edi, eax xor eax, eax mov ecx, 0x1000/4 rep stosd @@: ; 2s. Get and store the item for page array: 0xFF for pages with zeroes, ; physical address of the page plus 1 for reference counter otherwise, ; with access rights in bits 8-11 in both cases. mov edi, 0xFF mov eax, [cur_page] test eax, eax jz @f call get_pg_addr lea edi, [eax+1] stdcall free_kernel_space, [cur_page] @@: movzx eax, [cur_access] shl eax, 8 or eax, edi mov ecx, [page_index] mov esi, [descr] mov [esi+sizeof.PEDESCR+ecx*4], eax .page_created: ; 2t. Transform the item from page array to page table entry: ; - drop reference counter, ; - map zero-only page to LAZY_ALLOC_PAGE ; with optional flags LAZY_ALLOC_{UNREADABLE,UNWRITABLE}, ; PF handler will lazy-allocate it; ; - for pages with data, ; map readable and executable to user bit, ; for shareable pages map writable to writable bit, ; for non-shareable pages ignore writable to support copy-on-write. mov edx, eax and eax, not 0xFFF jz .page_set_zero inc [num_allocated_pages] or eax, PG_READ+PG_SHARED test dh, (IMAGE_SCN_MEM_READ+IMAGE_SCN_MEM_EXECUTE) shr 28 jz @f or al, PG_USER @@: test dh, IMAGE_SCN_MEM_SHARED shr 28 jz @f test dh, IMAGE_SCN_MEM_WRITE shr 28 jz @f or al, PG_WRITE @@: jmp .pte_generated .page_set_zero: mov al, LAZY_ALLOC_PAGE test dh, (IMAGE_SCN_MEM_READ+IMAGE_SCN_MEM_EXECUTE) shr 28 jnz @f or al, LAZY_ALLOC_UNREADABLE @@: test dh, IMAGE_SCN_MEM_WRITE shr 28 jnz @f or al, LAZY_ALLOC_UNWRITABLE @@: .pte_generated: mov edi, [pages] mov [edi+ecx*4], eax ; 2u. Advance to the next page, until PEDESCR.size is reached. inc ecx inc [page_index] add [page_offset], 0x1000 cmp ecx, [esi+PEDESCR.size] jb .fill_pages ; 2v. Release the lock. lea ecx, [esi+PEDESCR.page_array_lock] call mutex_unlock ; 3. Allocate a new SMAP. movi eax, sizeof.SMAP call malloc test eax, eax jz .fail_free_pages_unlocked mov ebx, eax ; 4. Lock the address space so that a random PF from other thread ; between end of step 5 and beginning of step 7 would not trash anything. mov ecx, [current_process] add ecx, PROC.heap_lock call mutex_lock ; 5. Allocate space in the address space. ; Prefer PEDESCR.defaultbase, but allow address anywhere else ; if allocation at PEDESCR.defaultbase is not possible. mov edi, [esi+PEDESCR.size] shl edi, 12 stdcall user_alloc_at_nolock, [esi+PEDESCR.defaultbase], edi test eax, eax jnz @f stdcall user_alloc_nolock, edi test eax, eax jz .user_alloc_failed @@: mov [img_base], eax ; 6. Fill SMAP with values and insert it to the list of SMAPs. mov [ebx+SMAP.type], SMAP_TYPE_PE mov ecx, [current_process] add ecx, PROC.smap_list mov edx, [ecx+SMAP.fd] mov [ebx+SMAP.fd], edx mov [ebx+SMAP.bk], ecx mov [ecx+SMAP.fd], ebx mov [edx+SMAP.bk], ebx mov [ebx+SMAP.base], eax mov [ebx+SMAP.size], edi mov [ebx+SMAP.parent], esi ; 7. Copy page table entries prepared at step 2 to the page table. mov edx, eax shr edx, 12 mov ecx, [esi+PEDESCR.size] mov esi, [pages] lea edi, [page_tabs+edx*4] rep movsd mov eax, [num_allocated_pages] shl eax, 12 ; 8. Release the address space lock. mov ecx, [current_process] add [ecx+PROC.mem_used], eax add ecx, PROC.heap_lock call mutex_unlock ; 9. Cleanup and return allocated address. mov eax, [pages] call free cmp [filedata], 0 jz @f stdcall kernel_free, [filedata] @@: mov eax, [img_base] ret .fail_and_free_data: stdcall kernel_free, [filedata] .fail: mov eax, edi ret .user_alloc_failed: mov ecx, [current_process] add ecx, PROC.heap_lock call mutex_unlock mov eax, ebx call free .fail_free_pages_unlocked: lea ecx, [esi+PEDESCR.page_array_lock] call mutex_lock .fail_free_pages: mov ecx, [page_index] test ecx, ecx jz .fail_free_pages_array mov eax, [esi+sizeof.PEDESCR+(ecx-1)*4] mov edx, eax and eax, not 0xFFF jz .fail_free_next and edx, 0xFF cmp edx, 0xFF jz .fail_free_next dec dword [esi+sizeof.PEDESCR+(ecx-1)*4] dec edx jnz .fail_free_next mov [esi+sizeof.PEDESCR+(ecx-1)*4], edx call free_page .fail_free_next: dec [page_index] jmp .fail_free_pages .fail_free_pages_array: lea ecx, [esi+PEDESCR.page_array_lock] call mutex_unlock mov eax, [pages] call free movi edi, -30 .fail_dereference: cmp [filedata], 0 jz @f stdcall kernel_free, [filedata] @@: call dereference_pe mov eax, edi ret .validate_header: mov [filedata], eax cmp ebx, 40h jb .validate_header.error mov [peheader], 0 cmp word [eax], STRIPPED_PE_SIGNATURE jz .validate_header.stripped cmp word [eax], 'MZ' jnz .validate_header.error mov ecx, [eax+3Ch] add eax, ecx add ecx, IMAGE_NT_HEADERS.OptionalHeader jc .validate_header.error cmp ecx, ebx ja .validate_header.error cmp [eax+IMAGE_NT_HEADERS.Signature], 'PE' jnz .validate_header.error mov [peheader], eax movzx edx, [eax+IMAGE_NT_HEADERS.FileHeader.SizeOfOptionalHeader] cmp edx, IMAGE_OPTIONAL_HEADER32.DataDirectory jb .validate_header.error add ecx, edx jc .validate_header.error cmp ecx, ebx ja .validate_header.error lea edx, [eax+IMAGE_NT_HEADERS.OptionalHeader+edx] mov [sections], edx movzx edx, [eax+IMAGE_NT_HEADERS.FileHeader.NumberOfSections] mov [num_sections], edx imul edx, sizeof.COFF_SECTION add ecx, edx jc .validate_header.error cmp ecx, ebx ja .validate_header.error mov edx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders] mov [header_size], edx cmp edx, ebx ja .validate_header.error mov edx, [num_sections] mov ecx, [sections] test edx, edx jz .validate_header.sections_ok @@: mov eax, [ecx+COFF_SECTION.PtrRawData] add eax, [ecx+COFF_SECTION.SizeOfRawData] jc .validate_header.error cmp eax, ebx ja .validate_header.error add ecx, sizeof.COFF_SECTION dec edx jnz @b .validate_header.sections_ok: .validate_header.ok: clc retn .validate_header.stripped: movzx ecx, [eax+STRIPPED_PE_HEADER.NumberOfRvaAndSizes] lea ecx, [sizeof.STRIPPED_PE_HEADER+ecx*8] movzx edx, [eax+STRIPPED_PE_HEADER.NumberOfSections] mov [num_sections], edx imul edx, sizeof.STRIPPED_PE_SECTION add edx, ecx cmp edx, ebx ja .validate_header.error mov edx, [eax+STRIPPED_PE_HEADER.SizeOfHeaders] mov [header_size], edx cmp edx, ebx ja .validate_header.error add ecx, eax mov [sections], ecx mov edx, [num_sections] test edx, edx jz .validate_header.stripped.sections_ok @@: mov eax, [ecx+STRIPPED_PE_SECTION.PtrRawData] add eax, [ecx+STRIPPED_PE_SECTION.SizeOfRawData] jc .validate_header.error cmp eax, ebx ja .validate_header.error add ecx, sizeof.STRIPPED_PE_SECTION dec edx jnz @b .validate_header.stripped.sections_ok: clc retn .validate_header.error: stc retn endp ; in: edi -> SMAP ; in: address space lock must be held proc release_pemap stdcall uses ebx esi, process:dword locals num_released_pages dd 0 mapped_pagedir dd -1 endl mov esi, [edi+SMAP.base] mov ebx, [edi+SMAP.parent] shr esi, 12 dec esi add ebx, sizeof.PEDESCR call .get_page_tab_entry mov ecx, [eax] and ecx, not MEM_BLOCK_DONT_FREE mov [eax], ecx shr ecx, 12 dec ecx jz .released .release: inc esi call .get_page_tab_entry mov edx, [eax] test dl, 1 jz .next test edx, PG_SHARED jz .next mov dword [eax], 0 inc [num_released_pages] xor edx, [ebx] test edx, not 0xFFF jnz .next mov edx, [ebx] mov eax, edx and edx, 0xFF cmp edx, 0xFF jz .next dec eax test dword [ebx], IMAGE_SCN_MEM_SHARED shr 20 jnz @f test eax, 0xFF jnz @f call free_page xor eax, eax @@: mov [ebx], eax .next: add ebx, 4 dec ecx jnz .release mov eax, [num_released_pages] shl eax, 12 mov edx, [process] sub [edx+PROC.mem_used], eax cmp [mapped_pagedir], -1 jz .released stdcall map_page, [tmp_task_ptab], 0, PG_UNMAP .released: ret .get_page_tab_entry: mov eax, [process] cmp eax, [current_process] jnz @f lea eax, [page_tabs+esi*4] retn @@: push edx mov edx, esi shr edx, 10 cmp edx, [mapped_pagedir] jz @f mov [mapped_pagedir], edx mov eax, [eax+PROC.pdt_0+edx*4] and eax, not 0xFFF stdcall map_page, [tmp_task_ptab], eax, PG_SWR @@: mov eax, [tmp_task_ptab] mov edx, esi and edx, 0x3FF lea eax, [eax+edx*4] pop edx retn endp proc unmap_pe_usermode stdcall uses ebx esi edi, address:dword mov ecx, [current_process] lea edi, [ecx+PROC.smap_list] add ecx, PROC.heap_lock call mutex_lock mov esi, edi mov eax, [address] .scan: mov edi, [edi+SMAP.fd] cmp edi, esi jz .notfound cmp [edi+SMAP.base], eax jnz .scan mov eax, [edi+SMAP.fd] mov edx, [edi+SMAP.bk] mov [eax+SMAP.bk], edx mov [edx+SMAP.fd], eax call mutex_unlock stdcall destroy_smap, [current_process] xor eax, eax ret .notfound: call mutex_unlock or eax, -1 ret endp