Unable to retrieve process memory usage Delphi - delphi

I am trying to write a little utility to retrieve a list of running processes on a computer and the memory being used by each process.
So far I have the following Delphi code:
{ ******************************************************************
Return list of running processes
******************************************************************* }
procedure TGkrTools.GetProcList(var thelist : tstrings);
var
ExeName : string;
PSize : cardinal;
PID : cardinal;
TheLoop : boolean;
proc : PROCESSENTRY32;
hSnap : HWND;
pmc : PPROCESS_MEMORY_COUNTERS;
cb : integer;
begin
thelist.Clear; // Clear the list on entry
proc.dwSize := SizeOf(PROCESSENTRY32);//Give proc.dwSize the Size of bytes of PROCESSENTRY32
hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
TheLoop := Process32First(hSnap,proc);
while Integer(TheLoop) <> 0 do
begin
ExeName := ExtractFileName(proc.szExeFile); // Name of process executable
cb := SizeOf(_PROCESS_MEMORY_COUNTERS);
GetMem(pmc, cb);
pmc^.cb := cb;
PID:=proc.th32ProcessID;
if GetProcessMemoryInfo(PID, pmc, cb) then
begin
Psize:=pmc^.WorkingSetSize;
end
else
begin
Psize:=0;
end;
Thelist.Add(ExeName + ' ' + IntToStr(Psize)+ ' ' + IntToStr(PID));
TheLoop := Process32Next(hSnap,proc);//Looper is oposite Zero until there is a program to process by this function
end;
end;
I successfully retrieve the "ExeName" and "PID" but when I execute the GetProcessMemoryInfo function, the call returns "false".
Any ideas on what is wrong in my code?
I'm running this on a 32 bit Windows 7 machine.
Thanks in advance,
Gudfinnur Kristjansson

You should read the documentation carefully. The first parameter is a process handle rather than a process ID. You need to call OpenProcess to get a process handle from the process ID. The documentation also says:
The handle must have the PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access right and the PROCESS_VM_READ access right.
Make sure that you request those access rights when calling OpenProcess.
When you are done with the handle, close it with CloseHandle. Again, read the documentation closely.
Do note that your current code leaks the dynamic memory that you allocate. There is actually no need to allocate memory dynamically. Rather than declaring a PPROCESS_MEMORY_COUNTERS, pointer to the struct, declare a variable of type PPROCESS_MEMORY_COUNTERS. Then the memory can be allocated automatically on the stack.
One final point to make is that the documentation also tells you to call GetLastError if the function fails. Do make sure that you do this to try to diagnose any failures.

Related

How to fix "No more files" error in Delphi application with Paradox tables on Windows 10 1803?

