【问题标题】:Is it possible to get a hash of an anonymous routine's implementation?是否可以获得匿名例程实现的哈希?
【发布时间】:2014-01-30 02:50:48
【问题描述】:
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 变量引用的可执行代码?我想那将是一个内存位置。然后可以计算在该内存位置找到的可执行代码的哈希值吗?

【问题讨论】:

  • 也许我已经在岩石下生活了 20 年,但是“计算哈希值”是什么意思?您的意思是您想通过某种校验和算法运行该过程的源代码吗?你能解释一下为什么吗。我当然乐于学习新事物,而且我很好奇。
  • “你的意思是你想通过某种校验和算法运行那个过程的源代码吗?”是的,没错。
  • 很有趣,但是为什么? (只是好奇)
  • 在这种情况下,我想将其作为编写 Debounce 函数的一部分。 stackoverflow.com/questions/21433336/… 它在复制保护例程中也可能很有用,以确保程序没有被修补。
  • 您正在尝试获取匿名函数的标识符。请注意,仅对可执行代码进行散列是不够的。您还需要包括捕获的变量。例如,考虑MakeAdder example from the documentationMakeAdder(1)返回的方法的代码部分显然会和MakeAdder(2)的代码相同,但是它们捕获的变量值不同,所以不应该判断它们是相同的。

标签: delphi


【解决方案1】:

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

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

首先,可以将匿名方法视为对与单个Invoke 方法的接口的引用,如Barry Kelly 所述。

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

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;

【讨论】:

  • 这仅适用于您的情况。匿名方法之所以称为方法,是因为它们是方法。所以它们由一个代码和一个数据指针组成。当您捕获变量时,这很重要(这就是存根方法执行 add eax 以更改接口引用的原因,哪些匿名方法在后台编译器生成的对象引用)。​​
【解决方案2】:

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

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;

这是一个简单而幼稚的实现,它假设偏移量永远不会大于一个字节(只有在同一例程中有数百个不同的匿名方法时才会发生这种情况(就像问题的原始源中有 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;

【讨论】:

    猜你喜欢
    • 2017-02-09
    • 1970-01-01
    • 1970-01-01
    • 2017-01-02
    • 1970-01-01
    • 2012-12-19
    • 2013-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多