4

I've come across a set of instructions using lock xadd which I'm guessing came from one of the C++ smart pointer templates. (It's been a very long time since I coded C++ so I'm not sure which).

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] ??

I can see a member of edi is decreased, but how does this effect ZF and when would the jne not be taken? This part of the code is never executed since edi is always 0, so I cannot step though.

To me it looks like a smart pointer, and when the value becomes 0 a destructor is called. To add to my confusion, there are two of these, side by side. The first operates on edi[1] the second on edi[2] which calls vtable[1].

2 Answers 2

3

I think your general commenting is rather correct (although not sure about the smart pointer). As a hint w/r to the xadd: A good assembler reference is tripod, giving a comprehensive description. Being an addition, xadd affects the zero flag. The lock prefix converts it into an atomic instruction. If your "smart pointer" interpretation is correct, this lock would be a necessity to keep the reference count clean.

The following slightly enhances your code comments from my interpretation:

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] 

In pseudocode, you could write it like this (the register values prepended with an underscore):

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
2
  • So, just to be clear, after the add edi[1] is not compared against anything, but it's value alone determines the state of the zero flag?
    – Twifty
    Aug 31, 2018 at 19:06
  • The addition sets the zero flag if the result was zero (then ZF=1). A possible subsequent compare reads the zero flag and acts accordingly. The jne jumps to its target in case of ZF == 0, i.e. the addition did not result in zero. It is the same as jnz. A compare (cmp) instruction is a substraction of the two operands, without modifying one of the operands. Quote from the referenced website: "The comparison is performed by subtracting the second operand from the first operand and then setting the status flags in the same manner as the SUB instruction."
    – josh
    Aug 31, 2018 at 20:09
4

The question about the zero flag and the xadd instruction has been sufficiently answered by josh. This answer is meant to add some surrounding information about the smart pointer you see.

What you see is typical code for shared_ptr, the standard reference counting smart pointer in the C++ standard library. A shared_ptr object (that is the pointer itself, not what it points to!) contains two(!) pointer values. The first pointer points to the pointee, the second pointer points to a management object. The management objects contains the vtable pointer followed by two reference counts, followed by other stuff depending on the specific kind of management object (for example, if the deleter is not stateless, the deleter's state is also included in the management object). The first reference count in the management object contains the number of strong references to the object (i.e. the number of shared_ptrs pointing to that object), whereas the second reference count contains the number of references to the management object. All strong references together count as a single reference to the management object, but every weak_ptr counts as an individual further reference to the management object.

Whenever a (non-null) shared_ptr goes out of scope, the count of strong references is decreased. If the count of strong references reaches zero, the lifetime of pointee ends, and the first vtable function is called to destroy the pointee. This invokes the deleter specified when creating the shared pointer (which (indirectly) defaults to delete (possibly delete[]for arrays) if no deleter was specified). Furthermore, after deletion of the object, the cumulative reference to the management object for all strong references is also decreased. If that reference count reaches zero, the management object can be destroyed, by calling the second vtable function.

When a (non-null) weak_ptr goes out of scope, just the reference count to the management object is decreased (as the weak_ptr does not carry a strong reference). If this reference count reaches zero, there are no strong references (because otherwise the cumulative reference to the management would still exist), and no other weak_ptrs. The pointee has already been destroyed when the last strong reference went out of scope, so just the management object has be destroyed (again, by calling the second vtable entry).

There is an optimization possibility when the pointee and the management object are created at the same time. In that case, the memory for both objects can be allocated in one block. The management object in that case is no bigger than the vtable and the two reference counts and precedes the pointee. The first vfunction just calls the destructor of the pointee, but does not free its memory. The second vfunction destroys the memory block containing the management object and the already destroyed pointee. This optimization is taken when a shared_ptr is created using make_shared. The downside of this optimization is that the memory block for the pointee is freed only when no weak or strong refences exist, instead of already then just the strong references are gone. In most use cases of shared_ptr, there are no weak_ptrs, so the downside usually does not matter.

1
  • Thanks for the explanation. The memory layout you mention exactly matches what I'm looking at.
    – Twifty
    Aug 31, 2018 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.