Delphi 10.4.2 IDE Attach to process... 到宿主进程时死锁的原因与修复
Delphi 10.4.2 IDE在附加到进程时死锁问题的分析与修复 问题现象: 当使用Delphi 10.4.2 IDE(bds.exe)通过"Attach to Process"附加到第三方进程时,IDE和目标进程会同时卡死。目标进程所有线程被挂起,约30-60秒后崩溃。 根本原因: 调试内核bordbk270N.dll在处理新线程事件时,会无条件调用advapi32!GetThreadWai

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 esi → xor eax, eax(不阻塞,eax=0) |
| 0x39F26 | 75 3C |
EB 3C |
jne +3Ch → jmp +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 成功路径 ; 无条件跳到成功路径
两处修改协同工作:
call esi→xor eax,eax:跳过阻塞调用,eax 置0jne→jmp:原本 eax=0 会走失败路径(失败路径会访问未初始化的输出缓冲区导致 AV),强制改为无条件跳转走成功路径,绕过对GetThreadWaitChain输出数据的访问
注意事项
- 补丁后 Delphi 线程状态窗口(Thread Status)中目标进程的线程等待链信息将不再显示,其余调试功能正常
- 每次 Delphi 更新后需重新确认偏移是否有效
- 此问题在 Delphi 11+ 版本中是否已修复未经验证
排查工具
- WinDbg x86(6.3.9600 或更新)
- Python 3.x(用于文件补丁)
- Process Explorer(观察线程状态)
更多推荐

所有评论(0)