是否有可能获得匿名例程的实现哈希?

时间:2014-01-30 02:50:49

标签: delphi

procedure DoSomething;
var
  MyAnonymousProcedure : TProc;
begin
  //assign an anonymous procedure to a variable.
  MyAnonymousProcedure := procedure
  begin
    Foo;
  end;
  MyAnonymousProcedure(); //Call the newly assigned procedure.



  // do the same thing again but with a different anonymous method.
  MyAnonymousProcedure := procedure
  begin
    Bar;
  end;
  MyAnonymousProcedure();
end;

在上面的代码中有两个匿名过程。它们依次分配给相同的TProc变量。每个匿名过程中的代码明显不同。有没有办法找到MyAnonymousProcedure变量引用的可执行代码?我猜那将是一个记忆位置。从那里可以计算在该内存位置找到的可执行代码的哈希值?

2 个答案:

答案 0 :(得分:3)

  

有没有办法找到可执行代码   MyAnonymousProcedure变量引用?

总有“一种方法”,但在这种情况下很棘手。

首先,匿名方法可以被视为对Barry Kelly所解释的单个Invoke方法的接口的引用。

将这个想法应用到您的代码中我们得到:

procedure MethRefToProcPtr(const MethRef; var ProcPtr);
type
  TVtable = array[0..3] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  // 3 is offset of Invoke, after QI, AddRef, Release
  TMethod(ProcPtr).Code := PPVtable(MethRef)^^[3];
end;

不幸的是,返回的ProcPtr值并不是您想要的 - 它是一个存根代码的地址,用于修复接口引用(将接口引用转换为对象引用)并跳转到我们正在查找的地址对于。如果你跟踪ProcPtr指向的代码,你会发现类似的东西(Delphi XE,32位):

     add eax,-$10
     jmp FooBar

并在FooBar地址找到

     call Foo

     call Bar

取决于您的匿名方法的当前值。

我想现在获取FooBar地址的唯一方法是解析汇编程序jmp指令。


以下是我用于实验的代码:

procedure Foo;
begin
  Writeln('Foo');
end;

procedure Bar;
begin
  Writeln('Bar');
end;

procedure MethRefToProcPtr(const MethRef; var ProcPtr);
type
  TVtable = array[0..3] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  // 3 is offset of Invoke, after QI, AddRef, Release
  TMethod(ProcPtr).Code := PPVtable(MethRef)^^[3];
end;

procedure DoSomething;
var
  MyAnonymousProcedure : TProc;
  MyProc : procedure;

begin
  //assign an anonymous procedure to a variable.
  MyAnonymousProcedure := procedure
  begin
    Foo;
  end;
//  MyAnonymousProcedure(); //Call the newly assigned procedure.

  MethRefToProcPtr(MyAnonymousProcedure, MyProc);
  Writeln(Format('%p', [@MyProc]));
  Writeln(Format('%p', [@Foo]));
  MyProc;

  // do the same thing again but with a different anonymous method.
  MyAnonymousProcedure := procedure
  begin
    Bar;
  end;
//  MyAnonymousProcedure();

  MethRefToProcPtr(MyAnonymousProcedure, MyProc);
  Writeln(Format('%p', [@MyProc]));
  Writeln(Format('%p', [@Bar]));
  MyProc;
end;

答案 1 :(得分:3)

除了这里的另一个答案之外,还有一个例程,它将编译器生成的方法存根转换为匿名方法的编译器生成类的“实际”方法。

procedure MethodStubToMethod(const Method; var Result);
var
  offset: ShortInt;
begin
  offset := PByte(TMethod(Method).Code)[2];
  TMethod(Result).Code := PByte(TMethod(Method).Code) + 3;
  TMethod(Result).Data := PByte(TMethod(Method).Data) + offset;
end;

这是一个简单而天真的实现,假设偏移量永远不会超过一个字节(只有在同一个例程中有100个不同的匿名方法才会发生(就像你在问题中的原始源中有2个)

它假设存根的布局是这样的(它用于匿名方法afaik)

add eax, offset
jmp address

然后你可以写:

procedure MethRefToProcPtr(const MethRef; var ProcPtr);
type
  TVtable = array[0..3] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  // 3 is offset of Invoke, after QI, AddRef, Release
  TMethod(ProcPtr).Code := PPVtable(MethRef)^^[3];
  TMethod(ProcPtr).Data := Pointer(MethRef);
end;

procedure DoSomething;
var
  MyAnonymousProcedure: TProc;
  Method: procedure of object;
begin
  //assign an anonymous procedure to a variable.
  MyAnonymousProcedure := procedure
  begin
    Foo;
  end;
  MyAnonymousProcedure(); //Call the newly assigned procedure.
  MethRefToProcPtr(MyAnonymousProcedure, Method); //
  Method(); //same as calling the anonymous method
  MethodStubToMethod(Method, Method)
  Method(); // now we are calling the method directly on the object     
end;