你怎么能找到谁在delphi程序中创建所有线程?

时间:2011-01-31 20:17:35

标签: multithreading delphi debugging

如果你有一套干净的Delphi代码,并且所有线程都是使用TThread创建的,你可以在构造函数方法(TThread.Create)中设置一个断点,并找出谁创建了你的线程。您甚至可以尝试使用Delphi TThread对象中内置的功能命名所有线程,该功能允许您为每个线程设置调试名称。

但是,如何确定持久的,难以找到的额外线程,这些线程仍然是匿名的(无调试名称),并且在模块初始化时出现,因为应用程序启动时。我可以单步执行模块初始化,但我无法确定可能创建线程的所有源模块(例如,已完成的900多个模块初始化部分),我还没有想出办法添加一个调试消息(使用断点属性和消息),它将在初始化时转储每个单元名称。在System.pas中设置断点的创造性使用,使用logging-messages允许我在调试简单的简单应用程序时做一些事情,但是我的应用程序越复杂,我就越觉得线程盲目,两者都是在中间创建的应用程序运行,以及在模块初始化时创建的那些(即在您进入项目dpr中代码的第一行之前)。

我想知道您可能找到哪些高级技术来识别并找出创建特定线程的人员。如果我们使用像GDB这样的调试器而不是像delphi IDE中内置的delphi调试器内核(Turbo Debugger?)这样的调试器,我认为我们可以在windows api函数上设置一个断点,比如BeginThread本身。但我不认为我能在Delphi中做到这一点。

更新:我不知道你可以在windows.pas的实现部分为kernel32.dll这样的外部windows dll设置一个断点。

更新2:似乎David H的回答是一般用途的最佳选择。我也正在研究一个我正在编写的小帮助代码库,它维护一个之前已经看过的线程id的字典,并根据它们的创建时间将一些调试名称分配给其他未命名的线程(函数是什么)我们在注意到新线程存在之前就已经调用了。我认为这将帮助我缩小40多个编号的线程,以便它们都被命名,即使其中一些是在外部C / C ++ dll或COM进程中创建的。

3 个答案:

答案 0 :(得分:9)

我可能希望使用像Process ExplorermadExcept这样的工具,但是有很多工具可以提供帮助。

我不相信Delphi使用Turbo Debugger。更重要的是,Delphi完全能够在像CreateThread这样的kernel32入口点上设置断点。

我会在启用Debug DCU的情况下运行,并在Windows.pas中设置CreateThread实现的断点。一旦中断,切换到CPU窗口并进入例程。你会看到JMP DWORD PTR [address]指令。跳过这个,嘿presto,你现在正在kernel32中调试。你可以在这里设置一个断点。

现在,如果您重置应用程序并再次开始调试,那么您将打破对来自您的进程的kernel32.CreateThread的所有调用。检查调用堆栈将告诉您如何到达那里。它看起来像这样:

enter image description here

最后,我不确定为什么你的应用创建线程会让你感到困扰。大多数体面的应用程序创建了大量的线程 - 这样做是完全正常的。你遇到了什么问题?

答案 1 :(得分:6)

  

...我想我们可以在windows api函数上设置一个断点    BeginThread本身。但我不认为我能在Delphi中做到这一点。

当然可以。

  • 启用项目,选项,Delphi编译器,编译,调试,使用调试.dcus。 (这是在Delphi XE中找到它的方法,不同Delphi版本的确切位置可能不同)。

  • 重新编译。

  • 打开系统单元并在BeginThread函数中的Result:= CreateThread ...上放置断点。

  • 运行程序并等待直到触发断点。

  • 打开CPU窗口(查看,调试Windows,CPU Windows,整个CPU)。

CPU窗口将显示如下内容:

System.pas.16559: Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
00406A97 8B4508           mov eax,[ebp+$08]
00406A9A 50               push eax
00406A9B 8B450C           mov eax,[ebp+$0c]
00406A9E 50               push eax
00406A9F 53               push ebx
00406AA0 B81C6A4000       mov eax,$00406a1c
00406AA5 50               push eax
00406AA6 8B45F8           mov eax,[ebp-$08]
00406AA9 50               push eax
00406AAA 8B45FC           mov eax,[ebp-$04]
00406AAD 50               push eax
00406AAE E855BBFFFF       call CreateThread
00406AB3 8BF0             mov esi,eax
  • 点击“调用CreateThread”一行进入CPU窗口。

  • 按F4。

  • 按F7。

您将被定位在调度表中:

CreateThread:
00402608 FF2594AA4F00     jmp dword ptr [$004faa94]
0040260E 8BC0             mov eax,eax
  • 按F5键将断点放在此处。

  • 重新运行程序(Ctrl-F2,F9)。

每次创建线程时都会触发断点。断点似乎发生在

的WindowsAPIs.INC中
function CreateThread(SecurityAttributes: Pointer; StackSize: LongWord;
                     ThreadFunc: TThreadFunc; Parameter: Pointer;
                     CreationFlags: LongWord; var ThreadId: LongWord): Integer; stdcall;
  external kernel name 'CreateThread';

(至少在Delphi XE中)。

可能仍然会错过一些线程创建调用。我不知道这个方法是否会捕获Direct X内部创建的线程,例如。

答案 2 :(得分:1)

实际上,BeginThread是System.pas中的一个函数,虽然是“私有”函数(接口部分没有函数声明)。因此,使用debug dcu,您只需在BeginThread函数中设置断点并从那里检查堆栈跟踪。

另一个选择是使用madCodeHook或KBSM(IIRC)挂钩BeginThread函数。在挂钩功能中,您可以使用以下内容:

  UseOurStuff := Assigned(Parameter) and IsInstanceOfType(Parameter, TThread);
  if Assigned(Parameter) then
    if UseOurStuff then
      ThreadClassName := Instance.ClassName
    else
      ThreadClassName := 'Non-object Parameter thread'
  else
    ThreadClassName := 'NIL Parameter thread';

因此您可以记录正在创建的所有线程,无论它们来自何处。您唯一缺少的是通过直接调用Windows API CreateThread创建的线程。但是你可以使用相同的挂钩技术来接听这些电话。

更新

哦,IsInstanceOfType是我们的库函数之一,但它基本上采用了无类型指针并检查它是否引用了给定类的对象。