전에 글에서 16비트 체제에선 ax,bx,cx,dx,si,di,bp,sp로 썼다는 것을 봤을 것이다.

32비트 체제로 넘어가서는 앞에 e(extend)를 넣어주어 eax,ebx,ecx,edx,esi,edi,ebp,esp로 사용한다.

그리고 64비트 체제에서는 앞에 r을 붙여 rax,rbx,rcx,rdx,rsi,rdi,rbp,rsp로 사용한다.


Ι................Ι................Ι................Ι................Ι................Ι................Ι................Ι................Ι

(<- 8비트 ->)  -> EX ) AL,BL,CL,DL,SIL,DIL,BPL,SPL

(<------16 비트 ------>) -> EX ) AX, BX, CX, DX, SI, DI, BP, SP

(<---------------- 32비트 ---------------->) -> EX ) EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP

(<------------------------------------- 64비트 -------------------------------------->)

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


64 비트 체제 전에는 그냥 파라미터를 전달했지만, 64비트 체제에서는 레지스터리를 통해서 파라미터를 전달한다.



64비트 체제에서는 리눅스와 윈도우의 파라미터 전달 방식이 나뉘어 집니다.


리눅스에서는 파라미터를 전달할 때 6개 레지스터를 사용한다.

 

RDI : 첫번째 인자

RSI : 두번째 인자

RDX : 세번째 인자

RCX : 네번째 인자

R8 : 다섯번째 인자 -> 추가된 범용 레지스터

R9 : 여섯번째 인자 -> 추가된 범용 레지스터


인자가 7개 이상일 경우부터는 스택을 같이 사용한다.



윈도우는 리눅스와 달리 레지스터를 4개까지 사용한다.


RCX : 첫번째 인자

RDX : 두번째 인자

R8 : 세번째 인자 -> 추가된 범용 레지스터

R9 : 네번째 인자 -> 추가된 범용 레지스터


리눅스와 마찬가지로 5개 이상일 경우 스택과 같이 사용한다.

함수가 호출되는 과정



1. 인자를 스택에 집어넣는다

호출된 함수가 인자를 받을 수 있도록 스택에 집어넣습니다.


2. 함수를 호출합니다

Call 명령이 실행되면 CPU는 EIP 레지스터, 즉 함수 호출이 종료된 뒤 돌아와야 할 코드의 주소를 스택에 집어 넣은 뒤,

Call 명령 뒤에 이어지는 인자를 EIP 레지스터에 로드한다.

스택에 집어넣은 EIP 레지스터 값은 이후 함수가 종료된 뒤에 돌아올 코드 위치가 된다.


3. 프레임 포인터를 설정한다

호출된 함수는 호출한 함수의 프레임 포인터를 스택에 보관하고, 그 시점의 스택 포인터 ESP를 프레임 포인터로 설정한다.

설정된 프레임 포인터는 스택 프레임 정보를 참조하는 기준 값으로 사용하게 된다.


4. 로컬 변수를 위한 공간을 할당한다

호출된 함수에서 사용할 로컬 변수를 저장할 공간을 준비한다.

이 작업은 스택 포인터를 이동시키는 것으로 완료된다.

즉 이 함수에서 로컬 변수는 EBP와 ESP 사이의 공간을 사용하게 되는 것으로,

프레임 포인터를 기준으로 첫 번째 로컬 변수는 [ebp-4], 두 번째 로컬 변수는 [ebp-8]과 같은 식으로 참조하게 된다.


5. 호출한 함수의 실행 상태를 보존한다

호출된 함수에서 범용 레지스터를 사용하는 경우, 이전 함수에서 범용 레지스터를 저장해 둔 값이 지워져서

원래 함수로 돌아간 뒤 정상적으로 실행을 재개할 수 없게 되니, 안전하게 이전 함수의 실행 내역을 보존하기 위해서

범용 레지스터의 내용을 스택에 보존한다.


6. 함수를 실행한다

함수의 실행에 필요한 준비과 되었으므로 실제 함수의 연산을 수행한다.


7. 호출한 함수의 실행 상태를 복구한다

5번 과정에서 저장한 레지스터를 복구한다.

⚠︎ 스택을 사용하고 있기 때문에 넣은 순서와 반대의 순서로 꺼내고 있다는 점에 주의해야 한다.


8. 스택을 정리하고, 프레임 포인터를 복구한다

로컬 변수 할당 등으로 사용한 원래대로 복구 한다.

이 과정은 함수에 진입할 시점의 스택 포인터를 복구하는 것으로 완료된다.

그 후 현재 프레임 포인터 위치에 있는 이전 함수의 프레임 포인터를 복구한다.


9. 함수로부터 돌아간다

ret 명령을 수행하면 스택에 저장해 둔 리턴 주소를 꺼내서 EIP에 로드 한다.

