简体   繁体   English

Delphi Assembler / RTTI-gurus:我可以获取函数中隐含的Result变量的内存地址和类型信息吗?

[英]Delphi Assembler/RTTI-gurus: Can I obtain the memory address and type info of the implied Result variable in a function?

Consider this typical code for method tracing (simplified for illustration): 考虑这种典型的方法跟踪代码(简化说明):

type
  IMethodTracer = interface
  end;

  TMethodTracer = class(TInterfacedObject, IMethodTracer)
  private
    FName: String;
    FResultAddr: Pointer;
    FResultType: PTypeInfo;
  public
    constructor Create(
      const AName: String;
      const AResultAddr: Pointer = nil;
      const AResultType: PTypeInfo = nil);
    destructor Destroy; override;
  end;

constructor TMethodTracer.Create(
  const AName: String;
  const AResultAddr: Pointer;
  const AResultType: PTypeInfo);
begin
  inherited Create();
  FName := AName;
  FResultAddr := AResultAddr;
  FResultType := AResultType;
  Writeln('Entering ' + FName);
end;

destructor TMethodTracer.Destroy;
var
  lSuffix: String;
  lResVal: TValue;
begin
  lSuffix := '';
  if FResultAddr <> nil then
    begin
      //there's probably a more straight-forward to doing this, without involving TValue:
      TValue.Make(FResultAddr, FResultType, lResVal);
      lSuffix := ' - Result = ' + lResVal.AsString;
    end;
  Writeln('Leaving ' + FName + lSuffix);

  inherited Destroy;
end;

function TraceMethod(
  const AName: String;
  const AResultAddr: Pointer;
  const AResultType: PTypeInfo): IMethodTracer;
begin
  Result := TMethodTracer.Create(AName, AResultAddr, AResultType);
end;

//////

function F1: String;
begin
  TraceMethod('F1', @Result, TypeInfo(String));
  Writeln('Doing some stuff...');
  Result := 'Booyah!';
end;

F1();

This is working as intended. 这是按预期工作的。 The output is: 输出是:

Entering F1 进入F1
Doing some stuff... 做一些事......
Leaving F1 - Result = Booyah! 离开F1 - 结果= Booyah!

I am now looking for a way to minimize the number of required parameters for the call to TraceMethod() , ideally allowing me to skip the Result -related arguments altogether. 我现在正在寻找一种方法来最小化调用TraceMethod()所需的参数数量,理想情况下允许我完全跳过与Result相关的参数。 I have no experience with assembler or stack layout myself but If I am not mistaken, judging by the "magic" I have seen other people do, at least the memory address of the implied magic Result -variable should be obtainable somehow, shouldn't it? 我自己没有使用汇编程序或堆栈布局的经验但是如果我没有弄错,从我见过的其他人所做的“魔法”判断,至少隐含的魔法Result变量的内存地址应该可以以某种方式获得,不应该它? And possibly one can work from there to get at its type info, too? 也许有人可以从那里得到它的类型信息吗?

Of course, if it were possible to even determine the name of the "surrounding" function itself that would eliminate the need for passing arguments to TraceMethod entirely... 当然,如果有可能甚至可以确定“周围”函数本身的名称,这将消除完全将参数传递给TraceMethod的需要......

I am using Delphi XE2, so all recently introduced language/framework features can be used. 我正在使用Delphi XE2,因此可以使用所有最近引入的语言/框架功能。

And before anyone mentions it: My actual code already uses CodeSite.EnterMethod / ExitMethod instead of Writeln -calls. 在任何人提到它之前:我的实际代码已经使用CodeSite.EnterMethod / ExitMethod而不是Writeln I am also aware that this simplified example cannot handle complex types and performs no error handling whatsoever. 我也知道这个简化的例子不能处理复杂的类型,也不会执行任何错误处理。

