You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调试调用IUnknown::Release的汇编代码:影子空间是否正确?

64位Windows Intel平台COM父对象IUnknown::Release汇编代码崩溃问题排查

原始问题代码

48 8B 41 08            ; MOV RAX, [RCX + 0x08] gets the refcount
48 FF C8               ; DEC RAX               ; decrement refCount
48 89 41 08            ; MOV [RCX + 0x08], RAX ; update refCount in obj
48 83 F8 00            ; CMP RAX, 0
74 01                  ; JE +1                 ; (skips early return if RAX is zero)
C3                     ; RET                   ; (return from function)
51                     ; PUSH RCX              ; to cache it
48 8B 49 10            ; MOV RCX, [RCX + 0x10] ; Read the child object into RCX
48 83 F9 00            ; CMP RCX, 0            ; ensure the child object hasn't been set to Nothing
74 11                  ; JE 0x11               ; jump the next 17 bytes to avoid calling release
48 8B 01               ; MOV RAX, [RCX]        ; Get vtable pointer from [RCX] into RAX
48 8B 40 10            ; MOV RAX, [RAX + 0x10] ; Get IUnknown::Release function pointer from [RAX + 0x10] into RAX (QI + hex(ptr_size * 2) for Release)
48 83 EC 28            ; SUB RSP, 0x28         ; Allocate shadow space
FF D0                  ; CALL RAX              ; Call the function pointed to by RAX
48 83 C4 28            ; ADD RSP, 0x28         ; Deallocate shadow space
59                     ; POP RCX               ; to restore it to the parent object
48 B8 {addrCoTaskMemFree} ; MOV RAX, addrCoTaskMemFree
48 83 EC 28            ; SUB RSP, 0x28
FF D0                  ; CALL RAX
48 83 C4 28            ; ADD RSP, 0x28
48 31 C0               ; XOR RAX, RAX
C3                     ; RET

父对象内存布局

OffsetValueSize
0x00vtablePointer0x08(64位)
0x08refcount0x08(64位)
0x10childObjectPtr0x08(64位)

崩溃原因定位

1. 引用计数操作非原子,多线程环境下计数错误

当前代码通过MOV/DEC/MOV三步修改引用计数,不是原子操作。当多个线程同时调用Release时,会导致引用计数被错误覆盖,出现计数未正确递减、重复释放对象等问题。外部COM对象(如VBA.Collection)可能被多线程访问,更容易触发这类崩溃。

2. 调用子对象Release时违反x64调用约定的栈对齐要求

64位Windows x64调用约定明确要求:

  • 调用函数前,RSP必须对齐到16字节边界(CALL指令会压入8字节返回地址,函数入口时RSP会是8字节不对齐,符合函数内部要求)
  • 调用者需为被调用函数分配32字节(0x20)的影子空间

当前代码的栈操作逻辑:

  • PUSH RCX使RSP减少8字节,随后SUB RSP, 0x28再减少40字节,导致调用CALL RAX前RSP处于8字节不对齐状态。
  • 调用后子Release函数入口的RSP变为16字节对齐,违反约定。外部COM对象的Release函数可能依赖正确的栈对齐(如使用XMM寄存器操作栈数据),从而触发崩溃。

3. 调用CoTaskMemFree时的影子空间分配冗余

CoTaskMemFree仅需32字节影子空间,当前代码分配0x28(40字节)属于冗余操作,虽不直接崩溃,但不符合调用约定规范。

修复后的汇编代码

; 原子递减引用计数,保证多线程安全
LOCK DEC QWORD PTR [RCX + 0x08]
MOV RAX, [RCX + 0x08]       ; 获取递减后的引用计数
CMP RAX, 0
JE @DestroyObject           ; 计数为0,执行销毁逻辑
RET                         ; 计数不为0,直接返回

@DestroyObject:
; 保存父对象指针到影子空间,避免PUSH破坏栈对齐
MOV QWORD PTR [RSP - 0x20], RCX
; 加载子对象指针到RCX
MOV RCX, [RCX + 0x10]
CMP RCX, 0
JE @FreeParentObject        ; 子对象为空,直接释放父对象

; 准备调用子对象的Release:保证栈16字节对齐,分配标准32字节影子空间
SUB RSP, 0x28
; 获取子对象的Release函数指针
MOV RAX, [RCX]
MOV RAX, [RAX + 0x10]
CALL RAX                    ; 调用子对象Release
ADD RSP, 0x28               ; 恢复栈

@FreeParentObject:
; 恢复父对象指针到RCX
MOV RCX, [RSP - 0x20]
; 调用CoTaskMemFree:分配标准32字节影子空间
SUB RSP, 0x20
MOV RAX, addrCoTaskMemFree
CALL RAX
ADD RSP, 0x20

XOR RAX, RAX                ; 返回0(释放后的引用计数)
RET

修复说明

  1. 原子引用计数:使用LOCK DEC指令原子递减引用计数,确保多线程环境下计数准确。
  2. 栈对齐与影子空间:
    • 调用子对象Release和CoTaskMemFree时,分配标准32字节(0x20)影子空间。
    • 通过SUB RSP,0x28确保调用前RSP处于16字节对齐状态,符合x64调用约定。
    • 使用影子空间保存父对象指针,避免PUSH操作破坏栈对齐。
  3. 逻辑简化:改用标签式分支跳转,避免偏移计算错误,提升代码可读性。

内容的提问来源于stack exchange,提问作者Greedo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:40:55