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

时间:2010-04-12 15:17:40

标签: delphi delphi-5

我使用Delphi Sampling Profiler分析了我的部分应用程序。 Like most people,我看到ntdll.dll内部花费了大部分时间。

  

注意:我将选项设置为忽略 Application.Idle时间,并从System.pas拨打电话。所以   不在ntdll内,因为   应用程序闲置:

     

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

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

enter image description here

来电者来自Virtual Treeview:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    
  

注意:该应用程序不在ntdll.dll内,因为   应用程序是空闲的,因为   来电者不是Application.Idle

让我感到困惑的是,本身这一行(即 PrepareCell中的内容)是调用者ntdll。更令人困惑的是:

  • 不仅 内的 <{strong> PrepareCell()
  • 它甚至不是PrepareCell setup (例如弹出堆栈变量,设置隐式异常帧等),这是调用者。这些内容会在Profiler中显示为PrepareCell内begin的热点。

VirtualTrees.pas:

procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
begin
   ...
end;

所以我想弄清楚这一行:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    

正在调用ntdll.dll


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

  • PaintInfo
  • Window.Left
  • NodeBitmap.Width

可能其中一个是调用ntdll的函数或属性获取器。所以我在线上放了一个断点,并在运行时查看CPU窗口:

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

那里的 一行可能是罪魁祸首:

call dword ptr [edx+$2c]

但是,当我按照跳转时,它不会在ntdll.dll中结束,而是TBitmap.GetWidth

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

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


那么这条线是怎么回事:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    

调用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;

当你真正看到它时:

USER32.WaitMessage
  mov eax,$00001226
  mov edx,$7ffe0300
  call dword ptr [edx]
  ret

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

这是你如何进入ntdll.dll:你打电话给它。

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


更新三

我转换了两个参数:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);

进入堆栈变量:

_profiler_WindowLeft := Window.Left;
_profiler_NodeBitmapWidth := NodeBitmap.Width;
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);

要确认瓶颈不是,请致电

  • Windows.Left
  • Nodebitmap.Width

Profiler仍然表示该行

PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);

本身就是瓶颈;没有任何内部 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

没有任何内容可以调用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]

没有任何内容可以调用ntdll.dll


问题仍然存在:

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

2 个答案:

答案 0 :(得分:3)

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

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

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

ntdll.dll!KiFastSystemCallRet

您可以在Process Explorer中使用线程堆栈视图,或在Delphi中,或在AsmProfiler的“实时视图”中使用StackWalk64 API来查看: http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer

答案 1 :(得分:0)

那里可能发生了两件事。

第一个是SamplingProfiler通过向上移动堆栈来识别调用者,直到它从Delphi代码中遇到看起来像Delphi的有效调用点。

问题是,某些程序可能会立即保留大量堆栈,而无需重新初始化它。这可能会导致误报。唯一的线索就是最近调用了你的假阳性。

第二个事情是ntdll本地化,这是肯定的,但是,ntdll是你在用户空间的等待点,而作为user197220,ntdll就是你要去的地方最终等待大部分时间你正在调用系统并等待结果。

在您的情况下,除非您降低采样率,否则您将看到247ms的CPU工作时间,如果这些247个样本是在几秒钟的实时时间内收集的,则可能会空闲。由于假阳性指向VirtualTree油漆准备,我的赌注是ntdll时间实际上是油漆时间(驱动程序或操作系统软件)。 您可以尝试注释掉实际完成绘画的代码。