getkernelbase:
pushad
xor edx, edx
mov esi, dword ptr FS:[edx]
__seh: lodsd
cmp eax, 0FFFFFFFFh
je __kernel
mov esi, eax
jmp __seh
__kernel: mov edi, dword ptr[esi + 4]
and edi, 0FFFF0000h
__spin: cmp word ptr[edi], 'ZM'
jz __test_pe
sub edi, 10000h
jmp __spin
__test_pe: mov ebx, edi
add ebx, [ebx.MZ_lfanew]
cmp word ptr[ebx],'EP'
je __exit_k32
sub edi, 10000h
jmp __spin
__exit_k32: mov [esp.Pushad_eax], edi
popad
ret
3.2 用VirtualAlloc and VirtualFree管理内存
我们是否知道新的缓冲区被定位了呢?答案是肯定的,它就在我们原程序的结尾处。为了描述我们缓冲区每一页的状态,我也将用4字节大小的表来描述每页的状态(我将称它为PTE)我们不得不时刻跟踪被VirtualAlloc分配的任何区域,因为一旦调用了VirtualFree我们可以在这页上做还未使用的记号,使得再次调用VirtualAlloc时可以返回。如果没有这些,我们甚至可能益出缓冲区,并导致页失败.首先用一个内存管理结构来描述被分配缓冲区的开始,范围和类型,与堆相似,但后来我会指出这样做太慢而且会导致内存泄漏。这时我们记得Intel CPU是如何把虚拟内存转变为物理内存的。在PDE/PTE中虚拟地址被用作索引,并包括物理结构和每页的状态,所以为什么不采用这样的技术和在ring 3级上做相同的事情和内存管理呢?它的速度很快,用页索引去访问存有每页状态的数据。
短暂的思考后,我就得到了我想要的,页的入口:(译者注:作者在ring3级上模仿虚拟内存转变为物理内存的方法非常有效,巧妙,请仔细阅读)
31 2 1 0
+---------------------------+---+---+
| FIRST PAGE INDEX | R | P |
+---------------------------+---+---+
P-当前的位显示,页是否在用或空闲。
R-保存位,仅仅在VirtualAlloc以MEM_RESERVE被调用。
FIRST PAGE INDEX又称为FPI-拥有第一页的索引,用来决定块大小。
缓冲区的格局如下::
+--------------+----------------------------------+--------------+
| Progy | VirtualAlloc/Free buffer | PTE |
+--------------+----------------------------------+--------------+
你的PTE大小也许是=(buffer/1000h)*4,在我这里,我分配了1000页并用4页去描述每页的状态。
为了获得PTE,你应该获得每页的索引,而获得它是非常简单。假如我们的程序开始于400000h,结束于500000h,则程序的结尾就是我们缓冲区的开始,PTE是被定位在我们缓冲区的最后4页
mov esi, [edi+memstart] ;esi=500000h
add esi, NEW_MEM_RANGE ;esi+1004000h
sub esi, 4000h ;structs for memory manager(PTEs)
memstart = end of progy
NEW_MEM_RANGE = 1004000h ;1000*1000 pages + 4000h for PTE
现在我们简单的来获取索引,假定eax是虚拟地址。
mov edx, eax
sub edx, [ebp+memstart] ;-500000h
shr edx, 0ch ;index into edx
瞧,用简单的[esi+edx*4]就可以看见是否这页被分配了,被保留或可用。当然,这是我的执行,在你的执行中组织可以是不同的PTE, 应该给PTEs分配足量的空间以来满足我们的要求。基本上我们用4000h来描述缓冲区1000000h字节的状态,难道这不美好?你不得不去喜欢Intel和他们把物理内存转换成虚拟内存的思想。当然,你可以分配更小的缓冲区和更小的PTE,那将完全取决于你。
现在我将告诉你关于FIRST PAGE INDEX以及它为什么这么重要。我随后将给你展示如何在这样的缓冲区中运用nonintrusive tracers,还是先告诉你FPI.FPI用来查看被分配缓冲区的大小和调用VirtualFree时释放缓冲区的大小。FPI含有第一页的索引,并被置于任何一个PTE描述的某个范围。如果FPI是1 in 3 PTEs ,这就意味着这个缓冲区开始于PTE的INDEX 1 ,并且所有的拥有FPI 1 的页都是相同缓冲区的一部分。这将稍后帮助我们编写nonintrusive tracer代码和释放内存,因为有时VirtualFree 被作为 VirtualFree(page_base, 0, MEM_DECOMMIT)来调用,并且如果不知道缓冲区的大小会导致内存泄露。也许你奇怪我为什么不储存第一个PTE的大小并在接着的PTEs上做记号,而是去储存它们每个的FPI。简单的说,FPI将使我们知道nonintrusive tracer中必须改变的内存缓冲区的大小.如果异常在第三页发生,你仅仅只用在第三页改变保护,但是我们想在调用VirtualAlloc时改变整个范围的保护是,这就更有意义了。
看一段代码,我想你会理解
allocatememory proc
arg virtualbase
arg range
arg flags
arg memprotection
local numofpages:dword
local dummy_var:dword
local virtualaddress:dword
call deltaalloc
deltaalloc: pop edi
sub edi, offset deltaalloc
mov esi, [edi+memstart]
add esi, NEW_MEM_RANGE
sub esi, 4000h ;structs for memory manager(PTEs)
mov eax, range
mov edx, eax
shr eax, 0ch
and edx, 0FFFh
test edx, edx
jz __mm0
inc eax
__mm0: mov numofpages, eax
cmp virtualbase, 0
jne __commitpage ;commit reserved pages???? yep
;find free block big enough and commit pages
;starting from index 1
mov ecx, 1
__cycle_empty: test dword ptr[esi+ecx*4], 1 ;committed?
jnz __next_pte
test dword ptr[esi+ecx*4], 2 ;reserved?
jz __check_size
__next_pte: inc ecx
cmp ecx, 1000h
jne __cycle_empty
__check_size: mov eax, numofpages
add eax, ecx
__cycle_size: dec eax
test dword ptr[esi+eax*4], 1
jnz __next_pte
test dword ptr[esi+eax*4], 2
jnz __next_pte
cmp eax, ecx
jne __cycle_size
;at this point we have found PTEs large enough to
;describe needed memory buffer
mov eax, numofpages ;ecx is index used to get page
add eax, ecx
mov edx, ecx
shl edx, 2 ;FPI
mov ebx, flags ;1 for P or 2 for R
__add_pages: dec eax
mov dword ptr[esi+eax*4], 0 ;set PTE to 0
or dword ptr[esi+eax*4], edx;set FPI
or dword ptr[esi+eax*4], ebx;set flags
cmp eax, ecx
jne __add_pages
__done: shl ecx, 0ch
add ecx, [edi+memstart]
mov virtualaddress, ecx
jmp __exitalloc
__commitpage: push virtualbase
pop virtualaddress
mov eax, virtualbase
sub eax, [edi+memstart]
shr eax, 0ch
mov ecx, numofpages
mov edx, eax
shl edx, 2
__commit_em: mov dword ptr[esi+eax*4], 0 ;clear pte
or dword ptr[esi+eax*4], 3 ;flags
or dword ptr[esi+eax*4], edx ;fpi
inc eax
loop __commit_em
__exitalloc: mov eax, virtualaddress
leave
retn 10h
endp
注意,如何从PTE的1索引开始。
; 从索引1开始
mov ecx, 1
这非常重要,空的PTE被我的deallocatememory设置为0,随后被分配和释放的区域将会把FPI置零,在这种情况下,寻找FPI是0的内存范围将返回更大的范围。当然,我们可以检验PTE的R和P位,但是这会扩大代码并且使代码的可读性降低。现在,如果你仔细读这个源代码,你将理解在ring 3级上用PTEs写一个漂亮的内存管理代码是多么容易,VirtualFree写起来也很简单。
deallocatememory:
mov esi, [ebp+memstart]
add esi, NEW_MEM_RANGE
sub esi, 4000h ;pointer to PTE
;freeing using index and FPI to find all pages
mov edx, eax
sub edx, [ebp+memstart]
shr edx, 0ch ;index into eax
mov ecx, edx ;FPI into ecx
mov eax, edx
cmp eax, 1000h
jnb __exit_free
__freemem: mov edx, [esi+eax*4]
shr edx, 2
cmp edx, ecx ;FPI...
jne __exit_free
mov dword ptr[esi+eax*4], 0 ;clear PTE
inc eax
jmp __freemem
__exit_free: retn
以上就是我要告诉你关于内存管理的一切.