-->
Mov eax, APIaddr push retaddr mov edi, edi push ebp mov ebp, esp lea eax, dword eax+5 jmp eax retaddr: APIaddr: mov edi, edi push ebp mov ebp, esp. According to this page: StackExchange's Reverse Engineering. In x86-64 mov edi,edi is not a NOP. In x86-64 it zeroes the top 32 bits of rdi. I though it was important enough to point it out, in supplement to snoone's answer.
The following section will walk you through a disassembly example.
Source Code
The following is the code for the function that will be analyzed.
Assembly Code
This section contains the annotated disassembly example.
Functions which use the ebp register as a frame pointer start out as follows:
This sets up the frame so the function can access its parameters as positive offsets from ebp, and local variables as negative offsets.
This is a method on a private COM interface, so the calling convention is __stdcall. This means that parameters are pushed right to left (in this case, there are none), the 'this' pointer is pushed, and then the function is called. Thus, upon entry into the function, the stack looks like this:
After the two preceding instructions, the parameters are accessible as:
For a function that uses ebp as a frame pointer, the first pushed parameter is accessible at [ebp+8]; subsequent parameters are accessible at consecutive higher DWORD addresses.
This function requires only two local stack variables, so a sub esp, 8 instruction. The pushed values are then available as [ebp-4] and [ebp-8].
For a function that uses ebp as a frame pointer, stack local variables are accessible at negative offsets from the ebp register.
Now the compiler saves the registers that are required to be preserved across function calls. Actually, it saves them in bits and pieces, interleaved with the first line of actual code.
It so happens that CloseView is a method on ViewState, which is at offset 12 in the underlying object. Consequently, this is a pointer to a ViewState class, although when there is possible confusion with another base class, it will be more carefully specified as (ViewState*)this.
XORing a register with itself is a standard way of zeroing it out.
The cmp instruction compares two values (by subtracting them). The jz instruction checks if the result is zero, indicating that the two compared values are equal.
The cmp instruction compares two values; a subsequent j instruction jumps based on the result of the comparison.
The compiler delayed saving the EBX register until later in the function, so if the program is going to 'early-out' on this test, then the exit path needs to be the one that does not restore EBX.
The execution of these two lines of code is interleaved, so pay attention.
The lea instruction computes the effect address of a memory access and stores it in the destination. The actual memory address is not dereferenced.
The lea instruction takes the address of a variable.
You should save that EBX register before it is damaged.
Because you will be calling ReleaseAndNull frequently, it is a good idea to cache its address in EBX.
Remember that you zeroed out the EDI register a while back and that EDI is a register preserved across function calls (so the call to ReleaseAndNull did not change it). Therefore, it still holds the value zero and you can use it to quickly test for zero.
The above pattern is a telltale sign of a COM method call.
COM method calls are pretty popular, so it is a good idea to learn to recognize them. In particular, you should be able to recognize the three IUnknown methods directly from their Vtable offsets: QueryInterface=0, AddRef=4, and Release=8.
Indirect calls through globals is how function imports are implemented in Microsoft Win32. The loader fixes up the globals to point to the actual address of the target. This is a handy way to get your bearings when you are investigating a crashed machine. Look for the calls to imported functions and in the target. You will usually have the name of some imported function, which you can use to determine where you are in the source code.
The function return value is placed in the EAX register.
Notice how you had to change your 'this' pointer when calling a method on a different base class from your own.
The first local variable is psv.
Note that the compiler speculatively prepared the address of the m_pvo member, because you are going to use it frequently for a while. Thus, having the address handy will result in smaller code.
Notice that the compiler concluded that the incoming 'this' parameter was not required (because it long ago stashed that into the ESI register). Thus, it reused the memory as the local variable pSink.
If the function uses an EBP frame, then incoming parameters arrive at positive offsets from EBP and local variables are placed at negative offsets. But, as in this case, the compiler is free to reuse that memory for any purpose.
If you are paying close attention, you will see that the compiler could have optimized this code a little better. It could have delayed the lea edi, [esi+0xa8] instruction until after the two push 0x0 instructions, replacing them with push edi. This would have saved 2 bytes.
These next several lines are to compensate for the fact that in C++, (IAdviseSink *)NULL must still be NULL. So if your 'this' is really '(ViewState*)NULL', then the result of the cast should be NULL and not the distance between IAdviseSink and IBrowserService.
Although the Pentium has a conditional move instruction, the base i386 architecture does not, so the compiler uses specific techniques to simulate a conditional move instruction without taking any jumps.
The general pattern for a conditional evaluation is the following:
The neg r sets the carry flag if r is nonzero, because neg negates the value by subtracting from zero. And, subtracting from zero will generate a borrow (set the carry) if you subtract a nonzero value. It also damages the value in the r register, but that is acceptable because you are about to overwrite it anyway.
Next, the sbb r, r instruction subtracts a value from itself, which always results in zero. However, it also subtracts the carry (borrow) bit, so the net result is to set r to zero or -1, depending on whether the carry was clear or set, respectively.
Therefore, sbb r, r sets r to zero if the original value of r was zero, or to -1 if the original value was nonzero.
The third instruction performs a mask. Because the r register is zero or -1, 'this' serves either to leave r zero or to change r from -1 to (val1 - val1), in that ANDing any value with -1 leaves the original value.
Therefore, the result of 'and r, (val1 - val1)' is to set r to zero if the original value of r was zero, or to '(val1 - val2)' if the original value of r was nonzero.
Finally, you add val2 to r, resulting in val2 or (val1 - val2) + val2 = val1.
Thus, the ultimate result of this series of instructions is to set r to val2 if it was originally zero or to val1 if it was nonzero. This is the assembly equivalent of r = r ? val1 : val2.
In this particular instance, you can see that val2 = 0 and val1 = (IAdviseSink*)this. (Notice that the compiler elided the final add eax, 0 instruction because it has no effect.)
Earlier in this section, you set EDI to the address of the m_pvo member. You are going to be using it now. You also zeroed out the ECX register earlier.
All these COM method calls should look very familiar.
The evaluation of the next two statements is interleaved. Do not forget that EBX contains the address of ReleaseAndNull.
Here are more COM method calls.
ANDing a memory location with zero is the same as setting it to zero, because anything AND zero is zero. The compiler uses this form because, even though it is slower, it is much shorter than the equivalent mov instruction. (This code was optimized for size, not speed.)
In order to call CancelPendingActions, you have to move from (ViewState*)this to (CUserView*)this. Note also that CancelPendingActions uses the __thiscall calling convention instead of __stdcall. According to __thiscall, the 'this' pointer is passed in the ECX register instead of being passed on the stack.
Remember that EDI is still zero and EBX is still &m_pszTitle, because those registers are preserved by function calls.
Notice that you do not need the value of 'this' any more, so the compiler uses the add instruction to modify it in place instead of using up another register to hold the address. This is actually a performance win due to the Pentium u/v pipelining, because the v pipe can do arithmetic, but not address computations.
Finally, you restore the registers you are required to preserve, clean up the stack, and return to your caller, removing the incoming parameters.