[System] 80x86 시스템 메모리 구조와 동작

 

시스템 해킹


(1) 80x86 시스템 메모리의 구조와 동작

1-1. 메모리의 기본 구조

[그림 1] 80x86 시스템 메모리 구조

 

1-1-1. 스택 (Stack)

  • 후입선출(LIFO) 방식에 의해 정보를 관리하는 데이터 구조

  • Top이라 불리는 스택 끝부분에서 데이터 삽입과 삭제가 발생 (스텍에 데이터 삽입 > Top에 정보 위치)

  • 스택에서 정보를 읽어 오려고 하면 스택의 Top 위치에 있는 정보가 반환

  • 스택에서는 가장 나중에 삽입된 정보가 가장 먼저 읽히는 특징이 있음

 

컴퓨터 사전에서 정의한 스택은 데이터 구조론을 기반으로 한 정의이며, 컴퓨터 메모리에서도 기본 작동 방식은 같다.

 

단지 차이는 스택에 저장되고 사용 목적이 프로그램 실행에 있다는 것이다.

 

컴퓨터 메모리 상의 스택은 프로그램 함수 내부에서 정의, 함수 종료와 동시에 사라지는 자동 변수가 저장되고 함수를 호출할 때 함수 내부의 로컬 변수 등이 저장되는 곳이다.

 

함수를 실행하는 동안만 존재하며, 함수 실행을 종료하면 해당 변수들도 사라진다.

 

ESP 레지스터는 항상 스택의 가장 위(끝)을 가리킨다.

 

 

1-1-2. 힙 (Heap)

  • 프로그램을 실행할 때까지 가변적 양의 데이터를 저장하는 데 사용하는 메모리 영역

  • 프로그램 실행 중에 데이터 저장을 위해 기억 장소 요청 시, OS는 힙에 존재하는 기억 장소를 프로그램에 할당

  • 프로그램에서는 기억 장치가 더 이상 필요 없으면 할당 받았던 기억 장소를 OS에 반납하며, 이때 OS는 반납된 기억 장소를 다시 힙에 돌려줌

  • 힙에 대한 기억 장소는 포인터를 통해 동적 할당되거나 반환되며 연결 리스트, 트리, 그래프처럼 동적인 데이터 구조에서 널리 사용

  • 스택 영역은 LIFO 방식으로 운영, 힙은 프로그램들이 요구하는 블록 크기나 요구/횟수 순서에 일정한 규칙이 없음

  • 프로그램 실행 중 해당 힙이 없어지면 메모리 부족으로 이상 종료함

 

프로그램에서는 new나 malloc() 함수로 메모리를 동적 할당하여 힙 영역을 사용할 수 있다.

 

예를 들어 다음 코드 실행 시에는 힙 영역에 메모리 1,000 바이트 할당한다.

char * p = new char[1000];

 

 

1-1-3. BSS 세그먼트

static int a;
  • 초기화 되지 않은 데이터 세그먼트라고도 함

  • 프로그램 실행할 때 0이나 Null 포인터로 초기화되는 영역

  • 위 코드는 외부 변수나 static 변수 중 초기화가 되지 않은 변수들을 저장하는 영역을 의미 

 

 

1-1-4. 데이터 세그먼트

static int a = 1;
  • 초기화 된 데이터 세그먼트라고도 하며, 위 코드처럼 외부 변수나 static 변수 등을 저장하는 영역

  • 보통 텍스트 세그먼트와 데이터 세그먼트 영역을 합쳐 프로그램이라 함

 

 

1-1-5. 텍스트 세그먼트

  • CPU로 실행되는 머신 코드가 있는 영역

  • EIP가 다음에 실행하는 명령을 가리킴


(2) 메모리 접근 모드와 동작

  • 초기 8086 CPU를 사용한 IBM-PC는 등장 당시 메모리가 1MB였으며, 상당히 큰 편이었음

  • IBM은 프로그램이 앞의 640KB(0~0x9fff)만 사용할 수 있게 하고, 나머지 384KB(0xa000~0xffff)는 BIOS와 ISA 장치용으로 사용하게 했음

  • 요즘은 메모리가 1MB인 PC는 볼 수 없으며, 최소 1GB 이상이며, 4GB도 흔함

  • 메모리가 1MB에서 4GB 이상으로 커지면서 메모리에 접근하여 데이터를 저장하고 읽는 방법도 달라짐

 

2-1. 실제 모드

  • x86 계열로 처음 등장한 8086 CPU에서 사용하던 동작 모드

  • 20비트 주소 버스로 총 1MB(2**20 = 1,048,567) 메모리를 사용하였음

  • 16비트 레지스터로 20비트 주소 버스를 사용

  • 20비트 주소 표현을 위해 세그먼트 레지스터를 도입

  • 16비트 세그먼트 레지스터와 16비트 오프셋을 중첩시킨 20비트 물리 주소를 생성

[그림 2] 20비트 메모리 주소 구성 방법

 

  • 예로 세그먼트 주소인 CS 레지스터가 0x2525h이고 오프셋인 IP가 0x95F3h
  • 0x2525h 뒤에 한 자리의 0x0h를 붙여 95F3h를 더한 2E843h가 실제 가리키는 물리 주소가 됨
  • 이를 2525h : 95F3h 또는 [CS] : 96F3h로 표현

 

모든 메모리 접근에는 세그먼트와 오프셋이 함께 필요하다.

 

세그먼트 하나를 사용하면 64KB(2**16 = 65,536) 메모리 사용 가능하다. 

 

 

[표 1] 세그먼트 레지스터별 기본 오프셋 레지스터

세그먼트 레지스터 오프셋 레지스터
CS IP
DS SI, DI, BX
SS SP, BP
ES SI, DI, BS

 

각 세그먼트 레지스터마다 사용하는 오프셋 레지스터는 위 표와 같다.

 

실제 모드는 16비트 CPU 8086, 80286뿐만 아닌 80386 이상의 CPU에서도 지원한다.

 

모든 프로그램은 처음 시작할 때는 실제 모드에서 동작한다.

 

[그림 3] 20비트 메모리 주소 구성 예

 

2-2. 보호 모드

  • 32비트 주소 버스 사용

  • 4GB 메모리 사용 가능

  • 메모리 보호 기능 & 페이징 등으로 가상 메모리 효율적 구현 가능

  • 인터럽트 & 예외 처리 등 모두 보호 모드에서 지원하는 기능을 활용

  • 세그먼테이션과 페이징을 이용 > 메모리 관리

  • 세그먼테이션은 4GB 메모리를 세그먼트 단위로 쪼갠 것으로, 16비트 셀렉터와 32비트 오프셋을 이용해 4GB 범위 32비트 선형 주소를 생성하는 일을 함

 

[그림 3] 보호 모드에서 메모리 변환 과정

 

만들어진 선형 주소는 위 그림과 같이 메모리를 4KB 단위로 쪼개서 관리하는 페이징을 이용 > 물리 주소로 변환한다.


# Reference

 

https://www.hanbit.co.kr/store/books/look.php?p_code=B3283906872

+ Recent posts