이로써 호출된 함수에서 완전히 빠져나와 원래 함수로 돌아온 상태가 된다.


10. 스택에 집어넣은 인자를 정리한다

cdecl 호출 규약에서는 호출자가 인자를 정리해야 하므로, 

스택 포인터에 (인자당 바이트 수) * ( 인자의 개수) 만큼 이동시켜서 인자를 꺼내는 처리를 수행한다.

이것으로 함수 호출 과정이 끝나며 리턴값을 갖는다면 eax 레지스터에 함수 호출의 결과값이 들어있게 된다.




함수 호출 규약이나 예외 처리를 사용했는가의 여부에 따라서 스택 프레임의 구조는 약간씩 달라질 수 있다.

위의 과정에서 3 ~ 5 번에 해당하는 함수 실행 준비 과정을 프롤로그 (Prolog) 라고 부르며,

7 ~ 9번의 함수 실행을 마무리 하는 프롤로그의 반대 과정을 에필로그 (Epilog) 라고 부른다.

메모리 구조는 메모리를 필요에 따라 여러가지 종류로 나누어 둠을 의미한다.

나누는 이유는 유사한 성향의 데이터를 묶어서 저장하면 관리가 용이해지고 접근속도가 향상되기 때문이다.

일반적으로 4가지, 좀 더 세분화 하면 5가지 정도로 구분된다.


⚀ Code 영역


코드 자체를 구성하는 메모리 영역으로 Hex파일이나 BIN 파일 메모리가 있다. 

프로그램 명령이 위치하는 곳으로 기계어로 제어되는 메모리 영역이다.


⚁ Data 영역


전역변수(global), 정적변수(static), 배열(array), 구조체(structure) 등이 저장된다.


     🁀 초기화 된 데이터는 data 영역에 저장되고,

     🁁 초기화 되지 않은 데이터는 BSS (Block Stated Symbol) 영역에 저장된다.


프로그램이 실행 될 때 생성되고 프로그램이 종료 되면 시스템에 반환 된다.

함수 내부에 선언된 Static 변수는 프로그램이 실행 될 때 공간만 할당되고, 그 함수가 실행 될 때 초기화 된다.

 

   

⚂ Heap 영역


필요에 의해 동적으로 메모리를 할당 하고자 할 때 위치하는 메모리 영역으로 동적 데이터 영역이라고 부르며, 

메모리 주소 값에 의해서만 참조되고 사용되는 영역이다.

이 영역에 데이터를 저장 하기 위해서 C는 malloc(), C++은 new() 함수를 사용한다.

 

   

⚃ Stack 영역


프로그램이 자동으로 사용하는 임시 메모리 영역이다.

지역(local) 변수, 매개변수(parameter), 리턴 값 등 잠시 사용되었다가 사라지는 데이터를 저장하는 영역이다.

함수 호출 시 생성되고, 함수가 끝나면 시스템에 반환 된다.

스택 사이즈는 각 프로세스마다 할당 되지만 프로세스가 메모리에 로드 될 때 

스택 사이즈가 고정되어 있어, 런타임 시에 스택 사이즈를 바꿀 수는 없다.

명령 실행시 자동 증가/감소 하기 때문에 보통 메모리의 마지막 번지를 지정 한다.



< 구체적인 메모리 영역 >




 

   



자료 출처 : http://sfixer.tistory.com/30


함수 호출 규약이란 함수를 호출 할 때 *파라미터를 어떤식으로 저장하는가에 대한 일종의 약속입니다.

함수를 호출하는 방식에 대해 인수는 어떻게 전달하며 리턴값은 어떻게 반환할 것이고 인수 전달을 위해 메모리는 누가 정리할 것인지를 규정한다.


*파라미터(Parameter)  :  매개변수라는 뜻으로 함수를 정의 할 때 외부로 받아들이는 임의의 값을 의미한다.



Ⅰ. 스택 (Stack)


호출 규약에 대해 알기 위해서는 스택에 대해 알아야 한다.

스택은 시스템이 사용하는 메모리 공간이며 CPU가 임시적인 정보를 저장할 필요가 있을 때 이 영역을 사용한다.

 


 

일반적인 운영체제의 메모리 구조는 위의 그림과 같다.

앞부분에는 프로그램의 코드, 이어서 데이터 영역, 자유영역인 힙이 있다.

스택은 메모리의 가장 뒷부분에 있는데 힙과 스택이 만나게 되면 메모리가 부족한 상태가 된다.




 

Ⅱ. 스택 프레임 (Stack Frame)



함수가 호출될 때 스택에는 함수로 전달되는 인자, 실행을 마치고 돌아올 복귀 번지, 

