System/Reversing

32Bit(x86)/64Bit(x64) 차이점 정리

pyozzi 2018. 2. 4. 02:56

[32Bit / 64Bit 차이점]

  32Bit

64Bit

주소공간 2^32 만큼 사용가능

2^64만큼 사용가능

(실제로는 2^48) 

레지스터

EAX,EBX,ECX,EDX,EBP,ESP,EDI,ESI

(총 8개)

RAX,RBX,RCX,RDX,RBP,RSP,RDI,RSI

R8,R9,R10,R11,R12,R13,R14,R15

(총 16개)

함수 호출 규약 Cdecl / Stdcall  Fastcall 

 


 

#64Bit에서 실제로는 48Bit만 사용 가능한 이유?

64Bit라면 64Bit만큼 주소 공간을 사용할 수 있어야 한다.

0xFFFF FFFF FFFF FFFF

총 16개Byte로 주소공간을 표현한다.

하지만, 64Bit를 전부 사용하게 되면 배보다 배꼽이 더 큰 상황이 생긴다.

 

2^64 만큼을 관리할 메모리 크기를 생각해보자

데이터의 크기가 20Byte라고 한다면 20Byte*2^64 만큼의 메모리가 필요하다.

(정확한 계산법은 아니겠지만 주객전도되는 상황이라는 점만 이해하도록 하자)

또한, 방대한 주소를 사용함으로써 그 주소들을 관리하기위해서

하드웨어, 소프트웨어적인 측면의 문제들도 생기게 된다.

 

그래서 현IT상황을 고려해서 타협한게 48Bit만 사용하자는 대안이 나오게 된 것이다.

 

따라서, 주소지정은 48Bit만 하고 그 상위Bit들은 47Bit를 그대로 복사되도록 짜여져있다.

BitTable로 이해를 돕자면

[주소 0번]

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

[주소 1번]

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001

[주소 2번]

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010

.

.

.

[주소 0x0000 7FFF FFFF FFFF]

0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

이렇게 46Bit까지 1로 채워진 시점에서 47Bit가 1이되면

 

[주소 0xFFFF 8000 0000 0000]

1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

그 순간 상위16Bit들은 1로 채워지면서 47Bit도 1로 바뀌게 된다.

 

이러한 주소표기를

Canonical address space 라고 한다.

중요한 개념이니 64Bit환경을 공부한다면 반드시 알아두도록 한다.

또한, 리눅스 등의 운영체제들은 이러한 주소표기를 이용해서

0 ~ [0x0000 7FFF FFFF FFFF] => 사용자 영역

[0xFFFF 8000 0000 0000] => [0xFFFF FFFF FFFF FFFF] => 커널 영역

으로 사용하고 있다.

 


 

 

#함수 호출 규약 [Cdecl / Fastcall]

 

32Bit에서는 Cdecl이나 Stdcall과 같은 규약을 사용했다.

그래서 x86에서 함수를 호출할때 Argument(인자값)가 스택을 통해 전달됐다.

실습을 통해 이해해보자

 

[assemble_Test.c]

 

실습을 위해 간단한 코드를 짰다.

이 코드를

 

gcc를 이용해 32Bit로 컴파일 해주면

 

아키텍처가 i386-32-little인 실행파일이 생긴다.

gdb로 열어보자

 

[32_assem main]

 

[32_assem Stack]

보는 것처럼 Stack을 이용해서 1~7을 저장해서 Argument를 전달함을 알 수 있다.

 

[32_assem t_func]

t_func도 특별한 동작없이 Stack을 통해 인자값을 사용함을 확인할 수 있다.

 


이번에는 Fastcall 규약을 사용하는 64Bit를 알아보자

Fastcall은 전달하는 6개 이후의 인자값만 Stack을 통해 전달한다.

6개까지는 레지스터를 통해 전달한다.

RDI,RSI,RDX,RCX,R8,R9 를 사용한다.

이번에도 실습을 통해 알아보도록 한다.

 

마찬가지로 gcc로 아키텍처가 amd64-64-little인 파일을 생성한다.

여기서 amd64는 x86-64를 뜻하는데, 이는 x86 명령어 집합 아키텍처의 64비트 모임이다.

인텔의 IA-64와 완전 다른것이니 착오 없길 바란다.

(자세한 내용 https://ko.wikipedia.org/wiki/X86-64)

 

이것도 gdb로 열어보도록 하자

 

[64_assem main]

보면 a7로 넘어가는 인자값 7은 Stack으로 전달되고

1~6 까지의 인자값은 레지스터로 전달됨을 확인할 수있다.

여기서 EDI,ESI,ECX,EDX가 쓰인 이유는 공간절약을 위해 쓰인 것으로 보인다.

 

[64_assem 레지스터/스택]

레지스터와 스택을 확인해보면 저장이 잘된 모습을 볼 수 있다.

 

[64_assem t_func]

t_func함수에서도 레지스터를 이용해 스택에 인자값을 스택에 저장하는 모습을 볼 수 있다.

여기서 64Bit환경의 특징을 하나 더 볼 수있는데

스택에 push를 하는게 아니라 미리 sub로 0x20(32)만큼 공간을 확보한 후

mov로 값들은 복사하는 방식으로 인자값을 스택으로 저장한다.

 

 

[t_func 스택]

t_func함수에서도 인자값 전달이 잘 된 모습을 볼 수 있다.