Freigeben über


X86-Disassemblierung mit Anmerkungen

Der folgende Abschnitt führt Sie durch ein Disassemblierungsbeispiel.

Quellcode

Im Folgenden finden Sie den Code für die Funktion, die analysiert wird.

HRESULT CUserView::CloseView(void)
{
    if (m_fDestroyed) return S_OK;

    BOOL fViewObjectChanged = FALSE;
    ReleaseAndNull(&m_pdtgt);

    if (m_psv) {
        m_psb->EnableModelessSB(FALSE);
        if(m_pws) m_pws->ViewReleased();

        IShellView* psv;

        HWND hwndCapture = GetCapture();
        if (hwndCapture && hwndCapture == m_hwnd) {
            SendMessage(m_hwnd, WM_CANCELMODE, 0, 0);
        }

        m_fHandsOff = TRUE;
        m_fRecursing = TRUE;
        NotifyClients(m_psv, NOTIFY_CLOSING);
        m_fRecursing = FALSE;

        m_psv->UIActivate(SVUIA_DEACTIVATE);

        psv = m_psv;
        m_psv = NULL;

        ReleaseAndNull(&_pctView);

        if (m_pvo) {
            IAdviseSink *pSink;
            if (SUCCEEDED(m_pvo->GetAdvise(NULL, NULL, &pSink)) && pSink) {
                if (pSink == (IAdviseSink *)this)
                    m_pvo->SetAdvise(0, 0, NULL);
                pSink->Release();
            }

            fViewObjectChanged = TRUE;
            ReleaseAndNull(&m_pvo);
        }

        if (psv) {
            psv->SaveViewState();
            psv->DestroyViewWindow();
            psv->Release();
        }

        m_hwndView = NULL;
        m_fHandsOff = FALSE;

        if (m_pcache) {
            GlobalFree(m_pcache);
            m_pcache = NULL;
        }

        m_psb->EnableModelessSB(TRUE);

        CancelPendingActions();
    }

    ReleaseAndNull(&_psf);

    if (fViewObjectChanged)
        NotifyViewClients(DVASPECT_CONTENT, -1);

    if (m_pszTitle) {
        LocalFree(m_pszTitle);
        m_pszTitle = NULL;
    }

    SetRect(&m_rcBounds, 0, 0, 0, 0);
    return S_OK;
}

Assemblycode

Dieser Abschnitt enthält das Beispiel für die Disassemblierung mit Anmerkungen.

Funktionen, die das ebp-Register als Framezeiger verwenden, beginnen wie folgt:

HRESULT CUserView::CloseView(void)
SAMPLE!CUserView__CloseView:
71517134 55               push    ebp
71517135 8bec             mov     ebp,esp

Dadurch wird der Frame so eingerichtet, dass die Funktion auf ihre Parameter als positive Offsets von ebp und lokale Variablen als negative Offsets zugreifen kann.

Dies ist eine Methode auf einer privaten COM-Schnittstelle, sodass die Aufrufkonvention __stdcall ist. Dies bedeutet, dass Parameter von rechts nach links gepusht werden (in diesem Fall gibt es keine), der Zeiger "this" wird gepusht und dann die Funktion aufgerufen. Daher sieht der Stapel beim Einstieg in die Funktion wie folgt aus:

[esp+0] = return address
[esp+4] = this

Nach den beiden vorherigen Anweisungen kann auf die Parameter wie folgt zugegriffen werden:

[ebp+0] = previous ebp pushed on stack
[ebp+4] = return address
[ebp+8] = this

Für eine Funktion, die ebp als Framezeiger verwendet, ist der erste pushte Parameter unter [ebp+8] zugänglich. Auf nachfolgende Parameter kann unter aufeinanderfolgenden höheren DWORD-Adressen zugegriffen werden.

71517137 51               push    ecx
71517138 51               push    ecx

Diese Funktion erfordert nur zwei lokale Stapelvariablen, also eine sub esp, 8-Anweisung. Die gepushten Werte sind dann als [ebp-4] und [ebp-8] verfügbar.

