2011년6월17일_C언어 변수선언과 대입한 코드를 disassembly하여 데이터이동명령에 대해 자세히 알아보자.


C언어가 컴파일러에 의해 어떻게 변역되어 지는지 보고 기계에 대해 좀 더 알아가자.

 

image

char형 a만 선언하고 브레이크포인터를 건다음 F5키를 눌러 디버그모드로 진입하여...

 

image

push ebp와 mov ebp, esp는 인입코드(entry code)로 호출함수의 ebp를 저장 후 피호출함수 main( )의 스택영역의 밑바닥을 설정하여..
스택에 자신만의 밥상(작업장)을 만드는 것과 같다.

sub esp, 44h에서 44h는 십진수68이고, 4Bytes스택폭에 17개의 변수가 들어갈 공간이다.
sub는 빼기명령이고 esp = esp - 68과 같다.


image

int형 b변수를 선언 후...

image

3번째 빼기명령의 값이 바뀌었다. 44h에서 48h로 4Bytes가 늘었다.
int형 변수의 크기는 4Bytes로 변수b를 하나 더 선언하였으니 그 만큼 stack도 더 확보해야 한다.

하나 더 int형 c변수를 선언 후...

image

3번째 빼기명령의 값이 또 바뀌었다. 48h에서 4Ch로 4Bytes가 늘었다.
변수 하나를 선언할 때 마다 4Bytes의 stack공간을 더 확보한다는 것을 알 수 있다.

int형 b, c를 보면 C언어에선 순서대로 하나씩 선언하였으나 3번째 줄을 보면 순서대로 되는 것이 아니라 한꺼번에 여러 개의 변수가..
들어갈 영역을 확보한다는 것을 알 수 있다.


image

char a = 100; 이라는 C언어 명령문은...
    mov byte ptr[ebp – 4], 64h
int b = 100; 은..
    mov dword ptr[ebp – 8], 64h
으로 해석되었다.

C언어에서 a, b라는 이름은 어디 간데 없고 [ebp – 4]와 [ebp – 8]로 바뀌어져 있다.
이때까지 실습한 변수 a, b의 절대주소는 없고 상대주소만 있는 것이다.
그것도 모두 ebp라는 stack의 베이스포인터 레지스터를 기준으로 삼고 있다.

64h는 10진수로 바꾸면 100이다.


image

위와 같이 char형 a와 int형 b, c의 선언문에 대한 어셈블리어코드는 없다.
변수선언은 3번째 sub명령으로 한 번에 스택포인터를 올림으로써 끝내고,
선언문아래에,
a = 100;과 b = 100;과 같이 어떤 일을 하는 경우엔 어셈블리어코드가 있다.

그래서 C언어를 공부한 우리에게 중요한 것은 symbol table이다. (변수 – 메모리관계를 알 수 있으니)

type

name

address

char

a

0x0013FF7C [ebp – 4]

int

b

0x0013FF78 [ebp – 8]

int

c

0x0013FF74 [ebp – 12]


★GCC버전이 높아지면 변수의 주소가 실행할 때마다 바뀜. 위치가 고정되어 있으면 해킹당함.
★메모리 내용을 읽어 오는 일을 덤프(dump)라고 함.


image

a에 -1을 대입한 후 a를 b에 대입하면,
-1은 1Byte자료형에선 0xFF이므로 그대로 넣고,
4Bytes(int형) b에 넣기 위해 movsx명령을 이용해 최상위 부호비트를 확장하여 eax(레지스터)에 집어 넣는다.
0xFF가 확장되어 0xFFFFFFFF가 되어 eax에 들어간다.
그런 후 eax레지스터의 값을 stack영역에 이동시킨다.
(아마 8 –> 32bit확장이동명령과 16 –> 32bit확장이동명령의 opcode는 다를 것이다.)

모두 보호없는 정수형 unsigned로 바꾸면..

image

a를 b에 대입하는 코드를 보면 더 부호있을 때 보다 더 늘었다.

image

eax : XX XX XX FF     <----   a : 0xFF
AND 00 00 00  FF
       00 00 00 FF

0x000000FF를 AND연산하게 되면 최하위 1Byte의 값만 남게 되어 상위 3Bytes의 쓰레기값은 0이 되어 버린다.


image 

b에 들어 있는 값을 c로 대입하는 명령문이,
어셈블리어로 번역되니,
메모리에 있는 b의 값을 읽어와 ecx에 넣은 뒤에 ecx의 값을 다시 변수c가 위치한 메모리로 넣는다.

메모리에서 메모리로 바로 데이터를 이동시키지 못 하고 범용레지스터를 거쳐 간다는 것을 알 수 있다.
이 어셈블리어 명령문만 봐도 CPU의 내부회로가 외부 메모리와 연결시 레지스터를 거친다는 것을 알 수 있다.

<결론>
● 부호있는(signed) 자료형은 추가 연산이 필요하다.
● 자료형이 다르면 추가 연산이 필요하다.

CPU가 32bit이므로 4Bytes 부호없는 정수형인 unsigned int형이 계산시 가장 빠르다.