Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La sección siguiente le guiará a través de un ejemplo de desensamblaje.
Código fuente
A continuación se muestra el código de la función que se analizará.
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;
}
Código de ensamblado
Esta sección contiene el ejemplo de desensamblaje anotado.
Las funciones que usan el registro ebp como puntero de marco se inician de la siguiente manera:
HRESULT CUserView::CloseView(void)
SAMPLE!CUserView__CloseView:
71517134 55 push ebp
71517135 8bec mov ebp,esp
Esto configura el marco para que la función pueda acceder a sus parámetros como desplazamientos positivos de ebp y variables locales como desplazamientos negativos.
Se trata de un método en una interfaz COM privada, por lo que la convención de llamada es __stdcall. Esto significa que los parámetros se insertan de derecha a izquierda (en este caso, no hay ninguno), se inserta el puntero "this" y, a continuación, se llama a la función . Por lo tanto, tras la entrada en la función, la pila tiene este aspecto:
[esp+0] = return address
[esp+4] = this
Después de las dos instrucciones anteriores, los parámetros son accesibles como:
[ebp+0] = previous ebp pushed on stack
[ebp+4] = return address
[ebp+8] = this
Para una función que usa ebp como puntero de marco, el primer parámetro insertado es accesible en [ebp+8]; Los parámetros posteriores son accesibles en direcciones DWORD superiores consecutivas.
71517137 51 push ecx
71517138 51 push ecx
Esta función solo requiere dos variables locales de pila, lo que implica una instrucción sub esp, 8. A continuación, los valores insertados están disponibles como [ebp-4] y [ebp-8].
Para una función que usa ebp como puntero de marco, las variables locales de pila son accesibles en desplazamientos negativos respecto al registro ebp.
71517139 56 push esi
Ahora el compilador guarda los registros que deben conservarse a través de las llamadas de función. En realidad, los guarda en fragmentos, mezclados con la primera línea de código real.
7151713a 8b7508 mov esi,[ebp+0x8] ; esi = this
7151713d 57 push edi ; save another registers
Resulta que CloseView es un método en ViewState, que se encuentra a la posición 12 del objeto subyacente. Por lo tanto, se trata de un puntero a una clase ViewState, aunque cuando haya posible confusión con otra clase base, se especificará con más cuidado como (ViewState*)this.
if (m_fDestroyed)
7151713e 33ff xor edi,edi ; edi = 0
Aplicar XOR a un registro con sí mismo es una manera estándar de ponerlo a cero.
71517140 39beac000000 cmp [esi+0xac],edi ; this->m_fDestroyed == 0?
71517146 7407 jz NotDestroyed (7151714f) ; jump if equal
La instrucción cmp compara dos valores (al restarlos). La instrucción jz comprueba si el resultado es cero, lo que indica que los dos valores comparados son iguales.
La instrucción cmp compara dos valores; una instrucción j posterior salta en función del resultado de la comparación.
return S_OK;
71517148 33c0 xor eax,eax ; eax = 0 = S_OK
7151714a e972010000 jmp ReturnNoEBX (715172c1) ; return, do not pop EBX
El compilador retrasó el guardado del registro EBX hasta más adelante en la función, por lo que si el programa va a salir anticipadamente en esta prueba, la ruta de salida debe ser la que no restaura EBX.
BOOL fViewObjectChanged = FALSE;
ReleaseAndNull(&m_pdtgt);
La ejecución de estas dos líneas de código está intercalada, así que preste atención.
NotDestroyed:
7151714f 8d86c0000000 lea eax,[esi+0xc0] ; eax = &m_pdtgt
La instrucción lea calcula la dirección del efecto de un acceso a la memoria y la almacena en el destino. La dirección de memoria real no se desreferencia.
La instrucción lea toma la dirección de una variable.
71517155 53 push ebx
Debería guardar el registro EBX antes de que se dañe.
71517156 8b1d10195071 mov ebx,[_imp__ReleaseAndNull]
Dado que llamará a ReleaseAndNull con frecuencia, es una buena idea almacenar en caché su dirección en EBX.
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
Recuerde que puso a cero el registro EDI un tiempo atrás y que EDI es un registro conservado entre las llamadas de función (por lo que la llamada a ReleaseAndNull no lo cambió). Por lo tanto, conserva el valor cero, y se puede usar para verificar que es cero rápidamente.
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
El patrón anterior es un signo telltale de una llamada al método COM.
Las llamadas de método COM son bastante populares, por lo que es una buena idea aprender a reconocerlas. En concreto, debería poder reconocer los tres métodos IUnknown directamente por sus desplazamientos de tabla virtual: QueryInterface=0, AddRef=4 y 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
Las llamadas indirectas a través de variables globales son cómo se implementan las importaciones de funciones en Microsoft Win32. El cargador corrige los globales para que apunten a la dirección real del destino. Esta es una manera práctica de orientarse cuando está investigando una máquina averiada. Busque las llamadas a funciones importadas y en el objetivo. Normalmente tendrá el nombre de alguna función importada, que puede usar para determinar dónde se encuentra en el código fuente.
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
El valor devuelto de la función se coloca en el registro EAX.
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
Observe cómo tuvo que modificar el puntero "this" al llamar a un método en una clase base distinta de la suya propia.
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
La primera variable local es 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
Tenga en cuenta que el compilador preparó especulativamente la dirección del miembro m_pvo, porque lo vas a utilizar con frecuencia durante un tiempo. Por lo tanto, tener la dirección a mano dará lugar a un código más compacto.
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)
Observe que el compilador concluyó que el parámetro entrante "this" no era necesario (porque hace mucho tiempo lo guardaba en el registro ESI). Por lo tanto, ha reutilizado la memoria como la variable local pSink.
Si la función usa un marco EBP, los parámetros entrantes llegan a desplazamientos positivos de EBP y las variables locales se colocan en desplazamientos negativos. Pero, como en este caso, el compilador es libre de reutilizar esa memoria para cualquier propósito.
Si está prestando mucha atención, verá que el compilador podría haber optimizado este código un poco mejor. Podría haber retrasado la instrucción lea edi, [esi+0xa8] hasta después de las dos instrucciones push 0x0, reemplazándolas con push edi. Esto habría ahorrado 2 bytes.
if (pSink == (IAdviseSink *)this)
Estas siguientes líneas son para compensar la necesidad de que en C++, (IAdviseSink *)NULL debe ser NULL. Por lo tanto, si "this" realmente es "(ViewState*)NULL", entonces el resultado de la conversión debe ser NULL y no la distancia entre IAdviseSink e IBrowserService.
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
Aunque Pentium tiene una instrucción de movimiento condicional, la arquitectura base i386 no lo hace, por lo que el compilador usa técnicas específicas para simular una instrucción de movimiento condicional sin realizar saltos.
El patrón general para una evaluación condicional es el siguiente:
neg r
sbb r, r
and r, (val1 - val2)
add r, val2
El neg r establece la bandera de acarreo si r es distinto de cero, porque neg niega el valor restando a partir de cero. Además, restar de cero generará un préstamo (establezca la carga) si resta un valor distinto de cero. También daña el valor en el registro r , pero eso es aceptable porque está a punto de sobrescribirlo de todos modos.
A continuación, la instrucción sbb r, r resta un valor de sí mismo, que siempre da como resultado cero. Sin embargo, también resta el bit de arrastre (préstamo), por lo que el resultado neto es establecer r en cero o -1, dependiendo de si el acarreo estaba limpio o establecido, respectivamente.
Por lo tanto, sbb r, r establece r en cero si el valor original de r era cero o para -1 si el valor original no era cero.
La tercera instrucción aplica una máscara. Dado que el registro r es cero o -1, esto sirve para dejar r en cero o cambiar r de -1 a (val1 - val1), en la que realizar una operación AND con cualquier valor y -1 deja el valor original.
Por lo tanto, el resultado de "y r, (val1 - val1)" es establecer r en cero si el valor original de r era cero, o en "(val1 - val2)" si el valor original de r era distinto de cero.
Por último, agregue val2 a r, lo que da como resultado val2 o (val1 - val2) + val2 = val1.
Por lo tanto, el resultado final de esta serie de instrucciones es establecer r en val2 si originalmente era cero o para val1 si era distinto de cero. Este es el equivalente de ensamblado de r = r ? val1 : val2.
En esta instancia concreta, puede ver que val2 = 0 y val1 = (IAdviseSink*)this. (Observe que el compilador elidó la instrucción final add eax, 0 ya que no tiene ningún efecto).
7151720e 394508 cmp [ebp+0x8],eax ; pSink == (IAdviseSink*)this?
71517211 750b jnz No_SetAdvise (7151721e) ; jump if not equal
Anteriormente en esta sección, estableciste EDI en la dirección del elemento m_pvo. Lo vas a usar ahora. También ha puesto a cero el registro ECX previamente.
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:
Todas estas llamadas al método COM deben ser muy familiares.
La evaluación de las dos siguientes instrucciones se entrelaza. No olvide que EBX contiene la dirección de ReleaseAndNull.
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
Estas son más llamadas de método COM.
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
Hacer AND a una ubicación de memoria con cero es lo mismo que configurarlo a cero, porque cualquier cosa AND cero es igual a cero. El compilador usa este formulario porque, aunque es más lento, es mucho más corto que la instrucción mov equivalente. (Este código se ha optimizado para el tamaño, no la velocidad).
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();
Para llamar a CancelPendingActions, debe pasar de (ViewState*)this a (CUserView*)this. Tenga en cuenta también que CancelPendingActions usa la convención de llamada __thiscall en lugar de __stdcall. Según __thiscall, el puntero "this" se pasa en el registro ECX en lugar de pasarse en la pila.
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;
Recuerde que EDI sigue siendo cero y EBX sigue siendo &m_pszTitle, ya que esos registros se conservan mediante llamadas de función.
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]
Tenga en cuenta que ya no necesita el valor de "this", por lo que el compilador usa la instrucción add para modificarla en lugar de usar otro registro para contener la dirección. Esto es realmente una mejora de rendimiento debido a la canalización Pentium u/v, ya que el canal v puede realizar cálculos aritméticos, pero no puede hacer cálculos de dirección.
return S_OK;
715172be 33c0 xor eax,eax ; eax = S_OK
Por último, restaure los registros que necesita para conservar, limpie la pila y vuelva al llamador, quitando los parámetros entrantes.
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