지역 변수등의 정보들이 저장된다. 이때 스택에 저장되는 함수의 호출 정보를 스택 프레임이라고 한다.

또한 함수 실행중에도 필요할 경우 임시적인 정보 저장을 위래 스택을 사용하되 이때 PUSH 함수와 POP 함수는 

일치하므로 함수가 리턴하면 정확하게 호출 전의 상태로 돌아가 항상성을 유지한다.

즉, 함수가 호출될 때 인수와 복귀 번지, 지역변수 영역등을 가진 

스택 프레임이 생성되고 리턴된 후 정확하게 복구하도록 되어있다.


⚀ 함수 호출에 대한 주요 내용


1. 인수도 함수 호출 중에만 유지되는 일종의 지역변수이다. 인수의 초기화 시점은 함수가 호출될 때이다.


2. 지역변수를 많이 선언하는 것과 함수의 실행속도와는 직접적인 상관이 없다.


3. 지역변수를 많이 쓴다고 해서 프로그램이 커지는 것도 아니다.


4. 지역변수를 위해 ESP를 위로 올려 공간만 만들 뿐이므로 별도의 초기식이 없으면 

   지역변수는 초기화 되지 않는다. 이때 원래 공간에 들어있던 값이 바로 쓰레기 값이다. 

   지역변수를 초기화화면 이때는 초기화하는 시간만큼 느려지고 필요한 코드만큼 프로그램의 크기도 늘어난다.


5. 함수를 호출할 때마다 스택프레임이 생성되었다가 사라지는 복잡한 과정을 거치므로

   함수 호출에는 오버헤더가 있다.

   

(?) 오버헤드는 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간 , 

    메모리등을 말한다. 여기서 오버헤더는 오버헤드 인가?




Ⅲ. 호출 규약



기본적인 C/C++ 언어의 호출 규약인  _cdecl은 인수를 뒤쪽부터 

순서대로 전달하며 인수 전달에 사용한 스택 영역은 *호출원이 정리한다. 

이런 호출 규약이 변경되면 스택 프레임의 모양과 관리 방법도 달라진다

아래는 각 호출 규약들에 대해 정리한 것이다.


* 호출원 : 함수를 호출한 곳



 



이 글에선 기초 어셈블리어의 명령어에 대해 소개할 것입니다.

⚠ 꼭 "어셈블리 명령어 이해하기 - 중" 을 읽으신 후 보시기 바랍니다!


 

< 데이터 타입 >


타입

 설 명

 BYTE

 8bit 부호 없는 정수

 SBYTE

 8bit 부호 있는 정수

 WORD

 16bit 부호 없는 정수

 SWORD

 16bit 부호 있는 정수

 DWORD

 33bit 부호 없는 정수

 SDWORD

 32bit 부호 있는 정수

 FWORD

 48bit 정수

 QWORD

 64bit 정수

 TBYTE

 80비트 정수


< 피연산자 (Operand) 타입 >


 

피연산자

 설 명 

 r8

 8bit 범용 레지스터

 r16

 16bit 범용 레지스터

 r32

 32bit 범용 레지스터

 Reg

 임의의 범용 레지스터

 Sreg

 16bit 세그먼트 레지스터

 imm

 8, 16, 32bit 즉시값

 imm8

 8bit 즉시값

 imm16

 16bit 즉시값

 imm32

 32bit 즉시값

 r/m8

 8bt 범용 레지스터, 메모리

 r/m16

 16bit 범용 레지스터, 메모리

 r/m32

 32bit 범용 레지스터, 메모리

 mem

 8, 16, 32bit 메모리


< 어셈블리 명령어 > 


INC(Increase)

피연산자에 1을 더한다.

연산 결과에 따라 ZF(Zero Flag)나 OF(Overflow Flag)가 세트될 수 있다.

ex. INC reg, INC mem

 

DEC(Decrease)

피연산자에 1을 뺀다.

연산 결과에 따라 ZF나 OF가 세트될 수 있다.

ex. DEC reg, DEC mem

 

ADD(Add)

Destination에 Source의 값을 더해서 Destination에 저장한다.

연산 결과에 따라 ZF, OF, CF(Carry Flag)가 세트될 수 있다.

ex. ADD eax(Destination), 100(Source) : eax레지스터에 100을 더해서 eax레지스터에 저장

 

SUB(Subtract)

Destination에 Source의 값을 빼서 Destination에 저장한다.

연산 결과에 따라 ZF, OF, CF가 세트될 수 있다.

ex. Sub eax(Destination), 100(Source) : eax레지스터에 100을 빼서 eax레지스터에 저장

 

MUL(Unsigned Integer Multiply)

