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
变量引用的可执行代码?我猜那将是一个记忆位置。从那里可以计算在该内存位置找到的可执行代码的哈希值?
答案 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;