Für eine Funktion, die ebp als Framezeiger verwendet, sind lokale Stapelvariablen bei negativen Offsets aus dem ebp-Register zugänglich.

71517139 56               push    esi

Jetzt speichert der Compiler die Register, die über Funktionsaufrufe hinweg beibehalten werden müssen. Tatsächlich speichert es sie in Bits und Teilen, die mit der ersten Zeile des tatsächlichen Codes überlappen.

7151713a 8b7508           mov     esi,[ebp+0x8]     ; esi = this
7151713d 57               push    edi               ; save another registers

Es kommt also vor, dass CloseView eine Methode in ViewState ist, die sich im zugrunde liegenden Objekt im Offset 12 befindet. Folglich ist dies ein Zeiger auf eine ViewState-Klasse, obwohl bei möglichen Verwechslungen mit einer anderen Basisklasse sorgfältiger als (ViewState*) angegeben wird.

    if (m_fDestroyed)
7151713e 33ff             xor     edi,edi           ; edi = 0

Das XORingen eines Registers mit sich selbst ist eine Standardmethode, um es auf null zu machen.

71517140 39beac000000     cmp     [esi+0xac],edi    ; this->m_fDestroyed == 0?
71517146 7407             jz      NotDestroyed (7151714f)  ; jump if equal

Die cmp-Anweisung vergleicht zwei Werte (durch Subtrahieren). Die jz-Anweisung überprüft, ob das Ergebnis null ist, was angibt, dass die beiden verglichenen Werte gleich sind.

Die cmp-Anweisung vergleicht zwei Werte; eine nachfolgende j-Anweisung springt basierend auf dem Ergebnis des Vergleichs.

    return S_OK;
71517148 33c0             xor     eax,eax           ; eax = 0 = S_OK
7151714a e972010000       jmp     ReturnNoEBX (715172c1) ; return, do not pop EBX

Der Compiler hat das Speichern des EBX-Registers bis zu einem späteren Zeitpunkt in der Funktion verzögert. Wenn das Programm also in diesem Test "early-out" wird, muss der Exitpfad der sein, der EBX nicht wiederhergestellt.

    BOOL fViewObjectChanged = FALSE;
    ReleaseAndNull(&m_pdtgt);

Die Ausführung dieser beiden Codezeilen ist überlappend, also achten Sie darauf.

NotDestroyed:
7151714f 8d86c0000000     lea     eax,[esi+0xc0]    ; eax = &m_pdtgt

Die lea-Anweisung berechnet die Effektadresse eines Speicherzugriffs und speichert sie im Ziel. Die tatsächliche Speicheradresse wird nicht dereferenziert.

Die lea-Anweisung akzeptiert die Adresse einer Variablen.

71517155 53               push    ebx

Sie sollten dieses EBX-Register speichern, bevor es beschädigt wird.

71517156 8b1d10195071     mov ebx,[_imp__ReleaseAndNull]

Da Sie ReleaseAndNull häufig aufrufen, empfiehlt es sich, die Adresse in EBX zwischenzuspeichern.

7151715c 50               push    eax               ; parameter to ReleaseAndNull
7151715d 897dfc           mov     [ebp-0x4],edi     ; fViewObjectChanged = FALSE
71517160 ffd3             call    ebx               ; call ReleaseAndNull
    if (m_psv) {
71517162 397e74           cmp     [esi+0x74],edi    ; this->m_psv == 0?
71517165 0f8411010000     je      No_Psv (7151727c) ; jump if zero

Denken Sie daran, dass Sie das EDI-Register vor einer Weile auf null gesetzt haben und dass EDI ein Register ist, das über Funktionsaufrufe hinweg beibehalten wird (sodass der Aufruf von ReleaseAndNull es nicht geändert hat). Daher enthält es weiterhin den Wert 0 (null), und Sie können ihn verwenden, um schnell auf 0 (null) zu testen.

        m_psb->EnableModelessSB(FALSE);
7151716b 8b4638           mov     eax,[esi+0x38]    ; eax = this->m_psb
7151716e 57               push    edi               ; FALSE
7151716f 50               push    eax               ; "this" for callee
71517170 8b08             mov     ecx,[eax]         ; ecx = m_psb->lpVtbl
71517172 ff5124           call    [ecx+0x24]        ; __stdcall EnableModelessSB

Das obige Muster ist ein verräterisches Zeichen eines COM-Methodenaufrufs.

COM-Methodenaufrufe sind sehr beliebt, daher ist es eine gute Idee, sie zu erkennen. Insbesondere sollten Sie die drei IUnknown-Methoden direkt aus ihren Vtable-Offsets erkennen können: QueryInterface=0, AddRef=4 und Release=8.

        if(m_pws) m_pws->ViewReleased();
71517175 8b8614010000     mov     eax,[esi+0x114]   ; eax = this->m_pws
7151717b 3bc7             cmp     eax,edi           ; eax == 0?
7151717d 7406             jz      NoWS (71517185) ; if so, then jump
7151717f 8b08             mov     ecx,[eax]         ; ecx = m_pws->lpVtbl
71517181 50               push    eax               ; "this" for callee
71517182 ff510c           call    [ecx+0xc]         ; __stdcall ViewReleased
NoWS:
        HWND hwndCapture = GetCapture();
71517185 ff15e01a5071    call [_imp__GetCapture]    ; call GetCapture

Indirekte Aufrufe über globale Elemente sind die Implementierung von Funktionsimporten in Microsoft Win32. Das Ladeprogramm korrigiert die globalen Werte so, dass sie auf die tatsächliche Adresse des Ziels verweisen. Dies ist eine praktische Möglichkeit, sich zu orientieren, wenn Sie eine abgestürzte Maschine untersuchen. Suchen Sie nach den Aufrufen importierter Funktionen und im Ziel. Sie haben in der Regel den Namen einer importierten Funktion, die Sie verwenden können, um zu bestimmen, wo Sie sich im Quellcode befinden.

        if (hwndCapture && hwndCapture == m_hwnd) {
            SendMessage(m_hwnd, WM_CANCELMODE, 0, 0);
        }
7151718b 3bc7             cmp     eax,edi           ; hwndCapture == 0?
7151718d 7412             jz      No_Capture (715171a1) ; jump if zero

Der Rückgabewert der Funktion wird im EAX-Register platziert.

7151718f 8b4e44           mov     ecx,[esi+0x44]    ; ecx = this->m_hwnd
71517192 3bc1             cmp     eax,ecx           ; hwndCapture = ecx?
71517194 750b             jnz     No_Capture (715171a1) ; jump if not

71517196 57               push    edi               ; 0
71517197 57               push    edi               ; 0
71517198 6a1f             push    0x1f              ; WM_CANCELMODE
7151719a 51               push    ecx               ; hwndCapture
7151719b ff1518195071     call    [_imp__SendMessageW] ; SendMessage
No_Capture:
        m_fHandsOff = TRUE;
        m_fRecursing = TRUE;
715171a1 66818e0c0100000180 or    word ptr [esi+0x10c],0x8001 ; set both flags at once

        NotifyClients(m_psv, NOTIFY_CLOSING);
715171aa 8b4e20           mov     ecx,[esi+0x20]    ; ecx = (CNotifySource*)this.vtbl
715171ad 6a04             push    0x4               ; NOTIFY_CLOSING
715171af 8d4620           lea     eax,[esi+0x20]    ; eax = (CNotifySource*)this
715171b2 ff7674           push    [esi+0x74]        ; m_psv
715171b5 50               push    eax               ; "this" for callee
715171b6 ff510c           call    [ecx+0xc]         ; __stdcall NotifyClients

Beachten Sie, dass Sie ihren "this"-Zeiger ändern mussten, wenn Sie eine Methode für eine andere Basisklasse als Ihre eigene aufrufen.

        m_fRecursing = FALSE;
715171b9 80a60d0100007f   and     byte ptr [esi+0x10d],0x7f
        m_psv->UIActivate(SVUIA_DEACTIVATE);
715171c0 8b4674           mov     eax,[esi+0x74]    ; eax = m_psv
715171c3 57               push    edi               ; SVUIA_DEACTIVATE = 0
715171c4 50               push    eax               ; "this" for callee
715171c5 8b08             mov     ecx,[eax]         ; ecx = vtbl
715171c7 ff511c           call    [ecx+0x1c]        ; __stdcall UIActivate
        psv = m_psv;
        m_psv = NULL;
715171ca 8b4674           mov     eax,[esi+0x74]    ; eax = m_psv
715171cd 897e74           mov     [esi+0x74],edi    ; m_psv = NULL
715171d0 8945f8           mov     [ebp-0x8],eax     ; psv = eax

Die erste lokale Variable ist psv.

        ReleaseAndNull(&_pctView);
715171d3 8d466c           lea     eax,[esi+0x6c]    ; eax = &_pctView
715171d6 50               push    eax               ; parameter
715171d7 ffd3             call    ebx               ; call ReleaseAndNull
        if (m_pvo) {
715171d9 8b86a8000000     mov     eax,[esi+0xa8]    ; eax = m_pvo
715171df 8dbea8000000     lea     edi,[esi+0xa8]    ; edi = &m_pvo
715171e5 85c0             test    eax,eax           ; eax == 0?
715171e7 7448             jz      No_Pvo (71517231) ; jump if zero

Beachten Sie, dass der Compiler die Adresse des m_pvo-Members spekulativ vorbereitet hat, da Sie sie für eine Weile häufig verwenden werden. Wenn Die Adresse zur Hand ist, führt dies zu kleinerem Code.

            if (SUCCEEDED(m_pvo->GetAdvise(NULL, NULL, &pSink)) && pSink) {
715171e9 8b08             mov     ecx,[eax]         ; ecx = m_pvo->lpVtbl
715171eb 8d5508           lea     edx,[ebp+0x8]     ; edx = &pSink
715171ee 52               push    edx               ; parameter
715171ef 6a00             push    0x0               ; NULL
715171f1 6a00             push    0x0               ; NULL
715171f3 50               push    eax               ; "this" for callee
715171f4 ff5120           call    [ecx+0x20]        ; __stdcall GetAdvise
715171f7 85c0             test    eax,eax           ; test bits of eax
715171f9 7c2c             jl      No_Advise (71517227) ; jump if less than zero
715171fb 33c9             xor     ecx,ecx           ; ecx = 0
715171fd 394d08           cmp     [ebp+0x8],ecx     ; _pSink == ecx?
71517200 7425             jz      No_Advise (71517227)

Beachten Sie, dass der Compiler zu dem Schluss kam, dass der eingehende Parameter "this" nicht erforderlich war (da er den Parameter längst in das ESI-Register eingefügt hat). Daher wurde der Speicher als lokale Variable pSink wiederverwendet.

Wenn die Funktion einen EBP-Frame verwendet, erhalten eingehende Parameter positive Offsets von EBP, und lokale Variablen werden an negativen Offsets platziert. Aber wie in diesem Fall kann der Compiler diesen Speicher für jeden Zweck wiederverwenden.

Wenn Sie genau darauf achten, werden Sie sehen, dass der Compiler diesen Code etwas besser optimiert hätte. Es hätte die Anweisung lea edi, [esi+0xa8] bis nach den beiden Push-0x0 Anweisungen verzögern können und sie durch push edi ersetzt. Dadurch wären 2 Bytes eingespart worden.

                if (pSink == (IAdviseSink *)this)

Diese nächsten Zeilen sollen die Tatsache kompensieren, dass in C++ (IAdviseSink *)NULL immer noch NULL sein muss. Wenn Ihr "this" also wirklich "(ViewState*)NULL" ist, sollte das Ergebnis der Umwandlung NULL und nicht der Abstand zwischen IAdviseSink und IBrowserService sein.

71517202 8d46ec           lea     eax,[esi-0x14]    ; eax = -(IAdviseSink*)this
71517205 8d5614           lea     edx,[esi+0x14]    ; edx = (IAdviseSink*)this
71517208 f7d8             neg     eax               ; eax = -eax (sets carry if != 0)
7151720a 1bc0             sbb     eax,eax           ; eax = eax - eax - carry
7151720c 23c2             and     eax,edx           ; eax = NULL or edx

Obwohl der Pentium über eine Anweisung für bedingtes Verschieben verfügt, ist dies in der basisbasierten i386-Architektur nicht der Fall, sodass der Compiler bestimmte Techniken verwendet, um eine Anweisung zum bedingten Verschieben ohne Sprünge zu simulieren.

Das allgemeine Muster für eine bedingte Auswertung sieht wie folgt aus:

        neg     r
        sbb     r, r
        and     r, (val1 - val2)
        add     r, val2

Die Neg-r legt das Carry-Flag fest, wenn r ungleich null ist, da neg den Wert negiert, indem es von null subtrahiert wird. Durch Subtrahieren von 0 (null) wird ein Anleihewert generiert (legen Sie den Carry fest), wenn Sie einen Wert ungleich 0 (null) subtrahieren. Es beschädigt auch den Wert im r-Register , aber das ist akzeptabel, da Sie ihn sowieso überschreiben.

Als Nächstes subtrahiert die Anweisung sbb r, r einen Wert von sich selbst, was immer 0 (null) ergibt. Es subtrahiert jedoch auch das Carry-Bit (Leihen), sodass das Nettoergebnis darin besteht, r auf Null oder -1 festzulegen, je nachdem, ob der Carry klar oder festgelegt war.

Daher legt sbb r, r auf null fest, wenn der ursprüngliche Wert von r null war, oder auf -1, wenn der ursprüngliche Wert ungleich null war.

Die dritte Anweisung führt eine Maske aus. Da das r-Register null oder -1 ist, dient "this" entweder dazu, r null zu belassen oder r von -1 in (val1 - val1) zu ändern, sodass anding jeder Wert mit -1 den ursprünglichen Wert belässt.

Daher besteht das Ergebnis von "und r, (val1 - val1)" darin, r auf Null festzulegen, wenn der ursprüngliche Wert von r null war, oder auf "(val1 - val2)", wenn der ursprüngliche Wert von r ungleich null war.

Schließlich fügen Sie val2 zu r hinzu, was zu val2 oder (val1 - val2) + val2 = val1 führt.

Daher besteht das endgültige Ergebnis dieser Reihe von Anweisungen darin, r auf val2 festzulegen, wenn es ursprünglich null war, oder val1 , wenn es ungleich null war. Dies ist das Assemblyäquivalent von r = r ? val1 : val2.

In diesem speziellen instance können Sie sehen, dass val2 = 0 und val1 = (IAdviseSink*)this. (Beachten Sie, dass der Compiler die endgültige Anweisung zum Hinzufügen von eax, 0 entfernt hat, da sie keine Auswirkungen hat.)

7151720e 394508           cmp     [ebp+0x8],eax ; pSink == (IAdviseSink*)this?
71517211 750b             jnz     No_SetAdvise (7151721e) ; jump if not equal

Weiter oben in diesem Abschnitt legen Sie EDI auf die Adresse des m_pvo-Members fest. Sie werden es jetzt verwenden. Sie haben auch das ECX-Register zuvor auf null gesetzt.

                    m_pvo->SetAdvise(0, 0, NULL);
71517213 8b07             mov     eax,[edi]         ; eax = m_pvo
71517215 51               push    ecx               ; NULL
71517216 51               push    ecx               ; 0
71517217 51               push    ecx               ; 0
71517218 8b10             mov     edx,[eax]         ; edx = m_pvo->lpVtbl
7151721a 50               push    eax               ; "this" for callee
7151721b ff521c           call    [edx+0x1c]        ; __stdcall SetAdvise
No_SetAdvise:
                pSink->Release();
7151721e 8b4508           mov     eax,[ebp+0x8]     ; eax = pSink
71517221 50               push    eax               ; "this" for callee
71517222 8b08             mov     ecx,[eax]         ; ecx = pSink->lpVtbl
71517224 ff5108           call    [ecx+0x8]         ; __stdcall Release
No_Advise:

Alle diese COM-Methodenaufrufe sollten sehr vertraut aussehen.

Die Auswertung der nächsten beiden Anweisungen ist überlappend. Vergessen Sie nicht, dass EBX die Adresse von ReleaseAndNull enthält.

            fViewObjectChanged = TRUE;
            ReleaseAndNull(&m_pvo);
71517227 57               push    edi               ; &m_pvo
71517228 c745fc01000000   mov     dword ptr [ebp-0x4],0x1 ; fViewObjectChanged = TRUE
7151722f ffd3             call    ebx               ; call ReleaseAndNull
No_Pvo:
        if (psv) {
71517231 8b7df8           mov     edi,[ebp-0x8]     ; edi = psv
71517234 85ff             test    edi,edi           ; edi == 0?
71517236 7412             jz      No_Psv2 (7151724a) ; jump if zero
            psv->SaveViewState();
71517238 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
7151723a 57               push    edi               ; "this" for callee
7151723b ff5034           call    [eax+0x34]        ; __stdcall SaveViewState

Hier sind weitere COM-Methodenaufrufe aufgeführt.

            psv->DestroyViewWindow();
7151723e 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
71517240 57               push    edi               ; "this" for callee
71517241 ff5028           call    [eax+0x28]        ; __stdcall DestroyViewWindow
            psv->Release();
71517244 8b07             mov     eax,[edi]         ; eax = psv->lpVtbl
71517246 57               push    edi               ; "this" for callee
71517247 ff5008           call    [eax+0x8]         ; __stdcall Release
No_Psv2:
        m_hwndView = NULL;
7151724a 83667c00         and     dword ptr [esi+0x7c],0x0 ; m_hwndView = 0

Das ANDing eines Speicherorts mit 0 (null) ist identisch mit dem Festlegen auf 0(0), da ALLES AND null null ist. Der Compiler verwendet diese Form, da es, obwohl es langsamer ist, viel kürzer als die entsprechende mov-Anweisung ist. (Dieser Code wurde für größe und nicht für geschwindigkeit optimiert.)

        m_fHandsOff = FALSE;
7151724e 83a60c010000fe   and     dword ptr [esi+0x10c],0xfe
        if (m_pcache) {
71517255 8b4670           mov     eax,[esi+0x70]    ; eax = m_pcache
71517258 85c0             test    eax,eax           ; eax == 0?
7151725a 740b             jz      No_Cache (71517267) ; jump if zero
            GlobalFree(m_pcache);
7151725c 50               push    eax               ; m_pcache
7151725d ff15b4135071     call    [_imp__GlobalFree]    ; call GlobalFree
            m_pcache = NULL;
71517263 83667000         and     dword ptr [esi+0x70],0x0 ; m_pcache = 0
No_Cache:
        m_psb->EnableModelessSB(TRUE);
71517267 8b4638           mov     eax,[esi+0x38]    ; eax = this->m_psb
7151726a 6a01             push    0x1               ; TRUE
7151726c 50               push    eax               ; "this" for callee
7151726d 8b08             mov     ecx,[eax]         ; ecx = m_psb->lpVtbl
7151726f ff5124           call    [ecx+0x24]        ; __stdcall EnableModelessSB
        CancelPendingActions();

Um CancelPendingActions aufzurufen, müssen Sie von (ViewState*)this zu (CUserView*) wechseln. Beachten Sie auch, dass CancelPendingActions anstelle von __stdcall die __thiscall Aufrufkonvention verwendet. Laut __thiscall wird der Zeiger "this" im ECX-Register übergeben, anstatt im Stapel übergeben zu werden.

71517272 8d4eec           lea     ecx,[esi-0x14]    ; ecx = (CUserView*)this
71517275 e832fbffff       call CUserView::CancelPendingActions (71516dac) ; __thiscall
    ReleaseAndNull(&_psf);
7151727a 33ff             xor     edi,edi           ; edi = 0 (for later)
No_Psv:
7151727c 8d4678           lea     eax,[esi+0x78]    ; eax = &_psf
7151727f 50               push    eax               ; parameter
71517280 ffd3             call    ebx               ; call ReleaseAndNull
    if (fViewObjectChanged)
71517282 397dfc           cmp     [ebp-0x4],edi     ; fViewObjectChanged == 0?
71517285 740d             jz      NoNotifyViewClients (71517294) ; jump if zero
       NotifyViewClients(DVASPECT_CONTENT, -1);
71517287 8b46ec           mov     eax,[esi-0x14]    ; eax = ((CUserView*)this)->lpVtbl
7151728a 8d4eec           lea     ecx,[esi-0x14]    ; ecx = (CUserView*)this
7151728d 6aff             push    0xff              ; -1
7151728f 6a01             push    0x1               ; DVASPECT_CONTENT = 1
71517291 ff5024           call    [eax+0x24]        ; __thiscall NotifyViewClients
NoNotifyViewClients:
    if (m_pszTitle)
71517294 8b8680000000     mov     eax,[esi+0x80]    ; eax = m_pszTitle
7151729a 8d9e80000000     lea     ebx,[esi+0x80]    ; ebx = &m_pszTitle (for later)
715172a0 3bc7             cmp     eax,edi           ; eax == 0?
715172a2 7409             jz      No_Title (715172ad) ; jump if zero
        LocalFree(m_pszTitle);
715172a4 50               push    eax               ; m_pszTitle
715172a5 ff1538125071     call   [_imp__LocalFree]
        m_pszTitle = NULL;

Denken Sie daran, dass EDI immer noch null und EBX immer noch &m_pszTitle ist, da diese Register durch Funktionsaufrufe beibehalten werden.

715172ab 893b             mov     [ebx],edi         ; m_pszTitle = 0
No_Title:
    SetRect(&m_rcBounds, 0, 0, 0, 0);
715172ad 57               push    edi               ; 0
715172ae 57               push    edi               ; 0
715172af 57               push    edi               ; 0
715172b0 81c6fc000000     add     esi,0xfc          ; esi = &this->m_rcBounds
715172b6 57               push    edi               ; 0
715172b7 56               push    esi               ; &m_rcBounds
715172b8 ff15e41a5071     call   [_imp__SetRect]

Beachten Sie, dass Sie den Wert von "this" nicht mehr benötigen, sodass der Compiler die Add-Anweisung verwendet, um ihn vor Ort zu ändern, anstatt ein anderes Register für die Adresse zu verwenden. Dies ist tatsächlich ein Leistungsgewinn aufgrund des Pentium u/v-Pipelinings, da die v-Pipe arithmetische, aber keine Berechnungen adressieren kann.

    return S_OK;
715172be 33c0             xor     eax,eax           ; eax = S_OK

Schließlich stellen Sie die Register wieder her, die Sie beibehalten müssen, sauber den Stapel nach oben, und kehren zu Ihrem Aufrufer zurück, indem Sie die eingehenden Parameter entfernen.

715172c0 5b               pop     ebx               ; restore
ReturnNoEBX:
715172c1 5f               pop     edi               ; restore
715172c2 5e               pop     esi               ; restore
715172c3 c9               leave                     ; restores EBP and ESP simultaneously
715172c4 c20400           ret     0x4               ; return and clear parameters