IA-32 Register
** 읽어주세요 **
이 내용은 '리버싱 핵심 원리'책에서 대부분 참고했습니다. 이 글보다는 직접 책을 읽으시는 것을 추천드립니다. 책에 대한 자세한 정보를 얻고 싶으시다면 여기로
레지스터는 CPU 내부에 존재하는 다목적 저장 공간이다. 우리가 일반적으로 메모리라고 얘기하는 RAM(Random Access Memory)와는 그 성격이 다르다. CPU가 RAM에 있는 데이터를 액세스하기 위해서는 물리적으로 먼 길을 돌아가야 하기 때문에 시간이 오래 걸린다.
어셈블리 명령어의 대부분은 레지스터를 조작하고 그 내용을 검사하는 것들이다. 하지만, 정작 레지스터를 모르면 명령어 자체도 이해하기 힘들다.
IA-32는 지원하는 기능도 무척 많고, 그만큼 레지스터의 수도 많다. 애플리케이션 디버깅의 초급 단계에서는 Basic program execution register에 대해서 알아두어야 한다. 디버깅을 할 때 가장 많이 보게 될 레지스터이기 때문이다.
Basic program executeion registers는 다시 4개의 그룹으로 나눌 수 있다.
범용 레지스터 (General Purpose Registers) (32bit – 8개)
세그먼트 레지스터 (Segment Registers) (16bit – 6개)
프로그램 상태와 컨트롤 레지스터 (Program Status and Control Register) (32bit – 1개)
Instruction Pointer (32비트 – 1개)
먼저 범용 레지스터에 대해서 설명하겠다. 범용 레지스터는 이름처럼 범용적으로 사용되는 레지스터들이다. IA-32에서 범용 레지스터들의 크기는 각각 32비트(4바이트)이다. 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 하고, 또한 어떤 레지스터들은 특수한 용도로 사용되기도 한다.
산술연산(ADD, SUB, XOR, OR 등) 명령어에서 상수/변수 값의 저장 용도로 사용되는 레지스터는 아래 4개가 있다.
EAX : Accumulator for operands and results data
EBX : Pointer to data in the DS segment
ECX : Counter for string and loop operations
EDX : I/O pointer
어떤 어셈블리 명령어 (MUL, DIV, LODS 등) 들은 특정 레지스터를 직접 조작하기도 한다(이런 명령어가 실행된 이후에 특정 레지스터들의 값이 변경된다). 추가적으로 ECX와 EAX는 특수한 용도로도 사용된다. ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용된다.(루프를 돌 때마다 ECX를 1씩 감소시킨다). EAX는 일반적으로 함수 리턴 값에 사용된다. 모든 Win32 API 함수들은 EAX에 리턴 값을 저장한 후 리턴한다.
메모리 주소를 저장하는 포인터로 사용되는 레지스터는 아래 4개가 있다.
EBP : Pointer to data on the stack (in the SS segment)
ESI : source pointer for string operations
EDI : destination pointer for string operations
ESP : Stack pointer (in the SS segment)
*SS segment는 스택 세그먼트를 말한다. (밑에 내려 보시면 설명이 있습니다.)
ESP는 스택 메모리 주소를 가리킨다. 어떤 명령어들 (PUSH, POP, CALL, RET)은 ESP를 직접 조작하기도 한다. (스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 다른 용도로 사용해선 안된다)
EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줌으로써 스택이 깨지지 않도록 한다(이것을 Stack Frame 기법이라고 한다).
ESI와 EDI는 특정 명령어들(LODS, STOS, REP MOVS 등)과 함께 주로 메모리 복사에 사용된다.
두 번째로 세그먼트 레지스터에 대해 설명 하겠다. 세그먼트 레지스터 관련 내용은 초보자에게는 어려운 지식이므로 향후 공부하도록 하겠다.
IA-32 보호 모드에서 세그먼트는 메모리를 조각내어 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말한다. 세그먼트 메모리는 Segment Descriptor Table(SDT)라고 하는 곳에 기술되어 있는데. 세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있다.
세그먼트 레지스터는 총 6개 (CS, SS, DS, ES, FS, GS)이며 각각의 크기는 16비트이다. 각 세그먼트 레지스터의 이름은 아래와 같다.
CS : Code Segment
SS : Stack Segment
DS : Data Segment
ES : Extra(Data) Segment
FS : Data Segment
GS : Data Segment
이름 그대로 CS는 프로그램의 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타낸다.
세 번째로 프로그램 상태와 컨트롤 레지스터에 대해서 설명하겠다.
EFLAGS : Flag Register
플래그 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기이다.(EFLAGS 레지스터 역시 16비트의 FLAGS 레지스터의 32비트 확장 형태이다)
EFLAGS 레지스터는 각각의 비트마다 의미를 가지고 있다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 혹은 True/False를 의미한다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅된다. 32개의 각 비트 의미를 전부 이해한다는 것은 상당히 어려운 일이기 때문에 어렵다면 애플리케이션 디버깅에 필요한 3가지 flag(ZF, OF, CF)에 대해서 잘 이해하면 된다. 이들 3개의 플래그가 중요한 이유는 특히 조건 분기 명령어(Jcc)에서 이들 Flag의 값을 확인하고 그에 따라 동작 수행 여부를 결정하기 때문이다.
Zero Flag(ZF)
연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅된다.
Overflow Flag(OF)
부호 있는 수(signed integer)의 오버플로가 발생했을 때 1로 세팅된다. 그리고 MSB(Most Significant Bit)가 변경되었을 때 1로 세팅된다.
Carry Flag(CF)
부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅된다.
마지막으로 Instruction Pointer에 대해서 설명하겠다.
Instruction Pointer는 CPU가 처리할 명령의 주소를 나타내는 레지스터이며, 크기는 32비트(4바이트)이다(16비트의 IP 레지스터의 확장 형태이다). CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킨다. 이런 식으로 계속 명령어를 처리해 나간다.
범용 레지스터들과는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 한다. EIP를 변경하고 싶을 때는 특정 명령어(JMP, Jcc, CALL, RET)를 사용하거나 인터럽트(interrupt), 예외(exception)를 발생시켜야 한다.
댓글
댓글 쓰기