부호 없는 al, ax, eax의 값을 피연산자와 곱한다. 피연산자가 8비트이면 al과 곱해서 ax에 저장되고 16비트면 ax와 곱하고 dx(상위16비트):ax(하위16비트)에 저장된다. 연산 결과에 따라 OF, ZF 플래그가 세트될 수 있다.

ex. MUL reg

 

IMUL(Integer Multiplication)

부호 있는 al, ax, eax의 값을 피연산자와 곱한다. 연산결과에 따라 CF, OF가 세트될 수 있다.

ex. IMUL r/m8 : 단일 피연산자이고 피연산자를 al, ax, eax에 곱한다.

     IMUL r16(destination), r/m16(value) : value를 al, ax, eax와 곱해서 destination에 저장

    IMUL r16(destination), r/m8(value), imm8(value) : value끼리 곱해서 destination에 저장

    (연산 결과가 destination 레지스터의 크기보다 크다면 OF, CF가 세트된다.)

 

DIV(Unsigned Integer Divide)

8, 16, 32비트 부호 없는 정수의 나눗셈을 수행한다. 연산결과에 따라 CF, OF, ZF가 세트될 수 있다.

ex. DIV reg

 

MOV(Move)

Source에서 Destination으로 데이터를 복사한다.

ex. MOV reg(Destination), mem(Source)

 

MOVS(Move String)

Source에서 Destination으로 데이터를 복사한다.

ex. MOVS Destination, Source

 

MOVSB, MOVSW, MOVSE(Move String)

SI 또는 ESI 레지스터에 의해 지정된 메모리 주소의 내용을 

DI 또는 EDI 레지스터에 의해 지정되는 메모리 주소로 복사한다.

MOVSB는 BYTE 단위, MOVSW는 WORD 단위, MOVSD는 DWORD 단위로 복사한다. 

방향 플래그(DF)가 1로 세트되어 있으면 ESI와 EDI는 복사 시에 감소하게 되고 

DF가 0으로 세트되어 있으면 ESI와 EDI는 복사 시에 증가하게 된다.

ex. MOVSB, MOVSW, MOVSD

 

MOVSX(Move with Sign-Extend)

BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 부호는 그대로 유지.

ex. MOVSX reg32, reg16

 

MOVZX(Move with Zero-Extend)

BYTE나 WORD크기의 피연산자를 WORD나 DWORD크기로 확장하고 남은 비트는 0으로 채운다.

ex. MOVZX reg32, reg16

 

INT(Interrupt)

소프트웨어 인터럽트를 발생시켜 운영체제의 서브루틴을 호출한다.

ex. INT imm

 

AND(Logical AND)

Destination과 Source 피연산자의 각 비트가 AND 연산된다.

AND 연산은 각 비트가 모두 1일 때만 결과 값이 1이 된다.

ex. Destination : 10011100

      Source      : 11001010 

      결과          : 10001000

     AND reg(Destination), mem(Source) : reg와 mem을 AND 연산한 후 결과를 reg에 저장

AND 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

 

OR(Inclusive OR)

Destination과 Source 피연산자의 각 비트가 OR 연산된다.

OR 연산은 각 비트가 모두 0이면 결과는 0이고 모두 0이 아니면 결과는 1이 된다.

ex. Destination : 10011100

      Source      : 11001010

      결과          : 11011110

      OR reg(Destination), mem(Source) : reg와 mem을 OR 연산한 후 결과를 reg에 저장

 OR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

 

XOR(Exclusive OR)

Destination과 Source 피연산자의 각 비트가 XOR 연산된다.

XOR 연산은 각 비트가 서로 다른 값일 때만 결과가 1이다. 같은 값이라면 결과는 0이 된다.

ex. Destination : 10011100

      Source      : 11001010

      결과          : 01010110

XOR 연산을 통해서 OF, CF가 0으로 세트되고 결과에 따라서 ZF가 1로 세트될 수 있다.

피연산자의 두 값이 같은 값이라면 결과는 항상 0이 된다.

레지스터를 0으로 초기화시킬때 MOV 명령어를 사용하기보다는 XOR reg, reg으로 많이 사용한다.

 

TEST(Test)

두 피연산자 사이에 논리적인 AND 연산을 수행하여 플래그 레지스터에 영향을 주지만 결과값은 저장하지 않는다. OF, CF는 항상 0으로 세트되고 TEST 연산 결과값이 0이면 ZF가 1로

세트, 0이 아니면 ZF가 0으로 세트된다.

ex. TEST reg, reg

 

STC(Set Carry Flag)

캐리 플래그(CF)를 1로 세트한다.

ex. STC

 

CLC(Clear Carry Flag)

캐리 플래그(CF)를 0으로 세트한다.

ex. CLC

 

STD(Set Direction Flag)

방향 플래그(DF)를 1로 세트한다.

ex. STD

 

CLD(Clear Direction Flag)

