Patching a running process code
The first feature we have to do is to allow on-the-fly change of the assembler code of a process.
When an executable is mapped in RAM, the memory page corresponding to the process code is marked as Read Only, in order to avoid any security attack from the outside. Only the current process can patch its own code.
We'll need to override a pointer
value in the code memory. The
following function, defined in SynCommons.pas
will handle it:
procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt); var RestoreProtection, Ignore: DWORD; begin if VirtualProtect(Code, SizeOf(Code^), PAGE_EXECUTE_READWRITE, RestoreProtection) then begin Code^ := Value; VirtualProtect(Code, SizeOf(Code^), RestoreProtection, Ignore); FlushInstructionCache(GetCurrentProcess, Code, SizeOf(Code^)); end; end;
The VirtualProtect
low-level Windows API is called to force the
corresponding memory to be written (via the PAGE_EXECUTE_READWRITE
flag), then modify the corresponding pointer
value, then the
original memory page protection setting (should be
PAGE_EXECUTE_READ
) is restored.
According to the MSDN documentation, we'd need to flush the CPU operation cache in order to force the modified code to be read on next access.
Per-class variable in the VMT
The VMT is the Virtual-Method Table, i.e. a Table which defines
every Delphi class
. In fact, every Delphi class
is
defined internally by its VMT, contains a list of pointers to the
class
’s virtual
methods. This VMT also contains
non-method values, which are class-specific information at negative
offsets:
Name | Offset | Description |
vmtSelfPtr |
–76 | points back to the beginning of the table |
vmtIntfTable |
–72 | TObject.GetInterfaceTable method value |
vmtAutoTable |
–68 | class’s automation table (deprecated) |
vmtInitTable |
–64 | reference-counted fields type information |
vmtTypeInfo |
–60 | the associated RTTI type information |
vmtFieldTable |
–56 | field addresses |
vmtMethodTable |
–52 | method names |
vmtDynamicTable |
–48 | dynamic methods table |
vmtClassName |
–44 | PShortString of the class name |
vmtInstanceSize |
–40 | bytes needed by one class Instance |
vmtParent |
–36 | parent VMT |
We'll use the trick as detailed in
http://hallvards.blogspot.com/2007/05/hack17-virtual-class-variables-part-ii.html
in order to use the vmtAutoTable
deprecated entry in the
VMT.
This slot entry was used in Delphi 2 only for implementing Automation.
Later version of Delphi (our goal) won't use it any more.
But the slot is still here, ready for being used by the framework.
We'll therefore be able to store a pointer to the
TSQLRecordProperties
instance corresponding to a
TSQLRecord
class, which will be retrieved as such:
class function TSQLRecord.RecordProps: TSQLRecordProperties; begin if Self<>nil then begin result := PPointer(PtrInt(Self)+vmtAutoTable)^; if result=nil then result := PropsCreate(self); end else result := nil; end;
Since this method is called a lot of time by our ORM, there is an asm-optimized version of the pascal code above:
class function TSQLRecord.RecordProps: TSQLRecordProperties; asm or eax,eax jz @null mov edx,[eax+vmtAutoTable] or edx,edx jz PropsCreate mov eax,edx @null: end;
Most of the time, this method will be executed very quickly. In fact, the
PropsCreate
global function is called only once, i.e. the first
time this RecordProps
method is called.
The TSQLRecordProperties
instance is therefore created within
this function:
function PropsCreate(aTable: TSQLRecordClass): TSQLRecordProperties; begin // private sub function makes the code faster in most case if not aTable.InheritsFrom(TSQLRecord) then // invalid call result := nil else begin // create the properties information from RTTI result := TSQLRecordProperties.Create(aTable); // store the TSQLRecordProperties instance into AutoTable unused VMT entry PatchCodePtrUInt(pointer(PtrInt(aTable)+vmtAutoTable),PtrUInt(result)); // register to the internal garbage collection (avoid memory leak) GarbageCollector.Add(result); end; end;
The GarbageCollector
is a global TObjectList
,
which is used to store some global instances, living the whole process time,
just like our TSQLRecordProperties
values.
A per-class TSQLRecordProperties
was made therefore available
for each kind of TSQLRecord
class.
Feedback and comments are welcome on our forum.