unit md_map; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses ntapi, vm, vmparam, windows; const MD_PAGE_SHIFT = 12; MD_PAGE_SIZE = 4*1024; MD_PAGE_MASK = MD_PAGE_SIZE-1; MD_ALLOC_GRANULARITY= 64*1024; MD_MAP_FIXED =$100; MD_MAP_RESERVED =$200; MD_MAP_ALIGN_SHIFT=24; function MD_MAP_ALIGN(align:QWORD):DWORD; inline; const VM_RW =VM_PROT_READ or VM_PROT_WRITE; VM_RX =VM_PROT_READ or VM_PROT_EXECUTE; VM_RWX=VM_PROT_READ or VM_PROT_WRITE or VM_PROT_EXECUTE; function md_placeholder_mmap (var base:Pointer;size:QWORD;flags:DWORD=0;hProcess:THandle=NtCurrentProcess):Integer; function md_placeholder_unmap (base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; function md_placeholder_split (base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; function md_placeholder_union (base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; function md_placeholder_commit (base:Pointer;size:QWORD;prot:DWORD;hProcess:THandle=NtCurrentProcess):Integer; function md_placeholder_commit (base:Pointer;size:QWORD;prot:DWORD;fd:THandle;offset:QWORD;hProcess:THandle=NtCurrentProcess):Integer; function md_placeholder_decommit(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; function md_memfd_create(var hMem:THandle;size:QWORD;maxprot:Byte):Integer; function md_memfd_open (var hMem:THandle;hFile:THandle;maxprot:Byte):Integer; function md_memfd_close (hMem:THandle):Integer; inline; function md_protect (base:Pointer;size:QWORD;prot:Integer;hProcess:THandle=NtCurrentProcess):Integer; function md_dontneed(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; function md_activate(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; function md_mmap (var base:Pointer;size:QWORD;prot:DWORD;fd:THandle=0;offset:QWORD=0;hProcess:THandle=NtCurrentProcess):Integer; function md_unmap(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; function kmem_alloc(size:QWORD;prot:DWORD):Pointer; procedure kmem_free (base:Pointer;size:QWORD); inline; const ICACHE=1; //Flush the instruction cache. DCACHE=2; //Write back to memory and invalidate the affected valid cache lines. BCACHE=ICACHE or DCACHE; procedure md_cacheflush(addr:Pointer;nbytes,cache:Integer); Function md_create_swap_file(const FNAME:RawByteString;SIZE:QWORD;Var FD:THandle):DWORD; implementation function MD_MAP_ALIGN(align:QWORD):DWORD; inline; begin Result:=BsfQWORD(align) shl MD_MAP_ALIGN_SHIFT; end; const MD_PROT_NONE=PAGE_NOACCESS; MD_PROT_R =PAGE_READONLY; MD_PROT_W =PAGE_READWRITE; MD_PROT_RW =PAGE_READWRITE; MD_PROT_X =PAGE_EXECUTE; MD_PROT_RX =PAGE_EXECUTE_READ; MD_PROT_WX =PAGE_EXECUTE_READWRITE; MD_PROT_RWX =PAGE_EXECUTE_READWRITE; wprots:array[0..7] of Byte=( MD_PROT_NONE,//___ MD_PROT_R ,//__R MD_PROT_W ,//_W_ MD_PROT_RW ,//_WR MD_PROT_X ,//X__ MD_PROT_RX ,//X_R MD_PROT_WX ,//XW_ MD_PROT_RWX //XWR ); waccess:array[0..7] of Byte=( SECTION_QUERY, //___ SECTION_QUERY or SECTION_MAP_READ, //__R SECTION_QUERY or SECTION_MAP_WRITE, //_W_ SECTION_QUERY or SECTION_MAP_WRITE or SECTION_MAP_READ, //_WR SECTION_QUERY or SECTION_MAP_EXECUTE, //X__ SECTION_QUERY or SECTION_MAP_EXECUTE or SECTION_MAP_READ, //X_R SECTION_QUERY or SECTION_MAP_EXECUTE or SECTION_MAP_WRITE, //XW_ SECTION_QUERY or SECTION_MAP_EXECUTE or SECTION_MAP_WRITE or SECTION_MAP_READ //XWR ); function md_dw_gran(x:Pointer):Pointer; inline; begin Result:=Pointer(QWORD(x) and (not (MD_ALLOC_GRANULARITY-1))); end; function md_dw_page(x:QWORD):QWORD; inline; begin Result:=x and (not (MD_PAGE_SIZE-1)); end; function md_up_page(x:QWORD):QWORD; inline; begin Result:=(x+(MD_PAGE_SIZE-1)) and (not (MD_PAGE_SIZE-1)); end; function md_placeholder_mmap(var base:Pointer;size:QWORD;flags:DWORD=0;hProcess:THandle=NtCurrentProcess):Integer; var ADDR:Pointer; len :ULONG_PTR; info:TMemoryBasicInformation; EXT :TMEM_EXTENDED_PARAMETER; REQ :TMEM_ADDRESS_REQUIREMENTS; begin size:=md_up_page(size); if ((flags and MD_MAP_FIXED)<>0) then begin ADDR:=md_dw_gran(base); len:=0; Result:=NtQueryVirtualMemory( hProcess, ADDR, MemoryBasicInformation, @info, sizeof(TMemoryBasicInformation), @len); if (Result=0) and (info.State=MEM_RESERVE) then begin if (ADDR>=info.BaseAddress) and ((ADDR+size)<=(info.BaseAddress+info.RegionSize)) then begin base:=info.AllocationBase; Exit(0); end; end; REQ.LowestStartingAddress:=nil; REQ.Alignment :=0; end else begin ADDR:=nil; REQ.LowestStartingAddress:=base; REQ.Alignment:=(flags shr MD_MAP_ALIGN_SHIFT) and $1F; if (REQ.Alignment<=16) then begin REQ.Alignment:=0; end else begin REQ.Alignment:=QWORD(1) shl REQ.Alignment; end; end; EXT.pType :=MemExtendedParameterAddressRequirements; EXT.Pointer:=@REQ; REQ.HighestEndingAddress:=nil; Result:=NtAllocateVirtualMemoryEx( hProcess, @ADDR, @size, MEM_RESERVE or MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, @EXT, 1 ); if (Result=0) then begin base:=ADDR; end else begin base:=nil; end; end; function md_placeholder_unmap(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; var pend:Pointer; addr,prev:Pointer; info:TMemoryBasicInformation; len:ULONG_PTR; begin if (base=nil) or (size=0) then Exit(0); pend:=base+size; addr:=base; repeat len:=0; NtQueryVirtualMemory( hProcess, addr, 0, @info, sizeof(TMemoryBasicInformation), @len); if (len=0) then Break; if (info.State<>MEM_FREE) then begin addr:=info.BaseAddress; case info._Type of MEM_MAPPED: begin //unmap Result:=NtUnmapViewOfSectionEx(hProcess,addr,MEM_PRESERVE_PLACEHOLDER); end; MEM_PRIVATE: begin //unmap len:=Info.RegionSize; Result:=NtFreeVirtualMemory( hProcess, @addr, @len, MEM_RELEASE or MEM_PRESERVE_PLACEHOLDER ); end; else Result:=-1; end; if (Result<>0) then Exit; end; prev:=addr; addr:=addr+Info.RegionSize; if (addr>=pend) then Break; until (prev>=addr); //union all Result:=NtFreeVirtualMemory( NtCurrentProcess, @base, @size, MEM_RELEASE or MEM_COALESCE_PLACEHOLDERS ); //ignore errors //free size:=0; Result:=NtFreeVirtualMemory( NtCurrentProcess, @base, @size, MEM_RELEASE ); end; function md_placeholder_split(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; begin Result:=NtFreeVirtualMemory( hProcess, @base, @size, MEM_RELEASE or MEM_PRESERVE_PLACEHOLDER ); end; function md_placeholder_union(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; begin Result:=NtFreeVirtualMemory( hProcess, @base, @size, MEM_RELEASE or MEM_COALESCE_PLACEHOLDERS ); end; function md_placeholder_commit(base:Pointer;size:QWORD;prot:DWORD;hProcess:THandle=NtCurrentProcess):Integer; begin prot:=wprots[prot and VM_RWX]; Result:=NtAllocateVirtualMemoryEx( hProcess, @base, @size, MEM_COMMIT or MEM_RESERVE or MEM_REPLACE_PLACEHOLDER, prot, nil, 0 ); end; function md_placeholder_commit(base:Pointer;size:QWORD;prot:DWORD;fd:THandle;offset:QWORD;hProcess:THandle=NtCurrentProcess):Integer; begin prot:=wprots[prot and VM_RWX]; Result:=NtMapViewOfSectionEx( fd, hProcess, @base, @offset, @size, MEM_REPLACE_PLACEHOLDER, prot, nil, 0 ); end; function md_placeholder_decommit(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; begin Result:=NtUnmapViewOfSectionEx(hProcess,base,MEM_PRESERVE_PLACEHOLDER); if (DWORD(Result)=$C0000019) then //STATUS_NOT_MAPPED_VIEW begin Result:=NtFreeVirtualMemory( hProcess, @base, @size, MEM_RELEASE or MEM_PRESERVE_PLACEHOLDER ); end; end; function md_memfd_create(var hMem:THandle;size:QWORD;maxprot:Byte):Integer; var Access:DWORD; prot:DWORD; begin Access:=waccess[maxprot and VM_RWX]; prot :=wprots [maxprot and VM_RWX]; hMem:=0; Result:=NtCreateSectionEx( @hMem, Access, nil, @size, prot, SEC_COMMIT, THandle(0), nil, 0 ); end; function md_memfd_open(var hMem:THandle;hFile:THandle;maxprot:Byte):Integer; var Access:DWORD; prot:DWORD; size:QWORD; begin Access:=waccess[maxprot and VM_RWX]; prot :=wprots [maxprot and VM_RWX]; size:=0; hMem:=0; Result:=NtCreateSectionEx( @hMem, Access, nil, @size, prot, SEC_COMMIT, hFile, nil, 0 ); end; function md_memfd_close(hMem:THandle):Integer; inline; begin Result:=NtClose(hMem); end; function md_protect(base:Pointer;size:QWORD;prot:Integer;hProcess:THandle=NtCurrentProcess):Integer; var old:Integer; begin prot:=wprots[prot and VM_RWX]; old:=0; Result:=NtProtectVirtualMemory( hProcess, @base, @size, prot, @old ); end; function md_dontneed(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; begin Result:=NtAllocateVirtualMemory( hProcess, @base, 0, @size, MEM_RESET, PAGE_NOACCESS ); end; function md_activate(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; inline; begin Result:=NtAllocateVirtualMemory( hProcess, @base, 0, @size, MEM_RESET_UNDO, PAGE_NOACCESS ); end; const atypes:array[0..3] of DWORD=( MEM_COMMIT , //___ MEM_COMMIT or MEM_RESERVE, //__F MEM_RESERVE , //_R_ MEM_RESERVE //_RF ); function md_mmap(var base:Pointer;size:QWORD;prot:DWORD;fd:THandle=0;offset:QWORD=0;hProcess:THandle=NtCurrentProcess):Integer; var atype:DWORD; ADDR :Pointer; EXT :TMEM_EXTENDED_PARAMETER; REQ :TMEM_ADDRESS_REQUIREMENTS; begin EXT.pType :=MemExtendedParameterAddressRequirements; EXT.Pointer:=@REQ; if ((prot and MD_MAP_FIXED)<>0) then begin ADDR:=md_dw_gran(base); REQ.LowestStartingAddress:=nil; REQ.Alignment :=0; end else begin ADDR:=nil; REQ.LowestStartingAddress:=base; REQ.Alignment:=(prot shr MD_MAP_ALIGN_SHIFT) and $1F; if (REQ.Alignment<=16) then begin REQ.Alignment:=0; end else begin REQ.Alignment:=QWORD(1) shl REQ.Alignment; end; end; REQ.HighestEndingAddress:=nil; atype:=atypes[(prot shr 8) and 3]; if (prot and MD_MAP_RESERVED)<>0 then begin prot:=PAGE_NOACCESS; fd :=0; end else begin prot:=wprots[prot and VM_RWX]; end; if (fd=THandle(0)) or (fd=THandle(-1)) then begin Result:=NtAllocateVirtualMemoryEx( hProcess, @ADDR, @size, atype, prot, @EXT, 1 ); end else begin Result:=NtMapViewOfSectionEx( fd, hProcess, @ADDR, @offset, @size, 0, prot, @EXT, 1 ); end; if (Result=0) then begin base:=ADDR; end else begin base:=nil; end; end; function md_unmap(base:Pointer;size:QWORD;hProcess:THandle=NtCurrentProcess):Integer; var pend:Pointer; addr,prev:Pointer; info:TMemoryBasicInformation; len:ULONG_PTR; begin if (base=nil) or (size=0) then Exit(0); pend:=base+size; addr:=base; repeat len:=0; NtQueryVirtualMemory( hProcess, addr, 0, @info, sizeof(TMemoryBasicInformation), @len); if (len=0) then Break; if (info.State<>MEM_FREE) then begin addr:=info.AllocationBase; case info._Type of MEM_MAPPED: begin //unmap Result:=NtUnmapViewOfSectionEx(hProcess,addr,0); end; MEM_PRIVATE: begin //unmap len:=0; Result:=NtFreeVirtualMemory( hProcess, @addr, @len, MEM_RELEASE ); end; else Result:=-1; end; if (Result<>0) then Exit; end else begin addr:=info.BaseAddress; end; prev:=addr; addr:=info.BaseAddress+Info.RegionSize; if (addr>=pend) then Break; until (prev>=addr); end; function kmem_alloc(size:QWORD;prot:DWORD):Pointer; var r:Integer; begin Result:=Pointer(KERNEL_LOWER); //lower r:=md_mmap(Result,size,prot); if (r<>0) then begin Writeln(stderr,'kmem_alloc(0x',HexStr(size,11),',0x',HexStr(prot,3),'):0x',HexStr(r,8)); end; end; procedure kmem_free(base:Pointer;size:QWORD); inline; begin md_unmap(base,size); end; // procedure md_cacheflush(addr:Pointer;nbytes,cache:Integer); begin if ((cache and ICACHE)<>0) then begin FlushInstructionCache(NtCurrentProcess,addr,nbytes); end; if ((cache and DCACHE)<>0) then begin FlushViewOfFile(addr,nbytes); end; end; Function NtTruncate(FD:THandle;SIZE:QWORD):DWORD; inline; var BLK:IO_STATUS_BLOCK; begin Result:=NtSetInformationFile( FD, @BLK, @SIZE, SizeOf(Int64), FileEndOfFileInformation); if (Result<>0) then begin Result:=NtSetInformationFile( FD, @BLK, @SIZE, SizeOf(Int64), FileAllocationInformation); end; end; function StrLen(p: pwidechar): sizeint; external name 'FPC_PWIDECHAR_LENGTH'; overload; Function md_create_swap_file(const FNAME:RawByteString;SIZE:QWORD;Var FD:THandle):DWORD; var W:WideString; OBJ :OBJECT_ATTRIBUTES; UPATH:UNICODE_STRING; BLK :IO_STATUS_BLOCK; begin W:=UTF8Decode(FNAME); W:='\??\'+W; OBJ:=Default(OBJECT_ATTRIBUTES); OBJ.Length :=SizeOf(OBJECT_ATTRIBUTES); OBJ.ObjectName:=@UPATH; UPATH:=Default(UNICODE_STRING); UPATH.Length :=strlen(PWideChar(w))*SizeOf(WideChar); UPATH.MaximumLength:=UPATH.Length+SizeOf(WideChar); UPATH.Buffer :=PWideChar(w); BLK:=Default(IO_STATUS_BLOCK); FD:=0; Result:=NtCreateFile(@FD, FILE_READ_DATA or FILE_WRITE_DATA or FILE_APPEND_DATA or FILE_READ_ATTRIBUTES or FILE_WRITE_ATTRIBUTES or FILE_CAN_DELETE or SYNCHRONIZE, @OBJ, @BLK, nil, FILE_ATTRIBUTE_TEMPORARY, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT or FILE_OPEN_REPARSE_POINT or FILE_NON_DIRECTORY_FILE or FILE_DELETE_ON_CLOSE, nil, 0); if (Result<>0) then Exit; Result:=NtTruncate(FD,SIZE); if (Result<>0) then begin NtClose(FD); FD:=0; end; end; end.