방향 플래그(DF)를 0으로 세트한다.

ex. CLD

 

STI(Set Interrupt Flag)

인터럽트 플래그(IF)를 1로 세트한다.

ex. STI

 

CLI(Clear Interrupt Flag)

인터럽트 플래그(IF)를 0으로 세트한다.

ex. CLI

 

SHR(Shift Right)

Destination 피연산자를 Source 피연산자의 크기만큼 오른쪽으로 각 비트를 시프트시킨다.

최상위 비트는 0으로 채워지고 최하위 비트는 캐리 플래그(CF)로 복사된다.

ex. SHR reg, imm16

 

SHL(Shift Left)

Destination 피연산자를 Source 피연산자의 크기만큼 왼쪽으로 각 비트를 시프트시킨다.

최상위 비트는 캐리 플래그(CF)로 복사되고 최하위 비트는 0으로 채워진다.

ex. SHL reg, imm16

 

PUSH(Push on Stack)

스택에 값을 넣는다.

ESP의 값이 4만큼 줄어들고 이 위치에 새로운 값이 채워진다.

ex. PUSH reg8

 

PUSHAD(Push All)

EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터의 값을 스택에 PUSH한다.

레지스터들의 값을 보관해야 할 때 사용한다.

ex. PUSHAD

 

PUSHFD(Push Flags)

플래그 레지스터를 스택에 PUSH한다.

플래그 레지스터의 값을 보관해야 할 때 사용한다.

ex. PUSHFD

 

POP(Pop from Stack)

ESP 레지스터가 가리키고 있는 위치의 스택 공간에서 4byte 만큼을 

Destination 피연산자에 복사하고 ESP 레지스터의 값에 4를 더한다.

ex. POP reg16(Destination)

 

POPAD(Pop All Flags from Stack)

스택에 존재하는 값을 EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP 레지스터로 POP한다.

PUSHAD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다.

ex. POPAD

 

POPFD(Pop Flags from Stack)

스택에 존재하는 값을 플래그 레지스터로 POP한다.

PUSHFD 명령어로 스택에 보관해 놓은 레지스터 정보를 다시 이용할 때 사용한다.

ex. POPFD

 

XCHG(Exchange)

두 피연산자의 내용이 서로 교환된다.

XCHG 명령은 imm 값이 피연산자로 올 수 없다.

ex. XCHG reg, mem

 

NEG(Negate)

피연산자의 2의 보수를 계산하여 결과를 피연산자에 저장한다.

ex. NEG reg

 

PTR

피연산자의 크기를 재설정한다.

ex. WORD PTR value : value의 크기를 WORD의 크기로 재설정한다.

 

OFFSET

세그먼트의 시작으로부터 변수가 위치한 거리까지의 상대적 거리를 리턴한다.

ex. OFFSET value : value가 존재하는 위치를 세그먼트 시작 지점으로부터의 상대적 거리를 구한다.

 

LEA(Load Effective Address)

Source 피연산자의 유효 주소를 계산하여 Destination 피연산자에 복사한다.

간단히 주소를 알아내서 복사하는 명령어다.

ex. LEA reg(Destination), mem(Source)

 

REP(Repeat String)

ECX 레지스터를 카운터로 사용해서 문자열 관련 명령을 ECX>0인 동안 반복한다.

한번 진행될 때마다 ECX 레지스터값이 -1 된다.

ex. REP MOVS destination, source

 

JMP(Jump Unconditionally to Lable)

피연산자의 위치로 실행 흐름이 변경된다. 피연산자가 가리키는 코드로 점프 뛰어서 

실행한다고 생각하면 된다. 피연산자에는 레이블이나 레지스터, 메모리 값이 올 수 있다.

short점프는 -127 ~ 127 byte범위 안에서, near점프는 같은 세그먼트 내부에서, far점프는 

현재 세그먼트를 벗어날 때 사용된다. JMP 명령어는 되돌아올 리턴 어드레스 값을 저장하지 않는다.

ex. JMP shortlabel, JMP nearlabel, JMP farlabel

 

CALL(Call a Procedure)

함수 호출시 사용된다. JMP명렁어 같이 프로그램의 실행 흐름이 변경되지만 

JMP명령와 다른 점은 되돌아올 리턴 어드레스(CALL 다음 명령)를 스택에 저장한다는 것이다. 

되돌아올 주소를 저장하기 때문에 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있다. 

호출한 함수가 일을 다 마치면 원래 위치에서 다시 프로그램이 실행될 수 있음을 의미한다.

ex. CALL nearlabel, CALL farlabel, CALL mem16, CALL 함수주소,

CALL DWORD PTR[EAX+8], CALL <JMP to API> : 특정 api 지목

 

CMP(Compare)

두 피연산자를 비교하는 작업을 한다. Destination 피연산자에서 

Source 피연산자를 묵시적으로 빼서 값을 비교한다. 두 피연산자의 값이 같다면 

결과는 0이 되고 제로 플래그(ZF)가 1로 세트된다. 다르다면 제로 플래그(ZF)는 0으로 세트된다.

ex. CMP reg, reg

 

NOP(No Operation)

아무 일도 하지 않는 명령어이다.

 

ex. NOP



출처 : http://dakuo.tistory.com/entry/기초-어셈블리어


레지스터메인 메모리나 시스템 메모리 외에 CPU의 내부에 있는 특별한 메모리 이다.

레지스터는 CPU가 접근할 수 있는 메모리 중에서 

가장 빠르게 동작하는 메모리로 CPU가 여러가지 연산등을 처리하는 동안 

필요한 임시적인 데이터를 보관하는데 사용된다.


메인 메모리는 바이트 단위로 매겨진 번지 혹은 주소를 이용해서 접근할 위치를 구분하는데, 

레지스터의 경우 번지의 개념이 없고 모두 고유한 이름이 부여되어 있다.


여러 레지스터 중에서 운영체제가 특별한 용도로 사용하는 레지스터를 제외하면 일반적인 응용프로그램이 

직접 이용할 수 있는 레지스터는 불과 10개 정도에 지나지 않는다.


⚠ 어셈블리 언어로 프로그래밍을 하려면 C/C++과 같은 고급언어와 달리 CPU 내부에 있는 

    특별한 메모리에 대해서 잘 알고 있어야 한다.



Ⅰ. 범용 레지스터


EAX      EBX      ECX      EDX 

 

이 레지스터는 말그대로 범용적인 목적으로 사용되는 레지스터로 크기는 각각 32 비트이고 다른 레지스터에 

비해서 비교적 다양한 역할을 한다. 이름은 단순히 A B C D 라는 이름을 가진 레지스터이며 A B C D 는 

Accumulator, Base, Counter, Data 라는 단어의 첫 글자이기도 하다.       

 

16비트 시절에는 AX, BX, CX, DX 라는 이름을 가지고 있다가, 

386 CPU부터 레지스터의 크기가 32비트로 확장되면서 모두 이름앞에 E(Extended) 가 붙여진 것이다. 

 

16비트 시절의 AX, BX, CX, DX 는 모두 16비트 크기였지만 8비트 크기로 나누어서 접근이 가능했다. 

그래서 AX의 경우는 상위 8비트가 AH, 하위 8비트가 AL 이라는 이름을 가진다(H는 high, L은 Low라는 뜻). 

BX, CX, DX 의 경우도 마찬가지로 BH, BL, CH, CL, DH, DL 이라는 이름으로 8비트 단위의 접근도 가능하다. 


범용 레지스터는 32비트의 공간을 이름에 따라 다양한 크기로 접근할 수 있는데 

이는 범용 레지스터의 독특한 특징이기도 하다.




Ⅱ. 포인터 레지스터


이 레지스터는 메인메모리의 번지값을 저장해서 포인터의 역할을 하는 레지스터로 

16비트 시절에는 SI, DI, SP, BP, IP 라고 불리던 레지스트이다. 

범용 레지스터 마찬가지로 32비트로 확장 되면서 앞에 E가 붙게 된 것이다. 

각각의 이름은 Source Index, Destination Index, Stack Pointer, Base Pointer, Instruction Pointer 에서 유래한다. 

 

⚀ EIP :  현재 실행되고 있는 프로그램의 실행코드가 저장된 메모리의 주소를 가리키는 레지스터


프로그램의 실행이 진행됨에 따라 자동으로 증가하고 프로그램의 실행 순서가 변경되는 

제어문이 실행될 때 자동으로 변경된다. 그래서 직접 접근해서 값을 저장하거나 읽거나 하는 일이 

없기 때문에 응용 프로그램에서는 손 댈 일이 없는 레지스터이다. 

 

⚁ ESI, EDI : 주로 메모리의 한 영역(Source)에서 다른 영역 (Destination)으로 데이터를 연속적으로 

   복사해서 옮길 때 사용하는 레지스터. 


이렇게 메모리의 번지를 저장하는 포인터의 역할 외에도 단순히 32비트 데이터를 저장하는 데도 사용할 수 있다. 

 

⚂ ESP, EBP : STACK 으로 동작하는 특별한 메모리 영역을 가리키는 데 사용되는 포인터 레지스터 


ESP 의 경우는 절대적으로 위에서 말한 용도로만 사용해야 한다. 


🁇 ESP 는 스택공간의 꼭대기를 가리키는 포인터 레지스터이다. 


ESP의 값은 직접 변경할 수도 있지만 보통은 스택에 데이터를 넣고 빼는 PUSH, POP 명령어의 

실행시 자동으로 변경된다.


 

🁈 EBP 는 ESP 에 보조적으로 사용되는 포인터 레지스터이다. 


스택 을 가리키는 데는 ESP 하나로도 충분하지만 ESP 대신 EBP를 보조적으로 사용하는 이유는 

프로그램의 오류로 중요한 스택 공간이 망가지는 일을 어느정도 방지할 수 있기 때문이다.

EBP를 이런 용도로 사용하지 않을 경우에는 단순히 데이터를 저장하는 용도로도 사용할 수 있다.



Ⅲ. 플래그 레지스터


EFLAGS 

 

이 레지스터는 16비트 시절 FLAGS 였고, 32비트로 확장 되면서 앞에 E가 붙은 것이다. 

이 레지스터는 이름이 암시하듯이 비트 단위의 플래그들을 저장하는 레지스터로 아주 특별한 용도로 사용된다. 

이 레지스터는 비트 단위로 의미를 가지는데 32 비트 모두가 사용되는 것은 아니고, 일부만 의미를 가진다. 

각각의 플래그 마다 SF (Sign Flag), ZF(Zero Flag), CF(Carry Flag), DF(Direction Flag) 등의 이름이 붙어 있다. 

 

보통 이 레지스터의 값을 직접 읽거나 쓰는 일은 거의 필요하지 않다. 각종 연산을 수행한 결과가 

어떠한 지를 기록해 놓는 용도나 특정 명령어의 동작을 조절하는 용도로 활용된다. 

연산 결과가 0인지 음수 인지 자리올림이 발생했는지 따라 각각 ZF, SF, CF등이 설정된다.

이렇게 설정된 플래그들은 조건 제어문들이 자동으로 참조해서

자신의 동작을 선택하기 때문에 우리가 직접 그 값을 챙길 필요는 없다. 

 

DF의 경우는 연속적으로 메모리 복사를 하는 명령어가 동작할 때 번지가 증가하면서 동작할지 감소하면서 

동작할 지를 지정해주는 용도로 사용된다. 이 때에도 이 플래그의 값을 켜고 끄는 명령어가 별도로 있기 

때문에 직접 EFLAGS 의 값을 손 댈 일은 없다. 

 

일반적인 응용 프로그램에서 DF의 경우를 제외하면 나머지 언급하지 않은 플래그들은 

물론 이거니와 ZF, SF, CF등도 관련되는 명령어들이 자동적으로 참조하기 때문에 

이들 플래그의 값을 직접 신경쓰지 않아도 무방하고 그냥 없는 듯이 여기면 된다. 



Ⅳ. 세그먼트 레지스터


CS    SS    DS    ES    (FS    GS) 

 

이들 레지스터는 모두 16비트 크기로 메모리의 특정 영역을 가리키는 용도로 사용되는 레지스터이다. 


인텔의 CPU는 메모리의 영역을 용도에 따라 세그먼트로 나누어서 사용한다. 

과거 16비트 프로그래밍 시절에는 한 세그먼트가 가리키는 메모리 영역이 64KB에 불과 했기
때문에 중요한 의미를 가졌지만 
요즘의 32비트 프로그래밍 시절에는 32비트 주소만 해도 4GB라는 

방대한 메모리 영역을 나타낼 수 있다. 그래서 운영체제가 한번 설정을 해주고 나면 응용 프로그램에서는 

신경 쓸일이 거의 없는 레지스터들이다. 일반적인 프로그램에서는 없듯이 생각해도 무방하다는 것이다. 

 

각각의 레지스터의 이름은 Code Segment, Stack Segment, Data Segment, Extra Segment이고  

코드 영역, 스택 영역, 데이터 영역 을 가리키는 용도로 사용된다.


굳이 세그먼트 레지스터에 대해서는 알 필요가 없다.




 꼭 알아야 하는 레지스터


범용 레지스터 : EAX, EBX, ECX, EDX 


포인터 레지스터 : ESI, EDI, ESP, EBP 




자료 참고 : http://blog.naver.com/kaswan/150938003

부산게임아카데미 김성완 교수님 블로그

어셈블리 언어란 기계어와 일대일로 대응이 되는 컴퓨터 프로그래밍의 저급 언어입니다.

이 글에선 어셈블리 명령어를 이해하기 전에 필요한 기초 지식을 정리해 보았습니다.



Ⅰ. 16 진법


16 진법의 표기에는 여러가지 방법이 있지만 대부분 아래와 같은 표기법을 사용합니다.


1. 0x 접두사와 함께 표기하는 방법 - Ex > 0x12ab

2. h 접미사와 함께 표기하는 방법 - Ex > 12abh