Your best bet is truly to just pass in @Result . 你最好的选择是真正传递@Result If you don't, then there's no guarantee that Result even has an address. 如果不这样做,则无法保证Result甚至地址。 Functions returning simple types like Integer and Boolean put the result in the EAX register. 返回简单类型(如IntegerBoolean函数将结果放入EAX寄存器。 If there's no reason for the result to have an address, then the compiler won't allocate any memory for it. 如果没有理由让结果有地址,那么编译器就不会为它分配任何内存。 Using the expression @Result forces the compiler to give it an address. 使用@Result表达式强制编译器为其指定地址。

Merely knowing the address won't get you the return type, though. 但是,仅仅知道地址不会得到返回类型。 There might be a way to discover it with RTTI. 可能有一种方法可以通过RTTI发现它。 It would involve three steps: 这将涉及三个步骤:

  1. Extract the class name from the method name. 从方法名称中提取类名称。 Then you can get the RTTI for that type . 然后,您可以获得该类型的RTTI This would require the method name to include an unambiguous name for the class (including unit name). 这将要求方法名称包含类的明确名称(包括单元名称)。

  2. Using the list of methods from that type, find the RTTI for the method. 使用该类型的方法列表 ,找到该方法的RTTI。 This will be complicated by the fact that the name doesn't necessarily uniquely identify a method. 由于名称不一定唯一地标识方法,因此这将变得复杂。 Overloads will all show the same name. 重载都将显示相同的名称。 (Rruz showed how to deal with RTTI of overloaded methods in the context of the Invoke method.) Also, the method name you get from the debug info won't necessarily match the RTTI name. (Rruz展示了如何Invoke方法的上下文中处理重载方法的RTTI 。)此外,从调试信息中获得的方法名称不一定与RTTI名称匹配。

    Instead of trying to match the name, you could instead loop over all the class's methods, searching for the one whose CodeAddress property matches the address of the caller. 您可以改为遍历所有类的方法,而不是尝试匹配名称,搜索CodeAddress属性与调用方地址匹配的方法。 Determining how to get the address of the start of the caller (instead of the return address) is proving more difficult to find than I expected, though. 然而,确定如何获得调用者的起始地址(而不是返回地址)比我预期的更难找到。

  3. Get the method's return type and use the Handle property to finally get the PTypeInfo value you want. 获取方法的返回类型并使用Handle属性最终获取所需的PTypeInfo值。

Some of this feature is already included in our TSynLog class , which works from Delphi 5 up to XE2. 其中一些功能已包含在我们的TSynLog类中该类从Delphi 5到XE2都有效。

It is full Open Source, and working with Delphi XE2, so you have all the needed source code at hand. 它是完全开源的,并且使用Delphi XE2,因此您可以获得所需的所有源代码。 And it features exception interception and stack trace. 它具有异常拦截和堆栈跟踪功能。

It allows coding trace like this: 它允许编码跟踪如下:

procedure TestPeopleProc;
var People: TSQLRecordPeople;
    Log: ISynLog;
begin
  Log := TSQLLog.Enter;
  People := TSQLRecordPeople.Create;
  try
    People.ID := 16;
    People.FirstName := 'Louis';
    People.LastName := 'Croivébaton';
    People.YearOfBirth := 1754;
    People.YearOfDeath := 1793;
    Log.Log(sllInfo,People);
  finally
    People.Free;
  end;
end;

It will be logged as such: 它将被记录为:

20120520 13172261  +    000E9F67 SynSelfTests.TestPeopleProc (784)
20120520 13172261 info      {"TSQLRecordPeople(00AB92E0)":{"ID":16,"FirstName":"Louis","LastName":"Croivébaton","Data":"","YearOfBirth":1754,"YearOfDeath":1793}}
20120520 13172261   -    000EA005 SynSelfTests.TestPeopleProc (794) 00.002.229

That is: 那是:

  • You have the line numbers and the method name; 您有行号和方法名称;
  • You have the exact time consummed within the method (00.002.229); 你有确切的时间在方法中完成(00.002.229);
  • It is very easy to display any result (here, the class is even serialized as JSON directly, and you can do the same with any kind of data, even records); 显示任何结果都非常容易(这里,该类甚至可以直接序列化为JSON,您可以对任何类型的数据执行相同的操作,甚至是记录);
  • Methods can be nested, and there is also a profiling tool available in our log viewer - that is, you can retrieve in which methods the most time is spent, on the customer side, from the log file; 方法可以嵌套,并且我们的日志查看器中还有一个分析工具 - 也就是说,您可以从日志文件中检索客户端花费最多时间的方法;
  • You can add a stack trace, or whatever information you want, very easily; 您可以非常轻松地添加堆栈跟踪或任何您想要的信息;
  • Our code is very optimized for speed: eg it uses an interface for the "auto-leave" function, just like you, but it won't make any memory allocation, since it uses a "fake reference count" trick; 我们的代码非常适合速度:例如,它使用“自动离开”功能的interface ,就像你一样,但它不会进行任何内存分配,因为它使用了“伪引用计数”技巧;
  • Of course, defining a Log variable on the stack is optional: if you need only to trace the Enter/Leave of the method, just writing TSQLLog.Enter is enough. 当然,在堆栈上定义Log变量是可选的:如果只需要跟踪方法的Enter / Leave,只需编写TSQLLog.Enter The Log local variable is used to easily nest additional information within the method ( Log.Log(sllInfo,People) ). Log局部变量用于在方法( Log.Log(sllInfo,People) )中轻松嵌套其他信息。

Note that the debugging information (ie the method name and line numbers) are retrieved from a proprietary very optimized binary conversion of the .map file generated at compile time. 请注意,调试信息(即方法名称和行号)是从编译时生成的.map文件的专有非常优化的二进制转换中检索的。 It will be much smaller than the .map itself (eg 900 KB .map -> 70 KB .mab which can be easily embedded within the exe), therefore smaller than the format used by JCL or MadExcept, and also smaller than the information embedded at compile time by Delphi. 这将是比小得多 .map本身(例如900 KB .map - >可以很容易地嵌入的exe内70 KB .mab),因此比由JCL或MadExcept使用的格式更小,并且也比嵌入的信息更小在Delphi编译时。

I do not think that having the "result" hard-coded within the "Enter" method is worth it. 我不认为在“Enter”方法中硬编码“结果”是值得的。 It will add a lot of coding (eg the conversion to TValue is time consuming), for a negligible benefit - most of the time, you'll need to know much more than the result content. 它会添加大量编码(例如转换为TValue非常耗时),但收益可以忽略不计 - 大部分时间,您需要了解的结果远远超过结果内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM