Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > OOP
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Grupo de Teaming del ClubDelphi

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 31-01-2009
poyo poyo is offline
Miembro
 
Registrado: ene 2009
Posts: 47
Poder: 0
poyo Va por buen camino
urgando las VMTs

En este otro hilo, Al González hizo algunos planteamientos que involucraban las famosas táblas de métodos virtuales (VMT's a partir de ahora).

No está muy claro este tema ya que no hay documentación oficial (al menos no la conozco). Lo poco que se conoce es por el Unit System.pas y por lo que algunos programadores han investigado haciendo un poco de ingeniería inversa.

Sé que no es bueno andar por lugares tan oscuros como la VMT ya que no sólo hay poco información sino que son muy suceptibles a los cambios sin aviso previo (ni posterior)...
De todos modos, muchas veces no hay otra opción. Cuando es así, no queda otra que manejarse con cautela y tener en cuenta las diferencias entre versiones.
De hecho, en la versión 2009 se han introducido modificaciones en la VMT...

A continuación pongo alguno links que tenía agendados y que estaban esperando a este momento.

http://hallvards.blogspot.com/2004/0...id-object.html
http://hallvards.blogspot.com/2006/0...structure.html
http://hallvards.blogspot.com/2006/0...vmt-calls.html
http://hallvards.blogspot.com/2007/0...s-part-ii.html

Ya bien empapados en el tema vamos a lo nuestro:

Miré código que tenía, algo de Hallvards, algo de GExperts y algo de JCL y terminé con lo siguiente:

Tenía un unit dedicado al RTTI así que ustedes pueden tener uno llamado RttiUtils.pas o como prefieran.
lo que nos compete va a continuación:

Código Delphi [-]
interface

uses ...Classes, typinfo....;

type
  TDMTIndex   = Smallint;
  PDmtIndices = ^TDmtIndices;
  TDmtIndices = array[0..High(Word)-1] of TDMTIndex;
  PDmtMethods = ^TDmtMethods;
  TDmtMethods = array[0..High(Word)-1] of Pointer;
  PDmt = ^TDmt;
  TDmt = packed record
    Count: word;
    Indicies: TDmtIndices; // really [0..Count-1]
    Methods : TDmtMethods; // really [0..Count-1]
  end;

  PClass = ^TClass;
  PSafeCallException = function  (Self: TObject; ExceptObject:
    TObject; ExceptAddr: Pointer): HResult;
  PAfterConstruction = procedure (Self: TObject);
  PBeforeDestruction = procedure (Self: TObject);
  PDispatch          = procedure (Self: TObject; var Message);
  PDefaultHandler    = procedure (Self: TObject; var Message);
  PNewInstance       = function  (Self: TClass) : TObject;
  PFreeInstance      = procedure (Self: TObject);
  PDestroy           = procedure (Self: TObject; OuterMost: ShortInt);
  PVmt = ^TVmt;

  TVmt = packed record
    SelfPtr           : TClass;
    IntfTable         : Pointer;
    AutoTable         : Pointer;
    InitTable         : Pointer;
    TypeInfo          : Pointer;
    FieldTable        : Pointer;
    MethodTable       : Pointer;
    DynamicTable      : PDmt;
    ClassName         : PShortString;
    InstanceSize      : PLongint;
    Parent            : PClass;
    equals            : pointer;
    GetHashCode       : pointer;
    ToString          : pointer;
    SafeCallException : PSafeCallException;
    AfterConstruction : PAfterConstruction;
    BeforeDestruction : PBeforeDestruction;
    Dispatch          : PDispatch;
    DefaultHandler    : PDefaultHandler;
    NewInstance       : PNewInstance;
    FreeInstance      : PFreeInstance;
    Destroy           : PDestroy;
    UserDefinedVirtuals: array[0..999] of procedure;
  end;

function GetVirtualMethodCount(AClass: TClass): Integer;
function GetVMT(AClass: TClass): PVMT; overload;
function GetVMT(AObject: TObject): PVMT; overload;

implementation

function GetVirtualMethodCount(AClass: TClass): Integer;
var
  BeginVMT: integer;
  EndVMT: integer;
  TablePointer: integer;
  I: Integer;
begin
  BeginVMT := integer(AClass);

  // Scan the offset entries in the class table for the various fields,
  // namely vmtIntfTable, vmtAutoTable, ..., vmtDynamicTable
  // The last entry is always the vmtClassName, so stop once we got there
  // After the last virtual method there is one of these entries.

  EndVMT := pinteger(integer(AClass) + vmtClassName)^;
  // Set iterator to first item behind VMT table pointer
  I := vmtSelfPtr + SizeOf(Pointer);
  repeat
    TablePointer := pinteger(integer(AClass) + I)^;
    if (TablePointer <> 0) and (TablePointer >= BeginVMT) and
       (TablePointer < EndVMT) then
      EndVMT := integer(TablePointer);
    Inc(I, SizeOf(Pointer));
  until I >= vmtClassName;

  Result := (EndVMT - BeginVMT) div SizeOf(Pointer);
end;


function GetVMT(AClass: TClass): PVMT; overload;
begin
  if assigned(AClass) then
  begin
    result := pvmt(integer(AClass) + vmtSelfPtr)
  end
  else
    Result := nil;
end;

function GetVMT(AObject: TObject): PVMT; overload;
begin
  if assigned(AObject) then
    result := pvmt(integer(AObject.ClassType) + vmtSelfPtr)
  else
    Result := nil;
end;

La respuesta la pregunta de Al se responde con la función GetVirtualMethodCount... aunque, como es de esperar, sólo devuelve los métodos declarados con la directiva "virtual", para los que estén declarados con "dinamic" ya es otro asunto.

A propósito, los métodos virtuales y abstractos (sin implementación por decirlo de algún modo), apuntan (tal como lo decía Al), a la función _AbstractError pero indirectamente... o sea que se puede hookear.
El puntero a interceptar está (como no podía ser de otra manera) en el System.pas:

AbstractErrorProc: procedure; { Abstract method error handler }

Allí, entonces, podremos colgar nuestra propia rutina para manejar el error, e inclusive llamar desde allí a la original.

sólo bastará declarar la función...

procedure CustomAbastractError;
begin
raise EAbstractError.Create('my custom abstract error');
end;

y asignarla:

AbstractErrorProc := CustomAbastractError;

Última edición por poyo fecha: 31-01-2009 a las 00:07:38. Razón: errores
Responder Con Cita
  #2  
Antiguo 31-01-2009
poyo poyo is offline
Miembro
 
Registrado: ene 2009
Posts: 47
Poder: 0
poyo Va por buen camino
para tener en cuenta

me olvidaba de algo importantísimo:
el código que puse arriba es para Delphi 2009...

En esta versión a la vmt han tocado. el system.pas dice:

Código Delphi [-]
{ Virtual method table entries }
  vmtSelfPtr           = -88;
  vmtIntfTable         = -84;
  vmtAutoTable         = -80;
  vmtInitTable         = -76;
  vmtTypeInfo          = -72;
  vmtFieldTable        = -68;
  vmtMethodTable       = -64;
  vmtDynamicTable      = -60;
  vmtClassName         = -56;
  vmtInstanceSize      = -52;
  vmtParent            = -48;
  vmtEquals            = -44 deprecated 'Use VMTOFFSET in asm code';
  vmtGetHashCode       = -40 deprecated 'Use VMTOFFSET in asm code';
  vmtToString          = -36 deprecated 'Use VMTOFFSET in asm code';
  vmtSafeCallException = -32 deprecated 'Use VMTOFFSET in asm code';
  vmtAfterConstruction = -28 deprecated 'Use VMTOFFSET in asm code';
  vmtBeforeDestruction = -24 deprecated 'Use VMTOFFSET in asm code';
  vmtDispatch          = -20 deprecated 'Use VMTOFFSET in asm code';
  vmtDefaultHandler    = -16 deprecated 'Use VMTOFFSET in asm code';
  vmtNewInstance       = -12 deprecated 'Use VMTOFFSET in asm code';
  vmtFreeInstance      = -8 deprecated 'Use VMTOFFSET in asm code';
  vmtDestroy           = -4 deprecated 'Use VMTOFFSET in asm code';

  vmtQueryInterface    = 0 deprecated 'Use VMTOFFSET in asm code';
  vmtAddRef            = 4 deprecated 'Use VMTOFFSET in asm code';
  vmtRelease           = 8 deprecated 'Use VMTOFFSET in asm code';
  vmtCreateObject      = 12 deprecated 'Use VMTOFFSET in asm code';

entonces, para versiones anteriores, al record TVmt hay que comentarle lo siguente:
equals : pointer;
GetHashCode : pointer;
ToString : pointer;

como verán arriba, hay unas cosas con offset positivo que todavía no las pusimo en el TVmt...
también desconozco que son los punteros nuevos.

se escuchan sugerencias
Responder Con Cita
  #3  
Antiguo 31-01-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 29
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Smile

¡Chanfle!

Ese tipo de cosas siempre han sido mis mayores motivaciones para usar nuevas versiones de Delphi (que ribbon controls, ni qué ocho cuartos ). Nada más de pensar que podría estar palpando esa nueva unidad System.pas...

No he probado la función GetVirtualMethodCount que pusiste, pero dudo que pueda funcionar en Delphi 7. Ayer estuve haciendo algunas pruebas con ésta versión y no hay un marcador seguro que señale la terminación de la VMT. Por cierto, esta es la documentación oficial que viene en la propia ayuda en esa y otras versiones anteriores:

Cita:
Delphi Language Reference
Class types

Topic Groups See Also

A class-type value is stored as a 32-bit pointer to an instance of the class, which is called an object. The internal data format of an object resembles that of a record. The object's fields are stored in order of declaration as a sequence of contiguous variables. Fields are always aligned, corresponding to an unpacked record type. Any fields inherited from an ancestor class are stored before the new fields defined in the descendant class.

The first 4-byte field of every object is a pointer to the virtual method table (VMT) of the class. There is exactly one VMT per class (not one per object); distinct class types, no matter how similar, never share a VMT. VMT's are built automatically by the compiler, and are never directly manipulated by a program. Pointers to VMT's, which are automatically stored by constructor methods in the objects they create, are also never directly manipulated by a program.

The layout of a VMT is shown in the following table. At positive offsets, a VMT consists of a list of 32-bit method pointers--one per user-defined virtual method in the class type--in order of declaration. Each slot contains the address of the corresponding virtual method's entry point. This layout is compatible with a C++ v-table and with COM. At negative offsets, a VMT contains a number of fields that are internal to Delphi's implementation. Applications should use the methods defined in TObject to query this information, since the layout is likely to change in future implementations of the Delphi language.

Virtual method table layout
Offset Type Description
-76 Pointer pointer to virtual method table (or nil)
-72 Pointer pointer to interface table (or nil)
-68 Pointer pointer to Automation information table (or nil)
-64 Pointer pointer to instance initialization table (or nil)
-60 Pointer pointer to type information table (or nil)
-56 Pointer pointer to field definition table (or nil)
-52 Pointer pointer to method definition table (or nil)
-48 Pointer pointer to dynamic method table (or nil)
-44 Pointer pointer to short string containing class name
-40 Cardinal instance size in bytes
-36 Pointer pointer to a pointer to ancestor class (or nil)
-32 Pointer pointer to entry point of SafecallException method (or nil)
-28 Pointer entry point of AfterConstruction method
-24 Pointer entry point of BeforeDestruction method
-20 Pointer entry point of Dispatch method
-16 Pointer entry point of DefaultHandler method
-12 Pointer entry point of NewInstance method
-8 Pointer entry point of FreeInstance method
-4 Pointer entry point of Destroy destructor
0 Pointer entry point of first user-defined virtual method
4 Pointer entry point of second user-defined virtual method
... ... ...
Aunque la System.pas de Delphi 7 presenta cuatro entradas estándares más, las cuales sólo se utilizan (si mal no recuerdo) cuando la clase implementa alguna interfaz (fueron introducidas cuando Delphi comenzó a soportar interfaces):
Código Delphi [-]
  vmtQueryInterface    = 0 deprecated;
  vmtAddRef            = 4 deprecated;
  vmtRelease           = 8 deprecated;
  vmtCreateObject      = 12 deprecated;
(NOTA: La directiva deprecated señala que esas constantes están cayendo en desuso, más no las entradas de VMT que representan).

Cita:
Empezado por poyo Ver Mensaje
A propósito, los métodos virtuales y abstractos (sin implementación por decirlo de algún modo), apuntan (tal como lo decía Al), a la función _AbstractError pero indirectamente... o sea que se puede hookear.
El puntero a interceptar está (como no podía ser de otra manera) en el System.pas:

AbstractErrorProc: procedure; { Abstract method error handler }

Allí, entonces, podremos colgar nuestra propia rutina para manejar el error, e inclusive llamar desde allí a la original.

sólo bastará declarar la función...

procedure CustomAbastractError;
begin
raise EAbstractError.Create('my custom abstract error');
end;

y asignarla:

AbstractErrorProc := CustomAbastractError;
Es lo que encontré ayer. Apuntan (pero directamente ) a la función _AbstractError, mas es el procedimiento indicado por la variable AbstractErrorProc el que define lo que hay que hacer, como bien señalas.

Algo curioso es que todos los métodos abstractos cuentan con su código máquina particular, que es simplemente un salto a la función _AbstractError. O sea que cada método abstracto tiene una dirección de memoria única (como cualquier otro tipo de rutina). Sin embargo, para estos casos, esas direcciones no son guardadas en las entradas de la VMT, sino que éstas almacenan la dirección de memoria de _AbstractError (una VMT con tres métodos abstractos contiene tres entradas de valor idéntico: la dirección de _AbstractError). ¿Entonces por qué genera el compilador código máquina particular con cada método abstracto? Tal parece que sólo para que tengan "identidad propia" a la hora de usarlos como valores procedimentales (@TClase.Metodo).

Esto de las entradas puntero a _AbstractError es lo que puede ayudar a saber, VMT mediante, si un método es abstracto o no, apoyándonos con ensamblador y el operador especial VMTOFFSET.

Cita:
Empezado por Ayuda de Delphi 7 (Assembly directives)
VMTOFFSET retrieves the offset in bytes of the virtual method pointer table entry of the virtual method argument from the beginning of the virtual method table (VMT). This directive needs a fully specified class name with a method name as a parameter (for example, TExample.VirtualMethod), or an interface name and an interface method name.
Pero, volviendo a la pregunta que originó este hilo (el cual es de agradecerse a Poyo), al menos en Delphi 7 no parece haber una forma segura de saber cuántos métodos virtuales tiene una clase hurgando en las entradas de la VMT. ¿O acaso sí?

Y aún más, ahora que recordé el asunto de las interfaces, creo que los métodos con que una clase las implementa son guardados también como entradas en desplazamientos positivos de la VMT. Según la propia ayuda, el operador VMTOFFSET también sirve con métodos de interfaces implementadas por la clase. Habría que hacer algunas pruebas para ver si esto último complicaría más el asunto en Delphi 7.

Nos vemos pronto, un saludo.

Al González.

Última edición por Al González fecha: 31-01-2009 a las 04:25:41.
Responder Con Cita
  #4  
Antiguo 31-01-2009
poyo poyo is offline
Miembro
 
Registrado: ene 2009
Posts: 47
Poder: 0
poyo Va por buen camino
Gracias Al por el pedazo de documentación. Siempre había tenido la sospecha (aunque nunca la había confirmado) de que las instancias comparten la VMT y NO poseen una copia para sí.

Cita:
Empezado por Al González Ver Mensaje
vmtQueryInterface = 0 deprecated;
vmtAddRef = 4 deprecated;
vmtRelease = 8 deprecated;
vmtCreateObject = 12 deprecated;
ahhh! claro! son de la implementación TInterfacedObject, IInterface...


Cita:
Empezado por Al González Ver Mensaje
No he probado la función GetVirtualMethodCount que pusiste, pero dudo que pueda funcionar en Delphi 7. Ayer estuve haciendo algunas pruebas con ésta versión y no hay un marcador seguro que señale la terminación de la VMT.

...

Pero, volviendo a la pregunta que originó este hilo (el cual es de agradecerse a Poyo), al menos en Delphi 7 no parece haber una forma segura de saber cuántos métodos virtuales tiene una clase hurgando en las entradas de la VMT. ¿O acaso sí?
En la versión 2009 (y creo que en ninguna otra) tampoco hay nada indicador de la terminación de la vmt. Lo que la función es pura aritmética de puntero. saca cuentas con las direcciones de memoria para deducir el final (hace una diferencia y lo divide por 4 (size of pointer en realidad).
Según lo que he visto, todos lo resuelven así. No lo he visto implementado de ninguna otra manera... ni se me ocurre como.
Y sí... es un poco turbio, pero al menos a mí me funciona. Creo que debería funcionar en versiones anteriores también, pues se viene usando desde hace mucho.

--------

Con respecto a _AbstractError, tienes absoluta razón! Apuntan allí y desde allí se fija si está asignado AbstractErrorProc y, si es así, la llama, sino, sigue con "_RunError", rutina famosa que muestra los cartelos "Runtime Error" y luego termina en la función Halt0 (que se encarga de terminar la aplicación).

debo ser la falta de sueño, de ansiolíticos o el exceso de horas en la máquina! (o la suma de todo. jejejeje)

Estuve investigando algo más... algo de eso ya me había cruzado.
En en INITIALIZATION del SysUtils.pas se llama (entre otras cosas) a el procedure InitExceptions que se encarga de inicializar la variable AbstractErrorProc del System.pas (y así también de otros errores como ser assert).

El manejador de errores de Abstracción es sencillo:

procedure AbstractErrorHandler;
begin
raise EAbstractError.CreateRes(@SAbstractError);
end;

la asignación también pero lo que me llama la atención son unos comentarios que hay alrededor de tal asignación y el IFNDEF:

{$IFNDEF PC_MAPPED_EXCEPTIONS}
// We don't hook this under PC mapped exceptions, because
// we have no idea what the parameters were to the procedure
// in question. Hence we cannot hope to unwind the stack in
// our handler. Since we just throw an exception from our
// handler, that pretty much rules out using this without
// exorbitant compiler support. If you do hook AbstractErrorProc,
// you must make sure that you never throw an exception from
// your handler if PC_MAPPED_EXCEPTIONS is defined.
AbstractErrorProc := @AbstractErrorHandler;
{$ENDIF}

Supongo que PC_MAPPED_EXCEPTIONS se trata de algo heredado del kylix y su compatibilidad con linux...

Cita:
Empezado por Al González Ver Mensaje
Algo curioso es que todos los métodos abstractos cuentan con su código máquina particular, que es simplemente un salto a la función _AbstractError. O sea que cada método abstracto tiene una dirección de memoria única (como cualquier otro tipo de rutina). Sin embargo, para estos casos, esas direcciones no son guardadas en las entradas de la VMT, sino que éstas almacenan la dirección de memoria de _AbstractError (una VMT con tres métodos abstractos contiene tres entradas de valor idéntico: la dirección de _AbstractError). ¿Entonces por qué genera el compilador código máquina particular con cada método abstracto? Tal parece que sólo para que tengan "identidad propia" a la hora de usarlos como valores procedimentales (@TClase.Metodo).
No entiendo bien lo que planteas aquí.
A ver si comprendo: una entrada de una función virtual en una VMT, si esta es abtracta, apuntará a _AbstractError. Cada Método Abstracto tiene un dirección de memoria particular (corresponfiende al Offset dentro de un array de punteros a métodos, no?).
La pregunta es por qué hay un elementos en la el array que no están implementados? (es decir, apuntan a _AbstractError)?

Siguiendo con la experimentación, querer reemplazar el puntero de un método virtúal de la VMT de la suguiente manera:

vmt^.UserDefinedVirtuals[x] := @TForm1.MyMethod;

me di cuenta de que no se podía por algo... ARROJABA UNA EXCEPCION!
Tras de mirar y mirar si estaba haciendo algo mal, caí en que los Administradores de Memoria (memory managers) de los sistemas operativos que trabajan con microprocesadores que operan en modo protegido, marcan a las páginas de memoria con atributos especiales haciendo que estas se puedan (o no) leer, escribir y/o ejecutar. Claro que todo esto depende del microprocesador y/o sistema operativo... en la actualidad creo que ya todos los microsprocesadores lo soportan y el windows lo viene soportando desde hace rato... no tengo idea.

La cuestión es que implementé una función que se encarga de desproteger la vmt y otra que vuelve a protegerla, para dejarla todo como estaba... bueno, casi

Otra duda surge de acá: Qué pasaría si el DEP esta activado?

Nota: Data Execution Prevention, es algo que se agregó a partir del SP2 del WinXP. Claro que el micro lo tiene que soportar.

pd: urgar va con H... lo puse en el título y está mal. Se podrá cambiar?

pd2: Adjunto RttiUtils.pas
Archivos Adjuntos
Tipo de Archivo: zip RttiUtils.zip (2,3 KB, 4 visitas)
Responder Con Cita
  #5  
Antiguo 02-02-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 29
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
¡Hola Poyo!

Viendo este hilo de Aeff, me vino a la mente que podría existir una manera de sustituir un método virtual por otro método, para casos como ese, donde estando ya un vasto árbol de clases definido, la derivación de clases sería una solución poco efectiva (si el compilador soportara herencia insertada, otro gallo cantaría ).

Entonces recordé que habías mencionado algo sobre sustituir entradas de la VMT:
Cita:
Empezado por poyo Ver Mensaje
Siguiendo con la experimentación, querer reemplazar el puntero de un método virtual de la VMT de la siguiente manera:

vmt^.UserDefinedVirtuals[x] := @TForm1.MyMethod;

me di cuenta de que no se podía por algo... ARROJABA UNA EXCEPCION!
Tras de mirar y mirar si estaba haciendo algo mal, caí en que los Administradores de Memoria (memory managers) de los sistemas operativos que trabajan con microprocesadores que operan en modo protegido, marcan a las páginas de memoria con atributos especiales haciendo que estas se puedan (o no) leer, escribir y/o ejecutar. Claro que todo esto depende del microprocesador y/o sistema operativo... en la actualidad creo que ya todos los microsprocesadores lo soportan y el windows lo viene soportando desde hace rato... no tengo idea.

La cuestión es que implementé una función que se encarga de desproteger la vmt y otra que vuelve a protegerla, para dejarla todo como estaba... bueno, casi
Con esa premisa y habiendo encontrado este código: http://www.koders.com/delphi/fid7782...px?s=algorithm, que parece ser el mismo que amablemente anexaste en el mensaje anterior, realicé esta prueba:
Código Delphi [-]
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Procedure Proc1; Virtual;
    Procedure Proc2;
  end;

  TF2 = Class (TForm1)
    Procedure Proc1; Override;
  End;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Método virtual a sustituir
Procedure TForm1.Proc1;
Begin
  ShowMessage ('Executing Proc1');
End;

// Método que reemplazará a Proc1
Procedure TForm1.Proc2;
Begin
  ShowMessage ('Executing Proc2!');
End;

Procedure TF2.Proc1;
Begin
  Inherited Proc1;
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
  Address :Pointer;
  Dummy : DWord;
  Protection : DWord;
begin
  { TEMA: Cómo cambiar una entrada de la VMT de una clase, para que al
    realizarse una llamada a un método virtual, sea otro el método
    ejecutado. }

  Proc1;  // Esta sentencia llama a Proc1 (como es de suponerse)

  { Obtenemos la dirección de memoria que tiene la entrada del método
    virtual Proc1 en la VMT de la clase TForm1 }
  Asm
    Mov EAX, Self
    Mov EAX, [EAX]  // Dirección de la clase (VMT) del formulario (Self)
    Add EAX, VMTOffset TForm1.Proc1  // Desplazamiento de la entrada Proc1
    Mov Address, EAX
  End;

  { Desprotegemos los primeros cuatro bytes que hay a partir de esa
    dirección de memoria para poder cambiarlos }
  VirtualProtect (Address, SizeOf (Pointer), Page_ReadWrite, Protection);

  Try
    { Hacemos que la entrada de la VMT apunte ahora al método Proc2
      (sustitución de Proc1 por Proc2) }
    PPointer (Address)^ := @TForm1.Proc2;
  Finally
    { Restauramos el nivel de protección de la región de memoria
      modificada }
    VirtualProtect (Address, SizeOf (Pointer), Protection, @Dummy);
  End;

  Proc1;  // ¡Esta sentencia llama a Proc2!

  { PERO: En este caso no es efectivo el cambio porque la sentencia
    "Inherited", en el interior de TF2.Proc1, es una llamada resuelta en
    tiempo de compilación. }
  With TF2.Create (Nil) Do
    Try
      Proc1;
    Finally
      Free;
    End;

  Close;
end;

Seguramente ya habrás hecho algo similar. Pero, como podrás apreciar en el ejemplo, el principal problema es que no todas las llamadas a métodos virtuales son late binding, es decir, aquellas donde el programa determina en tiempo de ejecución a qué rutina va a saltar revisando primero de qué clase es el objeto.

¿Cuáles llamadas a métodos virtuales son resueltas en tiempo de compilación (realizadas "estáticamente")? Particularmente las que usan la palabra reservada Inherited, y también las que se llevan a cabo mediante variables procedimentales.

Entonces, creo que la utilidad práctica de sustituir una entrada VMT de método virtual sería aplicable, básicamente, a aquellos casos donde ya existen varias clases derivadas de la clase que implementa el método a sustituir, pero ninguna de esas clases descendientes ha redefinido dicho método, o, si alguna lo ha hecho, no usa Inherited o no nos interesa que para ella se ejecute el código original (y, claro está, no queremos o no podemos modificar el código fuente de la clase ancestro en cuestión).


Cita:
Empezado por poyo Ver Mensaje
No entiendo bien lo que planteas aquí.
A ver si comprendo: una entrada de una función virtual en una VMT, si esta es abtracta, apuntará a _AbstractError. Cada Método Abstracto tiene un dirección de memoria particular (corresponfiende al Offset dentro de un array de punteros a métodos, no?).
La pregunta es por qué hay un elementos en la el array que no están implementados? (es decir, apuntan a _AbstractError)?
Eso fue referente a mi observación de que cada método virtual, aunque sea abstracto, tiene su propio código máquina, su propia dirección de memoria. La única instrucción relevante que contiene el código máquina de un método abstracto es un salto a la función _AbstractError. En la ventana CPU del depurador podrás observar la instrucción "jmp @AbstractError", si dentro de ella haces el seguimiento de este código:

Código Delphi [-]
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Procedure Proc1; Virtual; Abstract;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Type
  TM = Procedure Of Object;
Var
  M :TMethod;
begin
//  Proc1;  // Llamada habitual

  M.Data := Self;
  M.Code := @TForm1.Proc1;
  TM (M);  // Llamada con variable procedimental
end;

El camino que seguirá el programa es distinto, dependiendo de si el enlace de llamada al método virtual es estático o tardío (late binding). Con Inherited y variables procedimentales, saltará a la dirección del método abstracto sin consultar la VMT; mientras que con una llamada tipo "Objeto.Método" sí leerá la entrada respectiva de la VMT. ¿Interesante no?


En cuanto a la función GetVirtualMethodCount, aún no me queda claro en qué se basa ésta para determinar que ha encontrado el fin de la VMT. El código, aún con comentarios, me resulta un poco confuso. Insisto en que tiene que haber una especie de marca o dato indicativo seguro en el cual pueda basarse la función para saber dónde termina la lista de entradas de la VMT, puesto que el tamaño de cada VMT es variable y, que yo sepa, no hay ningún lugar donde esté señalado el tamaño que tiene. O bien una regla de almacenamiento que no estoy percibiendo...

Saludos.

Al González.
Responder Con Cita
  #6  
Antiguo 03-02-2009
poyo poyo is offline
Miembro
 
Registrado: ene 2009
Posts: 47
Poder: 0
poyo Va por buen camino
Esto en vez de divertido parece ser un dolor de cabeza... jajajaja
Cada vez que aclaramos, oscurece... y termina siendo más estático de lo que me imaginaba.
Ahora entiendo algunas limitaciones de los ClassHelpers. Hasta allí llegan...

Para implementar herencia insertada o cosas por el estilo creo que los muchachos de codegear deberían de replantearse una buena parte del asunto. un arduo trabajo aunque, lo más difícil es decidirse a comenzar, no?
Una vez que estás en camino casi nunca resulta tan complicado como parececía antes de empezar.

No, no había hecho la prueba de reemplazar un método de la VMT y luego llamarlo mediante "inherited"... daba por hecho que esta función iba a buscar la VMT del ClassParent y allí resolvía el puntero a la función, pero resultó no ser así.
es una lástima porque se hubiesen podido hacer cosas interesantes.

Es muy peciliar el uso que se hace de la VMT. Parece ser que al final de cuentas termina siendo sólo realmente útil para la persistencia y alguna que otra cosita más.

Con respecto a las llamadas (donde simplemente se hace un Jump):
...
jmp dword prt [$XXXXXXXX]
mov eax, eax
...

Se me hace que estas direcciones de memoria (que apuntan a las funciones en cuestión) son llenadas en runtime en el LoadPackage mediante GetProcAddress. Algo similar pasa con las variables globales cuando son compiladas para usarse dinámicamente.

En mi caso había una "tabla" de que saltos a funciones en los cuales estaban todos estos juntos (con rtl en runtime):

abstract error
tobject.initinstance
tobject.freeinstance
tobject.free
tobject.equals
tobject.gethashcode
tobject.tostring
tobject.dispatch
handlefinally
startexe
halt0
registermodule
finalization

Volviendo a GetVirtualMethodCount, a mí tampoco me queda del todo claro. :S
Sí cómo es obtenido y eso, pero exactamente porqué.
Sé que juegan con punteros. Toman el final de la vmt, luego recorren las entradas buscando las direcciones de los punteos, buscan la más alta, hacen la diferencia, dividen por 4 y les da. (más magia negra!) pero porqué esos punteros y no otros?
A mi entender, se basan en información que no he encontrado o no está disponible (en el orden en que es asignada (alloc) la memoria de la tabla de métodos virtuales).
Es decir, SABEN con seguridad que hay un puntero que se contiguo a la tabla de métodos virtuales.
Responder Con Cita
  #7  
Antiguo 03-02-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 29
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Cita:
Empezado por poyo Ver Mensaje
Volviendo a GetVirtualMethodCount, a mí tampoco me queda del todo claro. :S
Sí cómo es obtenido y eso, pero exactamente porqué.
Sé que juegan con punteros. Toman el final de la vmt...
Tengo una idea de dónde está la clave de eso, era algo que sólo suponía pero no había visto con detenimiento esta sentencia:
Código Delphi [-]
EndVMT := pinteger(integer(AClass) + vmtClassName)^;
Tal parece que la estructura de la VMT finaliza con el ShortString que contiene el nombre de la clase, lo cual es bastante lógico. Es información intrínseca de la VMT, pero, como es de longitud variable, en el desplazamiento vmtClassName sólo guardan un puntero a ese ShortString, así la estructura inicial permanece uniforme para todas las VMTs y rematan estas estructuras con el propio nombre de la clase. Es una técnica bastante usual colocar al final de una estructura las partes que pueden ser de tamaño variable.

No lo he probado, pero entonces con esto sí podríamos saber cuántas entradas de métodos virtuales y de métodos de interfaces tiene una VMT. Ahora la pregunta sería, ¿cómo distinguir unas de otras? Y algo más de considerar: la función GetVirtualMethodCount que enlacé parece ser de una versión más reciente que la mostrada inicialmente, y en ella el algoritmo es significativamente distinto.

Habrá que hacer algunas pruebas adicionales en un rato libre...

Espero te alivies de los dolores de cabeza, Poyo, tómalo con calma. Son simples bytes.

Me entusiasma participar con los colegas en este tipo de disertaciones.

Saludos.

Al González.
Responder Con Cita
Respuesta



Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro


La franja horaria es GMT +2. Ahora son las 16:03:20.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi