별빛의 낙하지 :: 별빛의 낙하지

[PE View] 제작(1)

카테고리 없음 2012. 11. 7. 03:56 Posted by byulbit

PE View를 만드는 과정을 블로그에 업데이트 할 예정이다.



[C/C++] switch의 jump table

C++ 2012. 11. 7. 02:58 Posted by byulbit

switch 문의 경우 case 조건이 4개 이상일때 단순한 비교값 jump가 아니라 jump table을 만들고, 
jump table에 이동할 case값을 기록한다. 
그리고 그 값을 참조하여 이동한다. 

그럼 실제로 한번 살펴보자.

#include <stdio.h>

int main() { int a; scanf("%d", &a); switch(a) { case 0: printf("0000"); break; case 1: printf("1111"); break; case 2: printf("2222"); break; case 3: printf("3333"); break; case 4: printf("4444"); break; case 5: printf("5555"); break; } }

  

8: switch(a)

000A1039 8B 45 F8             mov         eax,dword ptr [a]  

000A103C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  

000A1042 83 BD 30 FF FF FF 05 cmp         dword ptr [ebp-0D0h],5  

000A1049 0F 87 A1 00 00 00    ja          $LN1+17h (0A10F0h)  

000A104F 8B 8D 30 FF FF FF    mov         ecx,dword ptr [ebp-0D0h]  

000A1055 FF 24 8D 30 11 0A 00 jmp         dword ptr  (0A1130h)[ecx*4]  

     9: {

    10: case 0:

    11: printf("0000");

000A105C 8B F4                mov         esi,esp  

000A105E 68 54 31 0A 00       push        offset string "0000" (0A3154h)  

000A1063 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A1069 83 C4 04             add         esp,4  

000A106C 3B F4                cmp         esi,esp  

000A106E E8 ED 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    12: break;

000A1073 EB 7B                jmp         $LN1+17h (0A10F0h)  

    13: case 1:

    14: printf("1111");

000A1075 8B F4                mov         esi,esp  

000A1077 68 4C 31 0A 00       push        offset string "1111" (0A314Ch)  

000A107C FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A1082 83 C4 04             add         esp,4  

000A1085 3B F4                cmp         esi,esp  

000A1087 E8 D4 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    15: break;

000A108C EB 62                jmp         $LN1+17h (0A10F0h)  

    16: case 2:

    17: printf("2222");

000A108E 8B F4                mov         esi,esp  

000A1090 68 44 31 0A 00       push        offset string "2222" (0A3144h)  

000A1095 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A109B 83 C4 04             add         esp,4  

000A109E 3B F4                cmp         esi,esp  

000A10A0 E8 BB 00 00 00       call        _RTC_CheckEsp (0A1160h)  

    18: break;

.........

    27: break;

    28: }

    29: 

    30: 

    31: }


000A1039 8B 45 F8             mov         eax,dword ptr [a]  : 변수 a 값을 eax에 대입한다.  

000A103C 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax     :      eax를 ebp-0D0h에 기록한다.

000A1042 83 BD 30 FF FF FF 05 cmp         dword ptr [ebp-0D0h],5      :      case 범위를 넘기는가 비교

000A1049 0F 87 A1 00 00 00    ja          $LN1+17h (0A10F0h)               : ja(jump above) 5 보다 크면 switch case를 통과한다.


mov         dword ptr [ebp-0D0h], eax : 조건변수 값(eax)을 ebp-0D0h 번지에 기록함을 알 수 있다.

// 여기서는 001AF934~001AF936번지를 쓰고 있음을 알 수 있다. 

001AF934 02 00                add         al,byte ptr [eax]  

001AF936 00 00                add         byte ptr [eax],al  


000A1055 FF 24 8D 30 11 0A 00 jmp      :   dword ptr  (0A1130h)[ecx*4]  명령을 통해서

000A1130 + 8h 에 있는 값으로 jmp를 한다.( dword ptr  (0A1130h)[ecx*4]  )

000A1138h에는 000A108E 주소값이 들어 있으며, 이 주소는 case 2의 시작하는 주소와 같다.


<case 2>

000A1138 8E                   db          8eh  

000A1139 10                   db          10h  

000A113A 0A                   db          0ah  

000A113B 00                   db          00h  


<switch table>

000A1130 5C                   db          5ch  

000A1131 10                   db          10h  

000A1132 0A                   db          0ah  

000A1133 00                   db          00h  

000A1134 75                   db          75h  

000A1135 10                   db          10h  

000A1136 0A                   db          0ah  

000A1137 00                   db          00h  

000A1138 8E                   db          8eh  

000A1139 10                   db          10h  

000A113A 0A                   db          0ah  

000A113B 00                   db          00h  

000A113C A7                   db          a7h  

000A113D 10                   db          10h  

000A113E 0A                   db          0ah  

000A113F 00                   db          00h  

000A1140 C0                   db          c0h  

000A1141 10                   db          10h  

000A1142 0A                   db          0ah  

000A1143 00                   db          00h  

000A1144 D9                   db          d9h  

000A1145 10                   db          10h  

000A1146 0A                   db          0ah  

000A1147 00                   db          00h


    16:  case 2:

    17:  printf("2222");

000A108E 8B F4                mov         esi,esp  

000A1090 68 44 31 0A 00       push        offset string "2222" (0A3144h)  

000A1095 FF 15 E0 30 0A 00    call        dword ptr [__imp__printf (0A30E0h)]  

000A109B 83 C4 04             add         esp,4  

000A109E 3B F4                cmp         esi,esp  




