Inspect your memory image and see fragmentation
The VirtualQueryEx function can help to inspect the memory of a particular process. It returns information about the various memory pages allocated to a process. If a block is marked as MEM_IMAGE, it’s a loaded module, like an EXE or DLL, so you can use the GetModuleFileName function to find the full path of the loaded module.
Run the code below, which displays 2 instances of a form and graphically maps the process’s memory allocation map onto the forms. The yellow/green blocks indicate free memory. The white is MEM_COMMIT, and the red is a MEM_IMAGE loaded module. Move your mouse around the form and you can see the BROWSE record reflect what memory block you’re over. Between the first and the second form instances, memory is fragmented by allocating some huge strings and freeing every other one. You can see the difference between the two graphs.
To show the modules loaded in the process, try
SELECT DISTINCT filename FROM memmap
Try implementing an interface or calling a VFP COM server via early binding to make VFP allocate some memory using the PAGE_EXECUTE_READWRITE flag
Or try using a multithreaded VFP Com server and hit it with many threads (perhaps using IIS) and perhaps see more PAGE_GUARD attributes: one for each thread’s stack (if each thread’s stack is allowed to grow).
CLEAR ALL
CLEAR
#define PROCESSOR_ARCHITECTURE_INTEL 0
#define PROCESSOR_ARCHITECTURE_MIPS 1
#define PROCESSOR_ARCHITECTURE_ALPHA 2
#define PROCESSOR_ARCHITECTURE_PPC 3
#define PROCESSOR_ARCHITECTURE_SHX 4
#define PROCESSOR_ARCHITECTURE_ARM 5
#define PROCESSOR_ARCHITECTURE_IA64 6
#define PROCESSOR_ARCHITECTURE_ALPHA64 7
#define PROCESSOR_ARCHITECTURE_MSIL 8
#define PROCESSOR_ARCHITECTURE_AMD64 9
#define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10
*State:
#define MEM_COMMIT 0x1000
#define MEM_RESERVE 0x2000
#define MEM_FREE 0x10000
*Protect:
#define PAGE_NOACCESS 0x01
#define PAGE_READONLY 0x02
#define PAGE_READWRITE 0x04
#define PAGE_WRITECOPY 0x08
#define PAGE_EXECUTE 0x10
#define PAGE_EXECUTE_READ 0x20
#define PAGE_EXECUTE_READWRITE 0x40
#define PAGE_EXECUTE_WRITECOPY 0x80
#define PAGE_GUARD 0x100
#define PAGE_NOCACHE 0x200
#define PAGE_WRITECOMBINE 0x400
*Type
#define SEC_IMAGE 0x1000000
#define MEM_IMAGE SEC_IMAGE
#define MEM_PRIVATE 0x20000
#define MEM_MAPPED 0x40000
#define MEM_RESET 0x80000
#define MEM_TOP_DOWN 0x100000
#define MEM_LARGE_PAGES 0x20000000
#define MEM_4MB_PAGES 0x80000000
#define SEC_RESERVE 0x4000000
#define PROCESS_DUP_HANDLE (0x0040)
#define PROCESS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)
PUBLIC oForm1,oForm2
oForm1=CREATEOBJECT("MemMapForm")
oForm1.Show
oForm1.ReadMem()
*Now do something that takes a lot of memory, like create an In Process COM server
* ox=CREATEOBJECT("t1.c1")
DIMENSION aa(10)
FOR i = 1 TO ALEN(aa)
aa[i]=SPACE(1.5e7) && make many huge strings
ENDFOR
FOR i = 1 TO ALEN(aa) STEP 2 && now fragment mem
aa[i]=0
ENDFOR
*Create another instance
oForm2=CREATEOBJECT("MemMapForm")
oForm2.Show
oForm2.ReadMem()
DEFINE CLASS MemMapForm AS form
AllowOutput=.f.
height=400
width=600
caption=""
DataSession=2 && private data
PROCEDURE init
DECLARE integer VirtualQueryEx IN WIN32API integer hProcess, integer lpAddress,;
string @, integer dwLength
DECLARE GetSystemInfo IN WIN32API string @pSystemInfo
DECLARE integer GetCurrentProcess IN win32api
DECLARE integer GetModuleFileName IN WIN32API integer hModule, string @ cBuf, integer nSize
this.Top=(this.DataSessionId-2) * thisform.Height
PROCEDURE ReadMem()
cSystemInfo = SPACE(36) && sizeof(SYSTEM_INFO)
GetSystemInfo(@cSystemInfo)
dwPageSize=CTOBIN(SUBSTR(cSystemInfo,1*4+1,4),"4rs")
IF .f.
?"ProcessorArchitecture ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,0*4+1,4),"4rs"),"@0x")
?"PageSize ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,1*4+1,4),"4rs"),"@0x")
?"MinimumApplicationAddress",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,2*4+1,4),"4rs"),"@0x")
?"MaximumApplicationAddress",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,3*4+1,4),"4rs"),"@0x")
?"ActiveProcessorMask ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,4*4+1,4),"4rs"),"@0x")
?"NumberOfProcessors ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,5*4+1,4),"4rs"),"@0x")
?"ProcessorType ",CTOBIN(SUBSTR(cSystemInfo,6*4+1,4),"4rs") && 586 = PENTIUM
?"AllocationGranularity ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,7*4+1,4),"4rs"),"@0x")
?"ProcessorLevel ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,8*4+1,2),"2rs"),"@0x")
?"ProcessorRevision ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,8*4+1+2,2),"2rs"),"@0x")
ENDIF
DECLARE integer GetProcessHeap IN win32api
* ?TRANSFORM(GetProcessHeap (),"@0x")
*x=CREATEOBJECT("t1.c1")
CREATE CURSOR MEMMap (BaseAddr i, AllocBase i, AllocProt i, ;
RegionSize i, state i, Protect i, Type i,Filename c(200))
dwPage=1
DO WHILE .t.
cMBI = SPACE(28) && MEMORY_BASIC_INFORMATION
VirtualQueryEx(GetCurrentProcess(), dwPage * dwPageSize,@cMBI,LEN(cMBI))
IF CTOBIN(SUBSTR(cMBI,0*4+1,4),"4rs")>= 0x7ffe0000
EXIT
ENDIF
cFilename=SPACE(200)
INSERT INTO memMap VALUES (;
CTOBIN(SUBSTR(cMBI,0*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,1*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,2*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,3*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,4*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,5*4+1,4),"4rs"),;
CTOBIN(SUBSTR(cMBI,6*4+1,4),"4rs"),;
"")
IF BITAND(type,MEM_IMAGE)>0
IF AllocBase>0
nLen=GetModuleFileName(AllocBase,@cFileName,LEN(cFileName))
cFilename=LEFT(cFilename,nLen)
REPLACE filename WITH cFilename
ENDIF
ENDIF
dwPage = dwPage + RegionSize/dwPageSize
ENDDO
*use the BROWSE to see the data: mouse over the forms and see the current record change
cBrowName="oBrow"+TRANSFORM(thisform.DataSessionId - 1)
PUBLIC (cBrowName)
BROWSE NOWAIT NAME (cBrowName) TITLE "MemMap"+TRANSFORM(thisform.DataSessionId - 1) FIELDS ;
BaseAddr=TRANSFORM(BaseAddr,"@0x"),;
AllocBase=TRANSFORM(AllocBase,"@0x"),;
AllocProt=TRANSFORM(AllocProt,"@0x"),;
RegionSize=TRANSFORM(RegionSize,"@0x"),;
State=IIF(BITAND(state,MEM_FREE)>0,"Free",IIF(BITAND(state,MEM_RESERVE)>0,"Reserve","Commit")),;
Protect=TRANSFORM(Protect,"@0x"),;
Type=TRANSFORM(Type,"@0x"),;
FileName=JUSTFNAME(FileName)
oBrow = EVALUATE(cBrowName)
oBrow.height = thisform.Height
oBrow.Top=thisform.Top
oBrow.left=thisform.width
oBrow.width = 800
*SELECT DISTINCT filename FROM memmap
dx = thisform.Width && now graph it
dy = thisform.Height
nRatio = dx*dy/2^31 && max addr / max pixels
x0=0
y0=0
nPos=0 && from 0 to dx * dy
SCAN
nPos=nPos + RegionSize*nRatio
x1= MOD(nPos,dx)
y1 = INT(nPos /dx )
IF EMPTY(Filename)
coff = MOD(RECNO()*41,60)
thisform.ForeColor=IIF(BITAND(state,MEM_FREE)>0,0xffff-coff ,0xffffff) && yellow is free
ELSE
thisform.ForeColor=0xff
ENDIF
DO WHILE y0 < y1
thisform.Line(x0,y0,dx,y0)
x0=0
y0=y0+1
ENDDO
thisform.Line(x0,y0,x1,y1)
x0=x1
y0=y1
ENDSCAN
PROCEDURE MouseMove(nButton, nShift, nX, nY)
dx = thisform.Width
dy = thisform.Height
nRatio = dx*dy/2^31 && max addr / max pixels
nPos = nY * dx+nX
nAddr = nPos / nRatio
LOCATE FOR BETWEEN(nAddr, BaseAddr , BaseAddr + RegionSize)
ACTIVATE WINDOW ("MemMap"+TRANSFORM(thisform.DataSessionId - 1))
thisform.Caption=TRANSFORM(nAddr,"@0x")+" "+JUSTFNAME(filename)
ENDDEFINE
Comments
Anonymous
June 16, 2006
A customer asked
1) Is it better to set it to 0 (Auto) or some "larger" number, such as -16 (1024kb).&nbsp;...Anonymous
February 01, 2007
nice post .. i'm writing memory scanner for a day.. and i wondered how to tell if its a dll .. GetModuleFileName does the trick ) however .. what i do is .. for every cycle .. add RegionSize to AllocBase to produce the start of the region this saves the extra multiplication/division i dont get the sum of pages but i need the speed .. and i skip "free" areasAnonymous
August 09, 2007
It seems that quite often there are vertual memory regions marked as MEM_IMAGE, but GetModuleFileNameEx() does not return module name corresponding allocation base addresses. What could be the reason for that?Anonymous
August 09, 2007
It seems that quite often there are virtual memory regions marked as MEM_IMAGE, but GetModuleFileNameEx() does not return module name corresponding allocation base addresses. What could be the reason for that?