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.DataSessionId2) * 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 (4)

  1. A customer asked

    1) Is it better to set it to 0 (Auto) or some &quot;larger&quot; number, such as -16 (1024kb).&amp;nbsp;…

  2. luss says:

    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" areas

  3. Konstantin says:

    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?

  4. Konstantin says:

    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?

Skip to main content