C++ 관련 책을 보면 아래와 같은 코드에서 C++ 컴파일러가 클래스 내에서 생성자가 정의되어 있지 않을 경우, 기본 생성자(default constructor)를 삽입한다고 알려져 있다.

하지만 실제로는 컴파일러 구현체에 따라서 달라지겠지만, 내가 현재 주로 쓰고 있는 VC++에서는 그러한 뻘짓(?)을 하지 않는다.


class Ref
{
public:
	int a;
	int b;

};

int main()
{
	Ref a;
	a.a = 12;
	a.b = 13;
	Ref b = a;

}

실제 어셈블리로 변환된 코드는 아래와 같다.

Ref a;

a.a = 12;

002A101E  mov         dword ptr [a],0Ch  

a.b = 13;

002A1025  mov         dword ptr [ebp-8],0Dh  

Ref b = a;

002A102C  mov         eax,dword ptr [a]  

002A102F  mov         dword ptr [b],eax  

002A1032  mov         ecx,dword ptr [ebp-8]  

002A1035  mov         dword ptr [ebp-18h],ecx  


놀랍지 않은가?  보시다시피 기본 생성자를 부를꺼라고 예상했지만, 실제로는 그러한 코드는 없다.

또한 디폴트 복사 생성자도 존재하지 않음을 알 수 있다.


만약 생성자가 존재하는 상태에서 기본 복사 생성자도 존재할까?

아래의 코드를 디스어셈블 해보았다.

class Ref
{
public:
	int a;
	int b;
	Ref(int a, int b) { cout << "1 "; }
};

int main()
{
	Ref a(1, 2);
	Ref b = a;
}


그 결과 아래와 같은 코드로 변환이 되었음을 알 수 있었다.

Ref a(1, 2);

00EC101E  push        2  

00EC1020  push        1  

00EC1022  lea         ecx,[a]  

00EC1025  call        Ref::Ref (0EC1090h)                     // 생성자 호출 

Ref b = a;

00EC102A  mov         eax,dword ptr [a]  

00EC102D  mov         dword ptr [b],eax  

00EC1030  mov         ecx,dword ptr [ebp-8]  

00EC1033  mov         dword ptr [ebp-18h],ecx  

즉 사용자가 정의한 생성자는 호출되었지만, 복사 생성자는 역시 존재하지 않음을 알 수 있다.

객체의 동적 할당인 경우는 어떻게 될까?

아래의 코드를 보자

class Ref
{
public:
//	Ref(){ cout << "constructor call" << endl; }
//	~Ref() { cout << "destructor call" << endl; }

};

int main()
{
	Ref* p = new Ref;

	delete p;


}

Ref* p = new Ref;

00EA101E  push        1  

00EA1020  call        operator new (0EA106Ch)  

00EA1025  add         esp,4  

00EA1028  mov         dword ptr [ebp-0E0h],eax  

00EA102E  mov         eax,dword ptr [ebp-0E0h]  

00EA1034  mov         dword ptr [p],eax  


delete p;

00EA1037  mov         eax,dword ptr [p]  

00EA103A  mov         dword ptr [ebp-0D4h],eax  

00EA1040  mov         ecx,dword ptr [ebp-0D4h]  

00EA1046  push        ecx  

00EA1047  call        operator delete (0EA1066h)  

00EA104C  add         esp,4  

즉 여전히 기본 생성자는 찾아 볼 수가 없다. 메모리 할당, 해제와 관련된 함수 호출이 있을 뿐이다.  여기서 재미있는 것은 소멸자 또한 없다는 사실이다.

비교를 하기 위해서 아래는 명시적으로 생성자를 지정한 경우의 동적할당 어셈코드이다.


class Ref
{
public:
	Ref(){ cout << "constructor call" << endl; }
	~Ref() { cout << "destructor call" << endl; }

};

int main()
{
	Ref* p = new Ref;

	delete p;


}

Ref* p = new Ref;

0080103D  push        1  

0080103F  call        operator new (801B7Ah)  

00801044  add         esp,4  

00801047  mov         dword ptr [ebp-0F8h],eax  

0080104D  mov         dword ptr [ebp-4],0  

00801054  cmp         dword ptr [ebp-0F8h],0  

0080105B  je          main+70h (801070h)  

0080105D  mov         ecx,dword ptr [ebp-0F8h]  

00801063  call        Ref::Ref (801100h)  

00801068  mov         dword ptr [ebp-10Ch],eax  

0080106E  jmp         main+7Ah (80107Ah)  

00801070  mov         dword ptr [ebp-10Ch],0  

0080107A  mov         eax,dword ptr [ebp-10Ch]  

00801080  mov         dword ptr [ebp-104h],eax  

00801086  mov         dword ptr [ebp-4],0FFFFFFFFh  

0080108D  mov         ecx,dword ptr [ebp-104h]  

00801093  mov         dword ptr [ebp-14h],ecx  


delete p;

00801096  mov         eax,dword ptr [ebp-14h]  

00801099  mov         dword ptr [ebp-0E0h],eax  

0080109F  mov         ecx,dword ptr [ebp-0E0h]  

008010A5  mov         dword ptr [ebp-0ECh],ecx  

008010AB  cmp         dword ptr [ebp-0ECh],0  

008010B2  je          main+0C9h (8010C9h)  

008010B4  push        1  

008010B6  mov         ecx,dword ptr [ebp-0ECh]  

008010BC  call        Ref::`scalar deleting destructor' (801170h)  

008010C1  mov         dword ptr [ebp-10Ch],eax  

008010C7  jmp         main+0D3h (8010D3h)  

008010C9  mov         dword ptr [ebp-10Ch],0