主图

Delphi 10.4.2 IDE Attach 到宿主进程时死锁的根本原因与修复

问题描述

使用 Delphi 10.4.2 IDE(bds.exe)通过 Run → Attach to Process 附加到一个第三方宿主进程时,两个进程同时卡死:

  • bds.exe 无响应,Run 按钮灰置
  • 宿主进程所有线程被 suspend,无法恢复

等待约30~60秒后宿主进程崩溃,bds.exe 恢复。无法正常调试。

环境:

  • Delphi 10.4.2(BDS 21.0,版本号 27.0.40680.4203 )
  • 目标进程:32位 WOW64 进程
  • OS:64位 Windows 10/11

排查过程

第一步:用 WinDbg 抓取卡死时的线程栈

触发卡死后,开一个新的 WinDbg(x86版),attach 到卡死的 bds.exe,运行:

~* kb

Thread 0 的栈帧显示(无符号时栈展开不可信,需要手动沿 EBP 链追踪):

ntdll!ZwWaitForSingleObject
  advapi32!GetThreadWaitChain+0x17c
    bordbk270N!isDbkLoggingOn$qv+0x2faae
      dbkdebugide270!DebugTProcessntfyNewThread
        ...
        vcl270!VclFormsTApplicationRun

第二步:确认真实调用点

由于没有符号,~* kb 的栈展开不可靠(输出中有 WARNING: Stack unwind information not available)。需要手动沿 EBP 链追踪:

~0s
dds esp L8           ; 看栈顶
dds 0093d500 L4      ; 沿EBP链追踪
dds 0093d530 L4      ; 找到 bordbk270N 的返回地址

找到 bordbk270N 内的返回地址后,反汇编调用点:

u [返回地址-8] L6

输出:

push    0
mov     ecx, dword ptr [ebx+4]
push    ecx
call    esi                      ; ← 这里调用 GetThreadWaitChain
test    eax, eax
jne     +3Ch                     ; eax≠0 走成功路径

关键发现: 调用方式是 call esi(寄存器间接调用),不是 FF 15(IAT间接调用)也不是 E8(直接调用)。这是为什么常规的导入表搜索方法全部失败的原因。

第三步:理解死锁机制

attach 触发 DbgUiRemoteBreakin 注入目标进程
  → bordbk270N 收到"新线程创建"事件
    → 调用 GetThreadWaitChain 查询目标进程线程等待链
      → GetThreadWaitChain 内部向目标进程发出跨进程查询
        → 目标进程所有线程已被 suspend,无法响应
          → 永久等待 → 死锁

GetThreadWaitChain 是 Windows WCT(Wait Chain Traversal)API,需要目标线程配合响应。但 attach 时目标进程所有线程都被暂停,导致循环等待。

这是 bordbk270N.dll 的设计缺陷,在 attach 场景下调用 WCT 是不合适的。

第四步:定位文件偏移

bordbk270N.dll 的 PE 结构(Delphi BPL 特殊性):

Section [.text]: VA=0x1000, RAW=0x600, VSZ=0x54400, RSZ=0x0

注意:Delphi BPL 的 SizeOfRawData 字段为 0,标准 PE 解析工具会认为 section 为空,必须用 VirtualSize 代替。

call esi 的 RVA 每次运行不同(ASLR),但两次抓包确认 RVA 稳定为 0x3A924

文件偏移 = RVA - VirtualAddress + PointerToRawData
         = 0x3A924 - 0x1000 + 0x600
         = 0x39F24

验证文件偏移处的字节:

python -c "
data = open(r'C:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bordbk270N.dll','rb').read()
print(data[0x39F24:0x39F2A].hex())
"

输出:ffd685c0753c,对应:

FF D6       ; call esi
85 C0       ; test eax, eax
75 3C       ; jne +3Ch

根本原因

bordbk270N.dll(Delphi 调试内核)在处理 attach 时的"新线程"事件中,无条件调用 advapi32!GetThreadWaitChain 查询目标进程线程状态。该调用需要目标进程线程响应跨进程 WCT 查询,但 attach 时目标进程所有线程均已 suspend,导致死锁。

此行为硬编码在 bordbk270N.dll 中,无法通过注册表或 IDE 选项关闭。


修复方法

bordbk270N.dll 进行二进制补丁,跳过 GetThreadWaitChain 调用并强制走成功路径。

适用版本: Delphi 10.4.2,bordbk270N.dll MD5 = b8b5312dd53e50e3265845f05586654f,文件大小 546208 字节。

补丁内容

文件偏移 原始字节 补丁字节 说明
0x39F24 FF D6 31 C0 call esixor eax, eax(不阻塞,eax=0)
0x39F26 75 3C EB 3C jne +3Chjmp +3Ch(强制走成功路径)

补丁脚本

管理员身份运行:

import shutil, os

DLL = r"C:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bordbk270N.dll"

# 备份
bak = DLL + ".bak"
if not os.path.exists(bak):
    shutil.copy2(DLL, bak)
    print("backup:", bak)

data = bytearray(open(DLL, "rb").read())

# 验证
assert data[0x39F24:0x39F2A].hex() == "ffd685c0753c", "字节不匹配,版本不符"

# 打补丁
data[0x39F24] = 0x31  # xor eax, eax (高字节)
data[0x39F25] = 0xC0  # xor eax, eax (低字节)
data[0x39F26] = 0xEB  # jmp (无条件跳转)
# 0x39F27 保持 0x3C 不变

open(DLL, "wb").write(data)
print("patched:", bytes(data[0x39F24:0x39F2A]).hex())
print("done - restart Delphi")

还原方法

copy "C:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bordbk270N.dll.bak" "C:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bordbk270N.dll"

补丁原理说明

; 原始代码
FF D6           call esi          ; 调用 GetThreadWaitChain → 死锁
85 C0           test eax, eax
75 3C           jne  成功路径      ; eax≠0 跳走

; 补丁后
31 C0           xor eax, eax      ; 不调用,直接设 eax=0
85 C0           test eax, eax     ; eax=0,ZF=1
EB 3C           jmp  成功路径      ; 无条件跳到成功路径

两处修改协同工作:

  1. call esixor eax,eax:跳过阻塞调用,eax 置0
  2. jnejmp:原本 eax=0 会走失败路径(失败路径会访问未初始化的输出缓冲区导致 AV),强制改为无条件跳转走成功路径,绕过对 GetThreadWaitChain 输出数据的访问

注意事项

  1. 补丁后 Delphi 线程状态窗口(Thread Status)中目标进程的线程等待链信息将不再显示,其余调试功能正常
  2. 每次 Delphi 更新后需重新确认偏移是否有效
  3. 此问题在 Delphi 11+ 版本中是否已修复未经验证

排查工具

  • WinDbg x86(6.3.9600 或更新)
  • Python 3.x(用于文件补丁)
  • Process Explorer(观察线程状态)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