ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 32Bit(x86)/64Bit(x64) 차이점 정리
    System/Reversing 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함수에서도 인자값 전달이 잘 된 모습을 볼 수 있다.

     

     

    댓글

Designed by Tistory.