16진수에서는 0부터 f까지의 숫자와 문자를 사용하는데 이는 10진수의 0부터 15까지의 수를 의미합니다.

( 0 ~ 9 까지는 같으나 10 = a, 11 = b, 12 = c, 13 = d, 14 = e, 15 = f 이다. )


 16진수를 10진수로 변환하기


16진수를 10진수로 변환하기 위해서는 오른쪽 부터 각각의 수를 16의 0제곱, 16의 1제곱... 만큼 곱해야 합니다.

다음의 과정은 0x12ab (12abh) 를 10진수로 변환하는 과정입니다.


( b*16의 0제곱 ) + ( a* 16의 1제곱 ) + ( 2* 16의 2제곱 ) + (1* 16의 3제곱 )

= (11*1) + (10*16) + (2*256) + (1*4096)

= 11 + 160 + 512 + 4096

= 4779


따라서 0x12ab (12abh) 는 4779 입니다. 

16 진수가 나올때 마다 이 과정을 반복해야 하지는 않지만 00 ~ FF ( 0 ~ 255) 숫자에 익숙해 져야 합니다.

⚠ 반드시 오른쪽 부터 하여야 합니다! (저처럼 왼쪽부터 했다가 수정하는 일이 없기를...)


Ⅱ. 2 진법


2 진법은 숫자 0과 1을 이용한 수 표기법 입니다.

10 진수로 바꾸는 방법은 16 진수의 경우와 유사하지만 16의 제곱이 아닌 2의 제곱을 곱한 값들을 더해주어야 합니다.


⚀ 2진수를 10진수로 변환하기


1010 이라는 2진수를 10진수로 변환하여 보겠습니다.


( 0*2의 0제곱 ) + ( 1*2의 1제곱) + ( 0*2의 2제곱 ) + ( 1*2의 3제곱)

= 0 + 2 + 0 + 8

= 10


따라서 1010(2)의 10진수는 10 입니다.



⚁ 16진수에서 2진수로, 2진수에서 16진수로

  • 0x0 = 0000
  • 0x1 = 0001
  • 0x2 = 0010
  • 0x3 = 0011
  • 0x4 = 0100
  • 0x5 = 0101
  • 0x6 = 0110
  • 0x7 = 0111
  • 0x8 = 1000
  • 0x9 = 1001
  • 0xa = 1010
  • 0xb = 1011
  • 0xc = 1100
  • 0xd = 1101
  • 0xe = 1110
  • 0xf = 1111

 

예를 들어, 이진수 100101101001110를 16진수로 바꾸려면:

  1. 수의 길이가 4의 배수가 되도록 이진수 앞에 0을 추가합니다: 0100101101001110
  2. 수를 4자리만큼씩 나눕니다: 0100 1011 0100 1110
  3. 각각의 4자리 이진수를 16진수로 바꿉니다: 0x4 0xb 0x4 0xe
  4. 바꾼 수를 하나로 모아서 씁니다: 0x4b4e

 

반대로 16진수 0x469e를 2진수로 바꾸려면:

 

  1. 수를 한자리씩 나눕니다: 0x4 0x6 0x9 0xe
  2. 각각의 16진수를 2진수로 바꿔줍니다: 0100 0110 1001 1110
  3. 맨 앞의 0을 빼고 수를 하나로 모아서 씁니다: 100011010011110


Ⅲ. 자료형

자료형이란 16진수의 숫자가 어떻게 구분되고 나뉘어 지는지와 관련이 있습니다. 

보통 자료형은 비트(혹은 바이트) 수와 음수 표현이 가능 여부에 따라 분류할 수 있습니다.


비트(혹은 바이트) 수는 수의 길이를 정의합니다.

예를 들어 8비트(1바이트) 수는  0x03, 0x34, 0xFF와 같이두 개의 16진수로 구성됩니다.

이와 달리 16비트(2바이트) 수는 0x1234, 0x0001, and 0xFFFF 와 같이 표현 될 수 있습니다.


부호수(signed)와 무부호수(unsigned)는 음수 표현의 가능 여부에 의해 구분됩니다. 

음수 표현이 가능한 자료형은 그렇지 않은 경우에서 표현할 수 있는 최대값의 반 값까지만 표현할 수 있습니다.

음수와 양수를 표현하기 위해 수의 가장 앞자리 수를 이용합니다.

2진수의 경우 가장 앞자리가 1일때, 16진수의 경우에는 가장 앞자리 수가 8부터 F 사이의 수일때 그 수는 음수가 됩니다.



.

.

.

이 외에도 메모리, 포인터, 배열, 아스키 코드, 문자열 에 대해 알고 있다면 어셈블리어를 이해하는데 많은 도움이 될것입니다. 




자료 참고 : http://carpedm20.blogspot.kr/2012/08/this-page-is-going-to-be-about.html