Registor
프로세서 레지스터라고도 하는 레지스터 는 RAM보다 쉽게 액세스할 수 있도록 프로세서 주변에 흩어져 있는 빠른 메모리 위치입니다. 레지스터는 빠르기 때문에 일시적이고 지속적으로 액세스되는 데이터를 보유합니다. 그들은 크기가 상당히 작은 경향이 있습니다.
회로의 레지스터에는 크기를 결정하는 플립플롭 이 포함되어 있습니다. 32비트 레지스터에는 32개의 플립플롭이 있습니다. 레지스터에는 데이터 전송을 제어하기 위한 게이트도 포함되어 있습니다
- 레지스터는 일반적으로 소량의 빠른 스토리지로 구성되지만 일부 레지스터에는 특정 하드웨어 기능이 있으며 읽기 전용 또는 쓰기 전용일 수 있습니다.
- 어셈블리어는 레지스터 변수로 이루어진 언어이다.?
레지스터는 일반적으로 보유할 수 있는 비트 수로 측정됩니다( 예: “ 8비트 레지스터”, “ 32비트 레지스터” 또는 “ 64비트 레지스터” 또는 그 이상). 일부 명령어 세트 에서 레지스터는 여러 데이터(벡터 또는 데이터의 1차원 배열 )를 로드할 수 있는 더 작은 메모리(예: 32비트에서 4개의 8비트 메모리)로 저장 메모리를 분해하는 다양한 모드에서 작동할 수 있습니다.
CPU와 레지스터
연산장치는 CPU의 핵심 부분 중 하나로, 산술과 논리 연산을 수행하는 연산 회로 집합으로 구성됩니다.
제어장치는 입력, 출력, 기억, 연산 장치를 제어하고 감시하며, 주기억 장치에 저장된 명령을 차례로 해독하여 연산 장치로 보내 처리하도록 지시합니다.
레지스터는 처리 중인 데이터나 처리 결과를 임시 보관하는 CPU 내의 기억 장치로, 대개 연산 장치나 제어 장치에 함께 포함되어 있습니다.
동작과정
레지스터는 다양한 작업을 수행하는 데 사용됩니다. 디지털 시스템에서 레지스터는 CPU의 기능을 수행하기 위해 사용됩니다. 시스템에 입력을 주면 입력이 레지스터에 저장되고, 동작 결과를 시스템이 제공하면 결과 또한 레지스터에 저장됩니다.
- Fetch는 사용자 명령과 주 기억장치에 저장된 프로그램을 CPU프로세서로 가져옵니다.
- Decode는 가저온 명령을 해석합니다.
- Execute는 CPU에 의해 생성된 결과를 메모리에 저장합니다.
레지스터의 종류
유형 | 기본 기능 |
---|---|
메모리 주소 레지스터(MAR) | 프로세서에 필요할 수 있는 모든 명령어 세트의 메모리에 주소를 보유합니다. |
메모리 버퍼/데이터 레지스터(MBR/MDR) | 주 메모리에서 또는 주 메모리로 데이터를 가져옵니다. 적절한 시간까지 이러한 데이터를 버퍼링합니다. |
프로그램 카운터(PC) | 프로세서가 실행할 다음 명령어 세트의 주소를 보유합니다. |
누산기 레지스터(AR) | CPU에서 수학적 산술 계산의 중간 값을 보유하는 임시 저장 위치로 사용합니다. |
명령어 레지스터(IR) | 현재 스포레서에서 실행 중인 명령어 세트가 여기에 저장됩니다. |
데이터 레지스터(DR) | 정수와 같은 숫자 데이터 값, 일부 아키텍처에서는 부동 소수점 값, smasl 비트 배열 및 기타 데이터를 보유합니다. |
주소 레지스터 | 주 메모리에 간접적으로 접근하는 명령어가 사용하는 홀드 주소입니다. |
C언어 register변수
void main()
{
register int cnt;
register long sum;
...
}
위의 예제를 살펴보면 1000번의 참조연산을 하게 되는데 이렇게 많은 연산이 필요할 때, C언어 register 변수는 빠른 처리속도 측면에서 좋습니다.
하지만 빠르다고 모든 변수를 C언어 레지스터로 처리하게되면 문제가 발생할 수 있습니다. 왜냐하면 개수가 많아질수록 CPU에 무리를 주게되므로 너무 잦은 사용은 프로그램 전체속도를 느려지게합니다.
그 이유로는 프로그램 처리에 사용되어야할 리소스를 전체프로그램이 종료될때까지 차지하고 있기 때문입니다.
메모리개념을 사용할 수 없기 때문에, 주소의 개념도 없습니다. 그렇기 때문에 주소에 관한 &연산자는 사용이 불가능합니다. register키워드는 오로지 지역변수에만 사용할 수 있습니다.
스택과 레지스터
우리가 프로그램을 실행시키면 각각의 데이터들은 하드디스크로부터, 램(메인 메모리)위에 올라와 차근차근 실행되어집니다. 그런데 이 데이터들이 무질서하게 올라와 지는 것이 아니라, 각각의 데이터 특성에 따라 구분지어 영역을 갖게 되는데 그중 하나가 스택 영역이라는 것 입니다.
레지스터 ESP와 EBP는 이러한 스택공간의 주소값을 저장하기 위해서 설계된 레지스터 들입니다.
ESP
Extended Stack Pointer라고 하며, 범용적으로 SP라고 불립니다.
현재 스택의 최상단 주소값을 저장하고 있는 레지스터입니다. 스택의 주소는 높은 값에서 낮은 주소로 할당되므로, 최상단의 주소는 가장큰값이 아니라 가장 작은 값이 됩니다. 데이터가 스택 내에서 계속 쌓이거나, 반환되기 위해 필수적으로 필요한 레지스터라고 말할 수 있습니다.
참고로 반환된 주소의 데이터는 바뀌지 않고 그대로 남아있게 됩니다. 이유는 굳이 임의의 수로 초기화하지 않아도, ESP의 값만 바꿔주면 사실상 문제 될 것이 없기 때문입니다.
EBP
Extended Base Pointer라고 하며, 현재 스택 프레임의 베이스 주소를 담습니다. 기본적으로 ESP, EBP는 어셈블리 차원이 아니라, 프로그램이 어떻게 돌아가는지 다룰때도 필수적으로 공부해야 하는 중요한 레지스터들입니다.
- 스택 프레임이란 함수 호출 과정에서 할당되는 메모리 블록을 가르킵니다.
여기서 함수 fct2의 지역변수 e와 h는 경계를 구분짓기 위한 하나의 블록으로 묶이게 되는데(스택 내에서), 이를 스택 프레임이라 합니다. 그리고 이러한 스택 프레임의 base 주소, 바로 이 주소를 저장하는 녀석이 EBP인 것입니다. EBP는 intel cpu 레지스터 이름이고, 일반적으로 프레임 포인터(Frame pointer, FP)라고 불립니다.
프레임 포인터와 스택 포인터를 이용해서 Stack내에서는 함수호출을 하고, 반환하여 다시 돌아가고 하는 등의 프로그램의 실행흐름을 만들어 낼 수 있습니다.
어셈블리 레지스터
용어 | 설명 |
---|---|
EAX | 사칙연산 등 산술 연산에 자동으로 사용되며, 함수의 반환값을 처리할때도 EAX 레지스터가 사용됩니다. |
ESP | 하나의 스택프레임의 끝 지점 주소가 저장됩니다. PUSH/POP 명령어에 의해 값이 4씩 변합니다. |
EBP | 하나의 스택프레임의 시작주소가 저장됩니다. 현재 스택프레임 동안에는 절대로 값이 바뀌지 않다가 현재 스택 프레임이 소멸되면 이전 스택프레임을 가리키게 됩니다. |
EIP | 다음에 실행할 명령어의 주소를 가지고 있는 레지스터입니다. 현재 실행하고있는 명령어가 종료되면 EIP 레지스터에 있는 명령어를 실행하게 됩니다. |
SFP | 스택프레임을 거치고, 함수가 돌아가야할 위치를 저장해둔 포인터 입니다. 즉, 함수가 호출되기전의 스택의 흐름을 그대로 유지하기위한 레지스터라고 할 수 있습니다. |
어셈블리 명령어
용어 | 설명 |
---|---|
Add | 1에 2를 더하여 1에 저장합니다. ADD eax 100 => eax = eax + 100 |
SUB | 1에 2를 빼서 1에 저장합니다. SUB esp 30 => esp - sup - 30 |
MOV | 1에 2의값을 입력합니다. MOV ebp 20 => ebp == 20 |
XOR | 1과 2가 XOR 연산됩니다. 다른 연산도 있습니다. |
PUSH | ESP 레지스터가 가지고있는 메모리주소에 1을 복사하고, ESP의 값을 -4 합니다. |
POP | ESP 레지스터가 가지고있는 메모리주소에 1을 복사하고, ESP의 값을 +4 합니다. |
LEA | 1에 2의 주소값을 입력합니다. |
JMP | 2로 갑니다. C언어에서 goto문으로 생각할 수 있습니다. |
CALL | 2로 갑니다. 함수 호출시 사용됩니다. JMP명령어 같이 프로그램의 실행 흐름이 변경되지만 JMP명령과 다른 점은 되돌아올 위취를 스택에 저장한다는 것 입니다. |
RET | CALL한 주소로 돌아갑니다. |
NOP | 아무것도 하지 않습니다. |
스택 프레임과 함수 호출(Stack Frames and Function Calls)
스택은 반환 주소(return address), 함수 매개 변수, 지역 변수 등 함수 호출과 관련된 데이터를 위한 메모리 영역입니다. 스택(Stack)은 후입선출(LIFO, Last-In-First-Out)자료구조입니다. 바이너리의 스택이라는 이름이 여기서 유래되었습니다.
LIFO 방식이 함수를 호출하고 반환하는 방식과 일치하기 때문에 함수 호출을 위해 스택 자료 구조를 사용합니다. 즉 마지막으로 호출된 함수가 가장 먼저 반환하게 됩니다.
위 그림을 보면 스택은 0x7ffffffff8000에서 시작하며 a부터 e까지의 값을 가지고 있습니다. 더 낮은 주소에서는 초기화되지 않은 메모리가 ‘?’로 표시되어 있습니다.
스택 포인터 레지스터인 rsp는 항상 스택의 최상단을 가리키며, 가장 최근에 push한 값이 여기에 위치합니다. 처음(왼쪽 그림)은 e를 최근에 삽입하여, rsp가 e를 가리키는 모습니다.
Stack Frame
x86 리눅스 프로그램의 각 함수들은 스택에 자체 함수 프레임(스택 프레임)이 있고, 이는 이 스택 프레임의 하단을 가리키는 base pointer 레지스터 rbp와 상단을 가리키는 rsp로 구분됩니다.
함수 프레임은 함수의 스택 기반 데이터를 저장하는 데 사용됩니다. 함수 호출이 수행될 때마다 새 스택 프레임이 생성됩니다. caller함수의 스택 프레임이 복원되고 실행이 calling 함수로 실행이 넘어갈 때부터, return할 때까지의 그 함수의 스택 프레임이 유지됩니다.
레지스터
범용 레지스터
범용 레지스터(General Purpose Register)란 작은 데이터의 임시 저장 공간으로, 연산 처리 및 데이터의 주소를 지정하는 역활을 합니다. 컴퓨터의 장치들을 제어하는 역활 또는 수행합니다.
종류 | 설명 |
---|---|
EAX | 산술 연산 및 논리 연산 수행 |
EBX | 메모리 주소 저장 |
ECX | 반복문 사용 시 반복 카운터로 사용, 반복할 횟수 지정하고 반복 작업 수행 |
EDX | EAX레지스터와 같이 쓰임, 부호 확장 명령 등에 사용, 큰 수의 곱셈 또는 나눗셈 연산 |
EDI | 복사할 때 목적지 주소 저장 |
ESI | 데이터를 조작하거나 복사할 때 데이터의 주소 저장 |
ESP | 메모리 스택의 끝 지점 주소 포인터 |
EBP | 메모리 스택의 첫 시작 주소 포인터 |
EIP | 다음에 실행해야 할 명령어의 주소 포인터 |
세그먼트 레지스터
세그먼트에 대한 주소 지정을 제공합니다. PC계열에서 사용되고 있는 인텔 프로세서들은 자신의 주소 지정 능력을 제공합니다.
종류 | 설명 |
---|---|
CS | 기계 명령을 포함한 코드 세그먼트의 시작 주소를 가리킴 |
DS | 프로그램에 정의된 데이터 세그먼트의 시작 주소를 가리킴 |
SS | 실행 과정에서 필요한 데이터나 연산 결과 등을 임시로 저장하거나 삭제할 때 사용하는 스택 세그먼트의 시작 주소를 가리킴 |
ES | 추가로 사용된 데이터 세그먼트의 주소를 가리킴 |
FS, GS | 사용처 미정, 여분 레지스터 |
- 세그먼트(Segment)란 프로그램에 정의된 특정 영역으로, 코드, 데이터, 그리고 스택을 포함합니다.
플래그 레지스터(Flag Register)
마이크로 프로세서에서 다양한 산술 연산 결과의 상태를 알려주는 플래그 비트들을 저장합니다. 조건문과 같은 실행 순서의 분기를 저장할 때 주로 사용됩니다.
종류 | 설명 |
Z | 제로 플래그, 연산 결과가 0일 경우 참 |
C | 캐리 플래그, 부호 없는 숫자으 ㅣ연산 결과가 비트 범위를 넘어섰을 경우 참 |
A | 보조 캐리 플래그, 연산 결과 하위 4bit에서 비트 범위를 넘어섰을 경우 참 |
O | 오버플로우 플래그, 부호 있는 숫자의 연산 결과가 비트 범위를 넘어섰을 경우 참 |
S | 사인 플래그, 연산 결과가 음수일 경우 참 |
P | 패리티 플래그, 연산 결과에서 1로 비트의 수가 짝수일 경우 참 |
D | 디렉션 플래그, 문자열 조작에서 참일 경우 주소 레지스터 값이 감소, 거짓일 경우 증가 |
T | 트랩 플래그, 참일 경우 한 명령이 실행될 때마다 인터럽트 발생, 디버깅에 사용. |
함수의 호출 규약
CPU가 실행되는 동안 함수가 호출될 때 이동되는데 이것이 어떻게 이루어지는 가를 보여줍니다.
- Program Counter(PC)는 다음번 명령어의 주소를 나타냅니다. PC는 어디까지 왔어 순서가 햇갈리지 않도록 알려주는 역할을 합니다. 달리말하면 CPU는 프로그램 카운터가 가리키는 위치에 있는 명령어를 실행하게 됩니다. 즉, 함수 호출, 실행의 이동은 PC의 이동이라고 할 수 있습니다.
함수 호출 규약이라는 약속
함수 호출 규약이란, 어떻게 함수 호출을 정의할 것인가에 대해서 의미합니다. A함수가 있고 B함수가 있을 때,
- 프로그램 카운터를 어디다 빽업하고,
- 링크드 레지스터는 어디다 넣어주고,
- …
이런 일련의 과정들이 함수 호출과정에서 이루어지고, 역순으로 이루어지는 일들이 반환과정이라고 하는데, 이를 누군가가 가지고 있어야 합니다.
예를 들어 A함수가 B함수를 호출해야 할 때, 반환하는 코드를 A함수에 넣을 것인가 B함수에 넣을 것인가 약속을 해야 합니다.
- A가 반환과정을 가지고 있고, B가 반환 과정을 가지면 문제가 생깁니다.
- A가 오른쪽에서 부터 전달하면 B는 오른쪽에서부터 받아줘야합니다. 이러한 약속을 지키지 않으면 엉뚱한 데이터가 들어가게 됩니다.
함수호출 규약을 보면,
- 32bit 환경에서는 네가지를 약속하는 것을 볼 수 있습니다.
- 64bit에서는 크게 Windows, Linux로 두가지로 나뉘었습니다.
여기서 중요한 것은 누가 함수를 깨끗하게 하는지 볼 수 있습니다.
- __cdecl은 Caller가, __stdcall, __fastcall, __thiscall은 Function이 깨끗하게 합니다.
- 다만 32bit에서는 __fastcall은 ecx, edx 두개까지는 레지스터에 저장하고, __thiscall은 ecx하나만 이용합니다.
- 64bit로 가면 파라메터 호출 순서는 C스타일이고 스택을 깨끗하게 하는 것인 Caller가 하게 됩니다.
- 64bit는 레지스터가 좀더 여유가 생겨서 Windows는 4개까지는 빠르게 처리하겠다는 것 입니다. 리눅스의 경우 9개는 사용하겠다 합니다.
함수 호출 규약을 알아야 하는 이유
다만 함수 호출규약을 알아야 하는 이유는
- 여러 언어로 작성된 모듈을 결합하거나 작성된 언어가 아닌 다른 언어에서 운영 체제 또는 라이브러리 API를 호출할 때 호출자와 수신자가 사용하는 호출 규칙을 조정하기 위해 특별한 주의를 기울여야 합니다.
- 단일 프로그래밍 언어를 사용하는 프로그램이라도 코드 최적화를 위해 컴파일러에서 선택하거나 프로그래머가 지정하는 여러 호출 규칙을 사용할 수 있습니다.
함수 호출 규약이란, 함수 호출 시 일어나는 행동에 대한 규칙을 의미하며, 함수 인자들에 대해 어떤 순서로 스택에 쌓을 것이지, 인자를 레지스터로 이용할 것 인지 그리고 함수 종료 후 스택을 누가 정리할 것 인지에 대해 정해놓은 규칙입니다.
어떤 함수 규약이 호출되던, 공통되게 일어나는 일
- 모든 인자들은 4바이트(8바이트)로 확장되고 적절한 메모리 위치로 삽입됩니다. 이 위치들은 주로 스택 상 메모리이지만 레지스터들을 사용할 수 도 있습니다. 이는 호출 규약에 따릅니다.
- 프로그램은 실행은 호출된 함수의 주소로 점프합니다.
- 함수 안에서 보존 레지스터들이 스택에 저장됩니다. 함수 프롤로그라고도 하며 컴파일러가 작성합니다.
- 함수에 해당하는 코드들이 실행되고 return 반환값이 eax 레지스터에 저장됩니다.
- 3에서 저장한 레지스터들을 스택에서 꺼내 복구합니다. 함수 에필로그라고도 하며 컴파일러가 작성합니다.
- 스택에서 함수 인자들이 제거됩니다. 이 과정은 스택 비우기로 불리고 피호출자 함수 내에서 실행되거나 호출자에 의해 실행됩니다. 이는 함수 호출 규약에 따릅니다.
호출 규약
호출 규약(콜 컨벤션, calling convention)이란, 호출자(caller)와 피호출자(callee)간 함수의 인자를 전달하는 방식에 대한 규약을 정의한 것을 의미합니다.
인수 전달 및 명명 규칙
Microsoft C++ 컴파일러를 사용하면 함수와 호출자 간에 인수 및 반환 값을 전달하기 위한 규칙을 지정할 수 있습니다. 지원되는 모든 플랫폼에서 모든 규칙을 사용할 수 있는 것은 아니며 이부 규칙은 플랫폼별 구현을 사용합니다. 대부분의 경우 특정 플랫폼에서 지원되지 않는 규칙을 지정하는 키워드 또는 컴파일러 스위치는 무시되고 플랫폼 기본 규칙이 사용됩니다.
Keyword | Stack cleanup | Parameter passing |
---|---|---|
__cdecl | Caller | 역순(오른쪽에서 왼쪽)으로 스택에 매개변수를 푸시합니다. |
__clrcall | n/a | CLR표현식 스택에 매개변수를 순서대로(왼쪽에서 오른쪽으로 로드합니다. |
__stdcall | Callee | 역순(오른쪽에서 왼쪽)으로 스택에 매개변수를 푸시합니다. |
__fastcall | Callee | 레지스터에 저장된 다음 스택에 푸시됩니다. |
__thiscall | Callee | 스택에 푸시됨, this 포인터는 ECS에 저장됩니다. |
__vectorcall | Callee | 레지스터에 저장됩니다. 역순(오른쪽에서 왼쪽)으로 스택에 푸시됩니다. |