Delphi 10.4.2 IDE Attach 到宿主进程时死锁的根本原因与修复问题描述使用 Delphi 10.4.2 IDEbds.exe通过Run → Attach to Process附加到一个第三方宿主进程时两个进程同时卡死bds.exe 无响应Run 按钮灰置宿主进程所有线程被 suspend无法恢复等待约30~60秒后宿主进程崩溃bds.exe 恢复。无法正常调试。环境Delphi 10.4.2BDS 21.0版本号 27.0.40680.4203 目标进程32位 WOW64 进程OS64位 Windows 10/11排查过程第一步用 WinDbg 抓取卡死时的线程栈触发卡死后开一个新的 WinDbgx86版attach 到卡死的 bds.exe运行~* kbThread 0 的栈帧显示无符号时栈展开不可信需要手动沿 EBP 链追踪ntdll!ZwWaitForSingleObject advapi32!GetThreadWaitChain0x17c bordbk270N!isDbkLoggingOn$qv0x2faae 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 [ebx4] push ecx call esi ; ← 这里调用 GetThreadWaitChain test eax, eax jne 3Ch ; eax≠0 走成功路径关键发现调用方式是call esi寄存器间接调用不是FF 15IAT间接调用也不是E8直接调用。这是为什么常规的导入表搜索方法全部失败的原因。第三步理解死锁机制attach 触发 DbgUiRemoteBreakin 注入目标进程 → bordbk270N 收到新线程创建事件 → 调用 GetThreadWaitChain 查询目标进程线程等待链 → GetThreadWaitChain 内部向目标进程发出跨进程查询 → 目标进程所有线程已被 suspend无法响应 → 永久等待 → 死锁GetThreadWaitChain是 Windows WCTWait Chain TraversalAPI需要目标线程配合响应。但 attach 时目标进程所有线程都被暂停导致循环等待。这是bordbk270N.dll的设计缺陷在 attach 场景下调用 WCT 是不合适的。第四步定位文件偏移bordbk270N.dll 的 PE 结构Delphi BPL 特殊性Section [.text]: VA0x1000, RAW0x600, VSZ0x54400, RSZ0x0注意Delphi BPL 的 SizeOfRawData 字段为 0标准 PE 解析工具会认为 section 为空必须用 VirtualSize 代替。call esi的 RVA 每次运行不同ASLR但两次抓包确认 RVA 稳定为0x3A924文件偏移 RVA - VirtualAddress PointerToRawData 0x3A924 - 0x1000 0x600 0x39F24验证文件偏移处的字节python -c data open(rC:\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.dllDelphi 调试内核在处理 attach 时的新线程事件中无条件调用advapi32!GetThreadWaitChain查询目标进程线程状态。该调用需要目标进程线程响应跨进程 WCT 查询但 attach 时目标进程所有线程均已 suspend导致死锁。此行为硬编码在bordbk270N.dll中无法通过注册表或 IDE 选项关闭。修复方法对bordbk270N.dll进行二进制补丁跳过GetThreadWaitChain调用并强制走成功路径。适用版本Delphi 10.4.2bordbk270N.dll MD5 b8b5312dd53e50e3265845f05586654f文件大小 546208 字节。补丁内容文件偏移原始字节补丁字节说明0x39F24FF D631 C0call esi→xor eax, eax不阻塞eax00x39F2675 3CEB 3Cjne 3Ch→jmp 3Ch强制走成功路径补丁脚本以管理员身份运行importshutil,os DLLrC:\Program Files (x86)\Embarcadero\Studio\21.0\bin\bordbk270N.dll# 备份bakDLL.bakifnotos.path.exists(bak):shutil.copy2(DLL,bak)print(backup:,bak)databytearray(open(DLL,rb).read())# 验证assertdata[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 ; 不调用直接设 eax0 85 C0 test eax, eax ; eax0ZF1 EB 3C jmp 成功路径 ; 无条件跳到成功路径两处修改协同工作call esi→xor eax,eax跳过阻塞调用eax 置0jne→jmp原本 eax0 会走失败路径失败路径会访问未初始化的输出缓冲区导致 AV强制改为无条件跳转走成功路径绕过对GetThreadWaitChain输出数据的访问注意事项补丁后 Delphi 线程状态窗口Thread Status中目标进程的线程等待链信息将不再显示其余调试功能正常每次 Delphi 更新后需重新确认偏移是否有效此问题在 Delphi 11 版本中是否已修复未经验证排查工具WinDbg x866.3.9600 或更新Python 3.x用于文件补丁Process Explorer观察线程状态