In old Delphi applications which use the old and deprecated but still used BDE database engine with Paradox database files residing on a Windows 10 computer that's updated to the 1803 "Spring Creators Update" version, but the client computers using any older version of Windows like Windows 10 1709 or Windows 7, opening a Paradox table sometimes fails with a "No more files" error, idapi32.dll error code DBIERR_OSENMFILE. This raises a EDBEngineError exception in DBTables.pas / TTable.GetHandle(), which is called by TTable.CreateHandle, called by TBDEDataSet.OpenCursor().
The error seems to be caused by some file-sharing related changes in the Windows 10 1803 update. Removing the 1803 update from the file-sharing Windows 10 computer, or updating all the client computers to Windows 10 + 1803 seems to make the error go away.
People have speculated that the changes have something to do with the SMB protocol, maybe Windows Defender and/or other security related issues. Here's a Google Plus discussion
https://plus.google.com/106831056534874810288/posts/F4nsoTz2pDi
How could the "No more files" error be worked around by some reasonably easily doable changes in the Delphi application, while allowing the file-sharing client and server computers to keep using heterogeneous Windows versions?
Please try to refrain from answering or commenting self-evident things like "the sky is blue" or "BDE is old and deprecated". Keeping BDE is a decision that cannot be changed, certainly not as a "bug fix".
As an emergency fix, we have resorted to simply re-trying DbiOpenTable, when it returns the DBIERR_OSENMFILE error code. I posted an answer with source code to the idapi32.dll hack. So far it seems that if the first DbiOpenTable says "No more files", the second try succeeds, and the application works without noticing anything.
WARNING: what follows is a hack. A kludge. Band-aid, glue, duct tape and chewing gum. BDE is old. You are completely on your own if you use BDE and/or if you try this hack. I accept no responsibility over its use. If it works for you, good for you. If it ruins your business, bad for you.
Since the Paradox tables still mostly worked and the error seemed to be slightly randomly triggered, and since someone suspected Windows Defender having something to do with it, I thought maybe it just needs some kicking around. If DbiOpenTable() suddenly starts sometimes failing over a certain combination of SMB client/server versions, because "No more files" ... then why not just try the file operation again. I put an "if it returns a DBIERR_OSENMFILE error, then Sleep() and try again" logic around the DbiOpenTable function, and guess what - it seemed to work.
Hacking around the BDE's "features" is familiar to anyone who has to maintain BDE based applications. So I made a patching hook around idapi32.dll's DbiOpenTable function, starting from an old routine written by Reinaldo Yañez originally to fix the "insufficient disk space" error with BDE when the free disk space is at a 4 GB boundary. See https://cc.embarcadero.com/Item/21475
To use this, add Fix1803 in a uses clause, and call PatchBDE somewhere before starting to open Paradox tables. Maybe call UnPatchBDE when you're done, though I don't think that's necessary.
But remember, you're on your own, and this is highly experimental code.
unit Fix1803;
// * KLUDGE WARNING *
// Patch (hack) idapi32.dll DbiOpenTable() to try harder, to work with Windows 10 1803 "Spring Creators Update".
//
// The patching routine is an extension of code originally written by Reinaldo Yañez.
// see https://cc.embarcadero.com/Item/21475
//
// Some original Spanish comments are left in place.
interface
procedure PatchBDE;
procedure UnPatchBDE;
implementation
uses
Windows, Db, DbTables, BDE, SysUtils;
// ------------------------------------------- DbiOpenTable hook
var DbiOpenTable_address_plus_9 : Pointer;
function Actual_DbiOpenTable_CallStub(hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall; assembler;
asm
// these two instructions are implicitly contained in the start of the function
// push ebp
// mov ebp, esp
add esp, $fffffee8
jmp dword ptr [DbiOpenTable_address_plus_9]
end;
function LogHook_DbiOpenTable (hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall;
var
i : Integer;
begin
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
// if we got the "No more files" error, try again... and again.
i := 1;
while (Result = DBIERR_OSENMFILE) and (i < 10) do
begin
Windows.Sleep(i);
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
Inc(i);
end;
end;
// ------------------------------------------- Patching routines
const // The size of the jump instruction written over the start of the original routine is 5 bytes
NUM_BYTES_OVERWRITTEN_BY_THE_PATCH = 5;
type
TRYPatch = record
OrgAddr: Pointer;
OrgBytes: array[0..NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1] of Byte;
end;
procedure TRYPatch_Clear(var ARYPatch : TRYPatch);
begin
FillChar(ARYPatch, SizeOf(TRYPatch), 0);
end;
function RedirectFunction(OldPtr, NewPtr, CallOrigStub : Pointer; var OriginalRoutineAddressPlusN: Pointer; NumBytesInCompleteInstructionsOverwritten : Integer): TRYPatch;
type
PPtr=^pointer;
PPPtr=^PPtr;
TByteArray=array[0..maxint-1] of byte;
PByteArray=^TByteArray;
function SameBytes(Ptr1, Ptr2 : Pointer; NumBytes : Integer) : Boolean;
var
i : Integer;
begin
Result := true;
i := 0;
while (Result) and (i < NumBytes) do
begin
Result := Result and ((PByteArray(Ptr1)^[i] = PByteArray(Ptr2)^[i]));
Inc(i);
end;
end;
var
PatchingAddress : Pointer;
OldProtect,
Protect : DWORD;
p: PByteArray;
i : Integer;
begin
PatchingAddress := OldPtr;
if PWord(PatchingAddress)^ = $25FF then
begin {Es un JMP DWORD PTR [XXXXXXX](=> Esta utilizando Packages)}
p := PatchingAddress;
PatchingAddress := (PPPtr(#p[2])^)^; // PatchingAddress now points to the start of the actual original routine
end;
// Safety check (as if this thing was "safe"). The given replacement routine must start with the same bytes as the replaced routine.
// Otherwise something is wrong, maybe a different version of idapi32.dll or something.
if (CallOrigStub <> nil) and not SameBytes(PatchingAddress, CallOrigStub, NumBytesInCompleteInstructionsOverwritten) then
raise Exception.Create('Will not redirect function, original call stub doesn''t match.');
// Change memory access protection settings, so we can change the contents
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, #OldProtect);
// Save the old contents of the first N bytes of the routine we're hooking
Result.OrgAddr := PatchingAddress; // Save the address of the code we're patching (which might not be the same as the original OldPtr given as parameter)
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
result.OrgBytes[i] := PByte(Integer(PatchingAddress) + i)^;
// Replace the first bytes of the original function with a relative jump to the new replacement hook function
// First write the instruction opcode, $E9 : JMP rel32
PByte(PatchingAddress)^:= $E9;
// Then write the instruction's operand: the relative address of the new function
PInteger(Integer(PatchingAddress)+1)^ := Integer(NewPtr) - Integer(PatchingAddress) - 5;
// Address to jump to, for the replacement routine's jump instruction
OriginalRoutineAddressPlusN := Pointer(Integer(PatchingAddress) + NumBytesInCompleteInstructionsOverwritten);
// Restore the access protection settings
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, #Protect);
FlushInstructionCache(GetCurrentProcess, PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
procedure RestorePatch(RestorePatch: TRYPatch);
var
OldProtect,
Protect : DWORD;
OldPtr: Pointer;
i : Integer;
begin
OldPtr := RestorePatch.OrgAddr;
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, #OldProtect);
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
PByte(Integer(OldPtr) + i)^ := RestorePatch.OrgBytes[i];
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, #Protect);
FlushInstructionCache(GetCurrentProcess, OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
var
idapi32_handle: HMODULE;
Patch_DbiOpenTable : TRYPatch;
procedure PatchBDE;
begin
if idapi32_handle <> 0 then Exit; // already_patched
idapi32_handle := LoadLibrary('idapi32');
if idapi32_handle <> 0 then
begin
Patch_DbiOpenTable := RedirectFunction(GetProcAddress(idapi32_handle, 'DbiOpenTable'), #LogHook_DbiOpenTable, #Actual_DbiOpenTable_CallStub, DbiOpenTable_address_plus_9, 9);
end;
end;
procedure UnPatchBDE;
begin
if idapi32_handle <> 0 then
begin
{Leave everything as before, just in case...}
if Patch_DbiOpenTable.OrgAddr <> nil then
RestorePatch(Patch_DbiOpenTable);
FreeLibrary(idapi32_handle);
idapi32_handle := 0;
end;
end;
initialization
idapi32_handle := 0;
TRYPatch_Clear(Patch_DbiOpenTable);
end.
VMWare, Virtual Box, etc to virtualize an Windows 7. If, as you say, W7 work flawlessly that would solve the problem.

Access Violation - how do I track down the cause?

I'm getting an access violation when I close a form in my application. It seems to happen only after I have access a database a couple of times, but that doesn't seem to make sense.
I have traced through and put outputdebugstring messages in all the related OnDestroy() methods, but the AV appears to be outside of my code.
This is the text of the message:
Access violation at address 00405F7C in module
'MySoopaApplication.exe'. Read of address 00000008.
How do I find where in the application 00405F7C is?
What tools are available in Delphi 10.1 Berlin to help me with this?
Edit: added a bit more info ... when clicking "Break" the IDE always takes me to this piece of code in GETMEM.INC:
#SmallPoolWasFull:
{Insert this as the first partially free pool for the block size}
mov ecx, TSmallBlockType[ebx].NextPartiallyFreePool
Further edit: well, I found the culprit, though I can't honestly say that the debug tools got me there - they just seemed to indicate it wasn't in my code.
I had used code from the net that I used to find the Windows logged in user - this is it:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen);
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
once I had replaced the code with a simple result := '12345' the AVs stopped. I have no changed it to this code:
function GetThisComputerName: string;
var
nSize: DWord;
CompName: PChar;
begin
nSize := 1024;
GetMem(CompName, nSize);
try
GetComputerName(CompName, nSize);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
which seems to work and, as a bonus, doesn't cause AVs.
Thanks for your help, much appreciated.
Under Tools|Options in the IDE go to Embarcadero Debuggers | Language Exceptions and make sure Notify on Language Exceptions is checked. Also under Project Options | Compiling, make sure Debugging | Use debug DCUs is checked.
Allow the exception to happen, then go to View | Debug Windows | Call stack and you should be able to see exactly where it occurred. The fact that it occurs after db access is probably because that causes some object to be created which generates the AV when it is destroyed. Possibly because it is being Free()ed twice.
If that doesn't solve it, you may may need an exception-logging tool like madExcept mentioned by DavidH.
Read of address 00000008.
The fact that this address is a low number is suggestive of it being the address of a member of an object (because they are typically at low offsets from the base address of the object).
How do I find where in the application 00405F7C is?
With your app running, in the IDE go to Search | Go to Address. This should find it if the exception is in your application and not in some related module like a .DLL it is using. The menu item is enabled once the application is running in the IDE and stopped at a breakpoint. Also there is a compiler command line switch to find an error by address.
Others have explained how to diagnose an AV.
Regarding the code itself, there are issues with it:
Most importantly, you are not allocating enough memory for the buffer. GetMem() operates on bytes but GetComputetName() operates on characters, and in this case SizeOf (Char) is 2 bytes. So you are actually allocating half the number of bytes that you are reporting to GetComputerName(), so if it writes more than you allocate then it will corrupt heap memory. The corruption went away when you over-allocated the buffer. So take SizeOf(Char) into account when allocating:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen * SizeOf(Char)); // <-- here
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
In addition to that:
you are ignoring errors from GetComputerName(), so you are not guaranteeing that CompName is even valid to pass to Result in the first place.
You should use SetString(Result, CompName, nSize) instead of Result := CompName, since GetComputerName() outputs the actual CompName length. There is no need to waste processing time having the RTL calculate the length to copy when you already know the length. And since you don't check for errors, you can't rely on CompName being null terminated anyway if GetComputerName() fails.
You should get rid of GetMem() altogether and just use a static array on the stack instead:
function GetThisComputerName: string;
var
CompName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
nSize: DWORD;
begin
nSize := Length(CompName);
if GetComputerName(CompName, nSize) then
SetString(Result, CompName, nSize)
else
Result := '';
end;

Serial Port Synchronization in Delphi

I am still having issues with the TComPort component but this time is not the component itself is the logic behind it. I have a device witch sends some ascii strings via serial port i need to prase those strings the problem is the computer reacts very fast so in the event char it captures only a part of the string the rest of the string comes back later... so parsing it when it is recived makes it impossible.
I was thinking in writing a timer witch verify if there was no serial activity 10 secons or more and then prase the string that i am saving into a buffer. But this method is unprofessional isn't there a idle event witch i can listen...Waiting for the best solution for my problem. Thanks.
After using a number of serial-port-components, I've got the best results until now, by using CreateFile('\\?\COM1',GENERIC_READ or GENERIC_WRITE,0,nil,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0), passing that handle to a THandleStream instance, and starting a dedicated thread to read from it. I know threads take a little more work than writing an event handler, but it still is the best way to handle any synchronization issues that arise from using serial ports.
Typical handler for OnRXChar event:
procedure XXX.RXChar(Sender: TObject; Count: Integer);
begin
ComPort.ReadStr(s, Count);
Accumulator := Accumulator + s;
if not AccumContainsPacketStart then
Accumulator := ''
else if AccumContainsPacketEndAfterStart then begin
ExtractFullStringFromAccum;
ParseIt;
end;
end;
Note.
Most com-port components do not have a clue when to report back to the owner. Normally the thread that is responsible to gather the bytes from the port is informed by the OS that one or more bytes are ready to be processed. This information is then simply popped up to your level. So when you expect the message to be transferred, you get what the OS is giving you.
You have to buffer all incoming characters in a global buffer. When you get the final character in your message string, handle the message.
Here is an example where the message start is identified with a special character and the end of the message is identified with another character.
If your message is constructed in another way, I'm sure you can figure out how to adapt the code.
var
finalBuf: AnsiString;
{- Checking message }
Function ParseAndCheckMessage(const parseS: AnsiString) : Integer;
begin
Result := 0; // Assume ok
{- Make tests to confirm a valid message }
...
end;
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var
i,err: Integer;
strBuf: AnsiString;
begin
ComPort.ReadStr(strBuf, Count);
for i := 1 to Length(strBuf) do
case strBuf[i] of
'$' :
finalBuf := '$'; // Start of package
#10 :
begin
if (finalBuf <> '') and (finalBuf[1] = '$') then // Simple validate check
begin
SetLength( finalBuf, Length(finalBuf) - 1); // Strips CR
err := ParseAndCheckMessage(finalBuf);
if (err = 0) then
{- Handle validated string }
else
{- Handle error }
end;
finalBuf := '';
end;
else
finalBuf := finalBuf + strBuf[i];
end;
end;
If your protocol has begin/end markers, you can use TComDataPacket to provide you full packets, when they are available.
For certain amount of character we can use delay some miliseconds before ReadStr to make sure the data is completely sent. Example for 4 amount of character:
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
var
Str: String;
tegangan : real;
begin
sleep(100); //delay for 100ms
ComPort1.ReadStr(Str, 4);
...

Improve speed of own debug visualizer for Delphi 2010

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;

Reading arbitrary memory locations? Possible?

Is there a way to (read-only) access any arbitrary memory location without running into an access violation? I thought that each process has its own virtual adress space and that it can read all available memory locations...seems not to be the case, since my program hangs if I do something like
var
IntPtr : PInteger;
AnInteger : Integer;
...
IntPtr := $100;
AnInteger := IntPtr^;
I'm still trying to write my low-level recursive size-of function and try to detect if a data member is an object reference or not.
Thanks!
You can only access your own process' memory via pointers, and even then it's only those parts that have been mapped for your process. There are debugger hooks that will give you access to other processes memory; but they're tricky to get right.
So if you really want to iterate through your process memory, you can probably find the functions that you need here: http://msdn.microsoft.com/en-us/library/ms878234.aspx
AFAIR in windows also part of kernel is mapped to your processes memory space (which is the reason to not have all of 4G available for your process).
Your application hangs? There is something wrong with your application then.
Usually, there will be a simple AV. An AV leads to error message. That is all.
BTW, you should not be scared of it - just handle it.
function IsValidObject(const AObj: Pointer { or TObject} ): Boolean;
begin
try
...
// place your checking code there
Result := ...;
except
on EAccessViolation do
Result := False;
end;
end;
The only exception to this rule that comes to mind is if you're writing some sort of exception handler and want to detect if there is a valid object. In that case you probably do not want to generate an exception in handler of exception ;)
If this is your case - then try to use this code (this is an example):
function GetReadableSize(const AAddress: Pointer; const ASize: Cardinal): Cardinal;
const
ReadAttributes = [PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE];
var
MemInfo: TMemoryBasicInformation;
Tmp: Cardinal;
begin
Result := 0;
if (VirtualQuery(AAddress, MemInfo, SizeOf(MemInfo)) = SizeOf(MemInfo)) and
(MemInfo.State = MEM_COMMIT) and (MemInfo.Protect in ReadAttributes) then
begin
Result := (MemInfo.RegionSize - (Cardinal(AAddress) - Cardinal(MemInfo.BaseAddress)));
if Result < ASize then
begin
repeat
Tmp := GetReadableSize(Pointer(DWord(MemInfo.BaseAddress) + MemInfo.RegionSize), (ASize - Result));
if (Tmp > 0) then
Inc(Result, Tmp)
else
Result := 0;
until (Result >= ASize) or (Tmp = 0);
end;
end;
end;
function IsValidBlockAddr(const AAddress: Pointer; const ASize: Cardinal): Boolean;
begin
Result := (GetReadableSize(AAddress, ASize) >= ASize);
end;
But usually you should prefer first approach.
If you want to safely try reading any memory address with no fuss, and get a nice error-code rather than an exception when the memory you're trying to read is inaccessible, the function you want to use is in the WinAPI: ReadProcessMemory.
Memory might not be mapped at all adresses. And the lower 4kb or so are always protected afaik.
However if it is for VMs, if you control the memory manager, you can build up a list with all memory ranges that your application mapped.
Unless there is some Magic way that I'm unaware of, I'm pretty sure you can't do this. Windows uses protected memory, which means that you can't access anything that hasn't been specifically allocated to you.
There is DMA, but it's reserved for driver-level software.
In old Windows, 95, 98, Me you could do some inline asm in your function and read/write some arbitrary memory location or hardware port...
function ReadPortByte : Byte;
var
Base : Word;
begin
Base := FAddress;
asm
mov DX, Base
in AL, DX
mov Result, AL
end;
end;
You can still do this by using a device driver, but Vista may cause you some problems unless the driver is compiled properly for Vista and above.
There are several free ones out there and worth experimenting with.
John

Resources