次のセクションでは、逆アセンブルの例について説明します。
ソースコード
分析される関数のコードを次に示します。
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;
}
アセンブリ コード
このセクションには、注釈付き逆アセンブリの例が含まれています。
ebp レジスタをフレーム ポインターとして使用する関数は、次のように開始されます。
HRESULT CUserView::CloseView(void)
SAMPLE!CUserView__CloseView:
71517134 55 push ebp
71517135 8bec mov ebp,esp
これにより、関数が ebp からの正のオフセットとしてパラメーターにアクセスし、ローカル変数を負のオフセットとしてアクセスできるように、フレームが設定されます。
これはプライベート COM インターフェイスのメソッドであるため、呼び出し規約は __stdcall。 つまり、パラメーターは右から左にプッシュされ (この場合は存在しません)、"this" ポインターがプッシュされ、関数が呼び出されます。 したがって、関数に入ると、スタックは次のようになります。
[esp+0] = return address
[esp+4] = this
上記の 2 つの手順の後、パラメーターには次のようにアクセスできます。
[ebp+0] = previous ebp pushed on stack
[ebp+4] = return address
[ebp+8] = this
フレーム ポインターとして ebp を使用する関数の場合、最初にプッシュされたパラメーターは [ebp+8] でアクセスできます。後続のパラメーターは、上位の DWORD アドレスで連続してアクセスできます。
71517137 51 push ecx
71517138 51 push ecx
この関数は、2 つのローカル スタック変数のみを必要とするため、 sub esp、8 命令です。 その後、プッシュされた値は [ebp-4] および [ebp-8] として使用できます。
フレーム ポインターとして ebp を使用する関数の場合、スタック ローカル変数は ebp レジスタからの負のオフセットでアクセスできます。
71517139 56 push esi
これで、コンパイラは、関数呼び出し間で保持する必要があるレジスタを保存します。 実際には、小さな断片として保存され、実際のコードの最初の行とインターリーブされます。
7151713a 8b7508 mov esi,[ebp+0x8] ; esi = this
7151713d 57 push edi ; save another registers
そのため、CloseView は ViewState のメソッドであり、基になるオブジェクトのオフセット 12 にあります。 したがって、 これは ViewState クラスへのポインターですが、別の基底クラスと混乱する可能性がある場合は、(ViewState*) this としてより慎重に指定されます。
if (m_fDestroyed)
7151713e 33ff xor edi,edi ; edi = 0
それ自体でレジスタを XORing することは、それをゼロにする標準的な方法です。
71517140 39beac000000 cmp [esi+0xac],edi ; this->m_fDestroyed == 0?
71517146 7407 jz NotDestroyed (7151714f) ; jump if equal
cmp 命令では、2 つの値を (減算して) 比較します。 jj 命令は、結果が 0 であるかどうかをチェックし、比較された 2 つの値が等しいことを示します。
cmp 命令は 2 つの値を比較します。後続の j 命令は、比較の結果に基づいてジャンプします。
return S_OK;
71517148 33c0 xor eax,eax ; eax = 0 = S_OK
7151714a e972010000 jmp ReturnNoEBX (715172c1) ; return, do not pop EBX
コンパイラは関数の後半まで EBX レジスタの保存を遅らせたので、このテストでプログラムが "早期アウト" になる場合は、終了パスが EBX を復元しないパスである必要があります。
BOOL fViewObjectChanged = FALSE;
ReleaseAndNull(&m_pdtgt);
これら 2 行のコードの実行はインターリーブされるため、注意してください。
NotDestroyed:
7151714f 8d86c0000000 lea eax,[esi+0xc0] ; eax = &m_pdtgt
lea 命令は、メモリ アクセスの効果アドレスを計算し、それを宛先に格納します。 実際のメモリ アドレスは逆参照されません。
lea 命令は変数のアドレスを受け取ります。
71517155 53 push ebx
破損する前に、その EBX レジスタを保存する必要があります。
71517156 8b1d10195071 mov ebx,[_imp__ReleaseAndNull]
ReleaseAndNull は頻繁に呼び出されるため、そのアドレスを 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
EDI レジスタをしばらく前にゼロにし、EDI は関数呼び出し間で保持されるレジスタであることを忘れないでください (そのため 、ReleaseAndNull の呼び出しでは変更されませんでした)。 したがって、値 0 が保持され、それを使用してゼロをすばやくテストできます。
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
上記のパターンは、COM メソッド呼び出しの通知記号です。
COM メソッドの呼び出しは非常に一般的であるため、それらを認識する方法を学習することをお勧めします。 具体的には、3 つの IUnknown メソッドを Vtable オフセットから直接認識できる必要があります:QueryInterface=0、AddRef=4、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
グローバルを介した間接呼び出しは、関数のインポートを Microsoft Win32 で実装する方法です。 ローダーは、ターゲットの実際のアドレスを指すようにグローバルを修正します。 これは、クラッシュしたマシンを調査するときに状況を把握するための便利な方法です。 インポートされた関数とターゲット内の呼び出しを探します。 通常、インポートされた関数の名前は、ソース コード内のどこにいるかを判断するために使用できます。
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
関数の戻り値は 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
独自の基底クラスから別の基底クラスのメソッドを呼び出すときに、"this" ポインターを変更する必要があることに注意してください。
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
最初のローカル変数は 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
コンパイラは、しばらくの間頻繁に使用するため、 m_pvo メンバーのアドレスを予測的に準備しました。 したがって、アドレスを手元に置くと、コードが小さくなります。
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)
コンパイラは、受信した "this" パラメーターは必要ないと結論付けました (これはずっと前に ESI レジスタに隠されているためです)。 したがって、メモリをローカル変数 pSink として再利用しました。
関数が EBP フレームを使用する場合、受信パラメーターは EBP からの正のオフセットに到達し、ローカル変数は負のオフセットに配置されます。 ただし、この場合と同様に、コンパイラは任意の目的でそのメモリを自由に再利用できます。
細心の注意を払っている場合は、コンパイラがこのコードを少し改善している可能性があることがわかります。 lea edi、[esi+0xa8] 命令は、2つのpush 0x0 命令の後まで遅延させ、それらをpush edi に置き換える可能性があります。 これにより、2 バイトが保存されます。
if (pSink == (IAdviseSink *)this)
次のいくつかの行は、C++ では (IAdviseSink *)NULL が依然として NULL でなければならないという事実を補うことです。 したがって、"this" が実際に "(ViewState*)NULL" の場合、キャストの結果は NULL となり、IAdviseSink と 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
Pentium には条件付き移動命令がありますが、基本 i386 アーキテクチャには含まれていないため、コンパイラは特定の手法を使用して、ジャンプを取らずに条件付き移動命令をシミュレートします。
条件付き評価の一般的なパターンは次のとおりです。
neg r
sbb r, r
and r, (val1 - val2)
add r, val2
neg r は、r が 0 以外の場合、0 から減算して値を否定するため、キャリーフラグを設定します。 また、ゼロから非ゼロの値を減算すると、借用(キャリーを設定)が生成されます。 また、 r レジスタの値が破損しますが、上書きしようとしているので許容されます。
次に、 sbb r、r 命令はそれ自体から値を減算し、常にゼロになります。 ただし、キャリービット(借用ビット)も減算されるため、r の値は、キャリービットがクリアされている場合には 0 に、セットされている場合には -1 に設定されます。
したがって、 sbb r、r は r の元 の値が 0 の場合は r を、元の値が 0 以外の場合は -1 に設定します。
3 番目の命令はマスクを実行します。 r レジスタは 0 または -1 であるため、"this" は r ゼロのままにするか、r を -1 から (val1 - val1) に変更します。つまり、-1 を持つ任意の値を ANDing すると元の値が残ります。
したがって、"and r, (val1 - val1)" の結果は、r の元の値が 0 の場合は r を 0 に設定し、r の元の値が 0 以外の場合は "(val1 - val2)" に設定します。
最後に、 val2 を r に追加すると、 val2 または (val1 - val2) + val2 = val1 になります。
したがって、この一連の命令の最終的な結果は、r が最初に 0 の場合は val2 に設定され、0 以外の場合は val1 に設定されます。 これは、 r = r ? val1 : val2 に相当するアセンブリです。
この特定のインスタンスでは、 val2 = 0 と val1 = (IAdviseSink*)this を確認できます。 (コンパイラは、効果がないため、最後の 追加 eax、0 命令を省略していることに注意してください)。
7151720e 394508 cmp [ebp+0x8],eax ; pSink == (IAdviseSink*)this?
71517211 750b jnz No_SetAdvise (7151721e) ; jump if not equal
このセクションの前半では、EDI を m_pvo メンバーのアドレスに設定しました。 あなたは今それを使用するつもりです。 また、先ほど ECX レジスタをゼロにしました。
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:
これらすべての COM メソッド呼び出しは、よく知られているはずです。
次の 2 つのステートメントの評価は交互に実行されます。 EBX に 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
その他の 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
メモリ位置にゼロを論理積でとることは、それをゼロに設定することと同じです。なぜなら、何でも論理積でゼロをとると結果はゼロになるからです。 コンパイラはこの形式を使用します。低速であっても、同等の mov 命令よりもはるかに短いためです。 (このコードは、速度ではなくサイズに合わせて最適化されています)。
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();
CancelPendingActions を呼び出すには、(ViewState*)this から (CUserView*)this に移動する必要があります。 また、CancelPendingActions では、__stdcallの代わりに__thiscall呼び出し規則が使用されることにも注意してください。 __thiscallによると、"this" ポインターはスタックで渡されるのではなく、ECX レジスタで渡されます。
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;
EDI はまだ 0 のままであり、EBX は引き続き &m_pszTitle です。これらのレジスタは関数呼び出しによって保持されるためです。
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]
"this" の値はこれ以上必要ないため、コンパイラはアドレスを保持するために別のレジスタを使用する代わりに 、add 命令を使用して変更します。 実際には、v パイプは算術演算を実行できますが、アドレス計算はできないため、これは Pentium u/v パイプライン処理によるパフォーマンスの利点です。
return S_OK;
715172be 33c0 xor eax,eax ; eax = S_OK
最後に、保持する必要があるレジスタを復元し、スタックをクリーンアップして、呼び出し元に戻り、受け取ったパラメーターを削除します。
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