#1 2011-06-08 11:42:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,206
Website

True per-class variable

For our ORM, we needed a class variable to be available for each TSQLRecord class type. This variable is used to store the properties of this class type, i.e. the database Table properties (e.g. table and column names and types) associated with a particular TSQLRecord class, from which all our ORM objects inherit.

The class var statement was not enough for us:
- It's not available on earlier Delphi versions, and we try to have our framework work with Delphi 6-7;
- This class var instance will be shared by all classes inheriting from the class where it is defined - and we need ONE instance PER class type, not ONE instance for ALL

We need to find another way to implement this class variable. An unused VMT slot in the class type description was identified, then each class definition was patched in the process memory to contain our class variable.


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/h … rt-ii.html in order to use the vmtAutoTable deprecated entry in the VMT.

We'll 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.

Offline

#2 2015-09-29 16:44:23

Stefan
Member
Registered: 2012-11-23
Posts: 26

Re: True per-class variable

I was looking into this approach for some feature I am implementing currently as it seemed the most practical solution to my problem (per class metadata similar to your use case).
However I only know how to write protected memory on Windows and not how that would be done on the Mobile platforms. Do you have any information about that?

Offline

#3 2015-09-30 12:01:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,206
Website

Re: True per-class variable

We were able to make it work under Linux, MacOSX and Android, with FPC (and Kylix).
See how PatchCode() is implemented in SynCommons.pas.

Under Delphi for mobile, you have the mprotect() low-level libc function to make the executable memory writable.

Offline

#4 2015-10-01 09:30:27

Stefan
Member
Registered: 2012-11-23
Posts: 26

Re: True per-class variable

Thanks, will have someone more experienced with mobile look into it smile

Offline

Board footer

Powered by FluxBB