Delphi SampleProfiler:这段代码如何调用ntdll.dll?

Ian*_*oyd 8 delphi delphi-5

我用Delphi采样分析器描述了我应用程序的一部分.像大多数人一样,我看到大部分时间都花在了里面ntdll.dll.

注意:我打开选项忽略 Application.Idle时间,并从中调用System.pas.所以它不在里面,ntdll因为应用程序是空闲的:

alt text http://i40.tinypic.com/fkmc9j.jpg

在多次运行多次之后,大部分时间似乎花在了内部ntdll.dll,但奇怪的是调用者是谁:

在此输入图像描述

来电者来自Virtual Treeview:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    
Run Code Online (Sandbox Code Playgroud)

注意:应用程序不在内部,ntdll.dll因为应用程序处于空闲状态,因为调用者不是Application.Idle.

令我困惑的是,这条线本身(即不是PrepareCell 内部的东西)是调用者ntdll.更令人困惑的是:

  • 不仅不是内在的东西 PrepareCell()
  • 它甚至不是设置PrepareCell(如弹出堆栈变量,建立隐含例外帧等),这是主叫方.这些东西会在探查器中显示为beginPrepareCell内部的热点.

VirtualTrees.pas:

procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
begin
   ...
end;
Run Code Online (Sandbox Code Playgroud)

所以我想弄清楚这一行:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    
Run Code Online (Sandbox Code Playgroud)

正在打电话ntdll.dll.


其他唯一的方法是三个参数:

  • PaintInfo
  • Window.Left
  • NodeBitmap.Width

也许其中一个是函数或属性getter,它会调用ntdll.所以我在线上放了一个断点,并在运行时查看CPU窗口:

alt text http://i44.tinypic.com/2ut0pkx.jpg

还有就是在那里一条线,可能是罪魁祸首:

call dword ptr [edx+$2c]
Run Code Online (Sandbox Code Playgroud)

但是当我跟着那个跳跃时,它并没有结束ntdll.dll,但是TBitmap.GetWidth:

替代文字http://i44.tinypic.com/2uswzlc.jpg

正如你所看到的那样,在任何地方都不会打电话; 当然不是ntdll.dll.


那么这条线怎么样:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    
Run Code Online (Sandbox Code Playgroud)

呼唤ntdll.dll


注意:我完全知道它并没有真正调用ntdll.dll.所以任何有效的答案都必须包括"采样分析器误导;该行没有调用ntdll.dll".答案还必须要么说大多数时间没有花在ntdll.dll上,或者突出显示的行不是调用者.最后,任何答案都必须解释为什么Sampling Profiler是错误的,以及如何修复它.

更新2

什么是ntdll.dll文件?Ntdll是Windows NT的本机API集.Win32 API是一个包装器ntdll.dll,看起来像 Windows 1/2/3/9x中存在的Windows API.为了实际进入ntdll,你必须直接或间接调用一个使用ntdll的函数.

例如,当我的Delphi应用程序空闲时,它会通过调用user32.dll函数来等待消息:

WaitMessage;
Run Code Online (Sandbox Code Playgroud)

当你真正看到它的时候是:

USER32.WaitMessage
  mov eax,$00001226
  mov edx,$7ffe0300
  call dword ptr [edx]
  ret
Run Code Online (Sandbox Code Playgroud)

调用指定的函数$7ffe0300是Windows转换为Ring0的方式,调用EAX中指定的FunctionID.在这种情况下,被调用的系统函数是0x1226.在我的操作系统上,Windows Vista,0x1226对应于系统功能NtUserWaitMessage.

这是你如何进入ntdll.dll:你称之为.

当我说出原来的问题时,我正拼命地试图避免挥手无法回答.通过非常具体,仔细地指出我所看到的现实,我试图阻止人们忽视事实,并试图用挥手的论点.


更新三

我转换了两个参数:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Run Code Online (Sandbox Code Playgroud)

进入堆栈变量:

_profiler_WindowLeft := Window.Left;
_profiler_NodeBitmapWidth := NodeBitmap.Width;
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
Run Code Online (Sandbox Code Playgroud)

确认瓶颈不是呼叫

  • Windows.Left, 要么
  • Nodebitmap.Width

Profiler仍然表示该行

PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
Run Code Online (Sandbox Code Playgroud)

本身就是瓶颈; PrepareCell 里面没有任何东西.这必须意味着它是准备单元格的调用设置内部,或者在PrepareCell的开头:

VirtualTrees.pas.15746: PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
   mov eax,[ebp-$54]
   push eax
   mov edx,esi
   mov ecx,[ebp-$50]
   mov eax,[ebp-$04]
   call TBasevirtualTree.PrepareCell
Run Code Online (Sandbox Code Playgroud)

没有任何内容可以调用ntdll.现在预备在PrepareCell本身:

VirtualTrees.pas.15746: begin
   push ebp
   mov ebp,esp
   add esp,-$44
   push ebx
   push esi
   push edi
   mov [ebp-$14],ecx
   mov [ebp-$18],edx
   mov [ebp-$1c],eax
   lea esi,[ebp-$1c]
   mov edi,[ebp-$18]
Run Code Online (Sandbox Code Playgroud)

没有任何东西可以进入ntdll.dll.


问题仍然存在:

  • 为什么将一个变量推入堆栈,另外两个进入寄存器瓶颈?
  • 为什么PrepareCell内部没有任何内容成为瓶颈?

And*_*dré 3

好吧,这个问题实际上是我制作自己的采样分析器的主要原因:
http://code.google.com/p/asmprofiler/wiki/AsmProfilerSamplingMode

也许并不完美,但你可以尝试一下。让我知道你的想法。

顺便说一句,我认为这与几乎所有调用都以对内核的调用(内存请求、绘制事件等)结束这一事实有关。仅计算不需要调用内核。大多数调用都会以等待内核结果结束:

ntdll.dll!KiFastSystemCallRet
Run Code Online (Sandbox Code Playgroud)

您可以在带有线程堆栈视图的 Process Explorer 中看到这一点,或者在 Delphi 中,或者在我的 AsmProfiler 的“实时视图”中使用 StackWalk64 API:
http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer