XADD 指令如何修改零标志?

逆向工程 部件 x86
2021-07-09 01:41:00

我遇到了一组使用lock xadd我猜测来自 C++ 智能指针模板的指令(自从我编码 C++ 以来已经很长时间了,所以我不确定是哪个)。

lea ecx,dword ptr ds:[edi+0x4]   ; edi is a class object
or eax,0xFFFFFFFF
lock xadd dword ptr ds:[ecx],eax ; add -1 to edi[1]
jne sdk.1000C801
mov eax,dword ptr ds:[edi]       ; edi's vtable
mov ecx,edi                      ; prepare thiscall
call dword ptr ds:[eax]          ; call edi->vtable[0] ??

我可以看到 edi 的成员减少了,但是这个效果如何ZF,什么时候jne不会被占用?这部分代码永远不会执行,因为edi它始终为 0,所以我无法单步执行。

对我来说,它看起来像一个智能指针,当值变为 0 时,会调用析构函数。更让我困惑的是,其中有两个并排。第一个edi[1]edi[2]调用的第二个进行操作vtable[1]

2个回答

关于零标志和xadd指令的问题已经被josh充分回答了。此答案旨在添加有关您看到的智能指针的一些周围信息。

您看到的是 的典型代码shared_ptrC++ 标准库中标准引用计数智能指针。一个shared_ptr对象(即指针本身,而不是它指向的对象!)包含两个(!)指针值。第一个指针指向指针对象,第二个指针指向一个管理对象。管理对象包含 vtable 指针,后跟两个引用计数,后跟其他内容,具体取决于管理对象的特定类型(例如,如果删除器不是无状态的,则删除器的状态也包含在管理对象中)。管理对象中的第一个引用计数包含对对象的引用数(即shared_ptrs 指向该对象),而第二个引用计数包含对管理对象的引用数。所有强引用一起算作对管理对象的单个引用,但每个强引用都weak_ptr算作对管理对象的单独进一步引用。

每当(非空)shared_ptr超出范围时,强引用的计数就会减少。如果强引用计数为零,则指针生命周期结束,调用第一个 vtable 函数销毁指针。这将调用创建共享指针时指定的删除器(如果未指定删除器,则(间接)默认为delete(可能delete[]用于数组)。此外,删除对象后,所有强引用对管理对象的累积引用也减少。如果该引用计数达到零,则可以通过调用第二个 vtable 函数来销毁管理对象。

当(非空)weak_ptr超出范围时,只会减少对管理对象的引用计数(因为weak_ptr不携带强引用)。如果此引用计数达到零,则没有强引用(因为否则对管理的累积引用仍然存在),也没有其他weak_ptrs。当最后一个强引用超出范围时,指针已经被销毁,所以只有管理对象被销毁(再次,通过调用第二个 vtable 条目)。

当同时创建指针对象和管理对象时,存在优化的可能性。在这种情况下,可以在一个块中为两个对象分配内存。在这种情况下,管理对象不大于 vtable 并且两个引用计数并在指针之前。第一个 vfunction 只是调用指针的析构函数,但不释放其内存。第二个 vfunction 销毁包含管理对象和已销毁指针的内存块。shared_ptr使用make_shared. 这种优化的缺点是,当没有为指针对象的内存块才会被释放掉弱或强refences存在的,而不是已经那么正好,参考文献不见了。在 的大多数用例中shared_ptr,没有weak_ptrs,因此缺点通常无关紧要。

我认为您的一般评论是相当正确的(尽管不确定智能指针)。作为对 xadd 的提示:一个好的汇编程序参考是tripod,给出了全面的描述。作为一个补充,xadd 影响零标志。锁定前缀将其转换为原子指令。如果您的“智能指针”解释是正确的,则此锁将是保持引用计数清洁的必要条件。

以下从我的解释中略微增强了您的代码注释:

lea ecx,dword ptr ds:[edi+0x4]   ; edi is a class object
or eax,0xFFFFFFFF
lock xadd dword ptr ds:[ecx],eax ; add -1 to edi[1], eax obtains edi[1]
jne sdk.1000C801                 ; edi[1] != 1 ? yes -> sdk.1000c801
mov eax,dword ptr ds:[edi]       ; edi's vtable
mov ecx,edi                      ; prepare thiscall
call dword ptr ds:[eax]          ; call edi->vtable[0] 

在伪代码中,你可以这样写(寄存器值前面带有下划线):

int _eax = _edi[1];
if (_edi[1]-- != 1)
    goto sdk.1000c801;      //_eax has _edi[1] value, in case it's needed here
_edi->vtable[0] ();         //call first entry in vtable