[Assembly] 어셈블리어 기초 사용법 & 예제 총정리
- Language/Assembly
- 2021. 1. 26.
어셈블리어란?
어셈블리어(assembly language)는 기계어와 일대일 대응이 되는 컴퓨터 프로그래밍의 저급 언어입니다. 전류가 흐른다 그렇지 않다로 구성되는 0과 1의 이진수로 프로그램을 하는 기계어는 인간의 관점에서는 컴퓨터가 바로 읽을 수 있다는 점만 빼면 장점이 없는 언어이기 때문에 이를 보완하기 위해 나온 언어가 어셈블리어입니다. 기계어와 명령어가 1:1로 대응되는 단어들로 구성되어 있습니다. 고급언어는 컴파일하는 시간이 오래 걸리는 단점이 있는 반면 저급 언어는 컴퓨터와 가까운 언어이기 때문에 컴파일을 해도 간단한 명령으로 실행돼서 실행 속도가 굉장히 빠릅니다. 하지만 저급 언어는 배우기가 어렵고 유지보수가 힘들다는 이유로 특수한 경우를 제외하고는 사용되지 않고 있습니다. 하지만 임베디드 시스템이나 커널 프로그래밍, 컴퓨터 보안 등은 어셈블리어를 알아야 하고 디버깅이 불가능한 라이브 서비스를 진행하고 있는 환경에서 어느정도 디버깅을 할 수 있게 해주며 특히 어셈블리에 대한 전반적인 지식을 배움으로써 컴퓨터의 구조를 더 자세히 알 수 있어 어셈블리어로 코딩을 할 정도는 아니어도 구글링을 하며 해석 정도는 할 수 있어야 한다고 생각합니다.
Intel 문법과 AT&T 문법
어셈블리어에는 Intel문법과 AT&T 문법이 존재하며 또한 이들은 서로 호환되지 않습니다. 서로의 문법에 대해 차이점이 존재합니다.
명령어 수행 방식
만약 위와 같은 명령어가 있다고 가정할 때 Intel Operand의 경우 Operand2가 Source고 Operand1이 Destination이 되며 EBX의 값을 EAX에 더한다라는 뜻이 되고 AT&T의 경우에는 반대가 되어 EAX의 값을 EBX로 더한다라는 뜻이 됩니다.
Opcode : 명령어
Operand : 피 연산자
숫자 표기 방식
Intel (숫자를 그대로 사용)
ex) 1, 2, 3, 4, 5
AT&T (앞에 $를 붙여서 사용)
ex) $1, $2, $3, $4, $5
레지스터 표기 방식
Intel (명칭 그대로 사용)
ex) EAX, EBX, EBP
AT&T (앞에 %를 붙임)
ex) %EAX, %EBX, %EBP
이렇듯 두 가지 문법이 서로 차이점이 있습니다. 이 두 가지 문법 중 저희와 같은 개발자들이 많이 사용하는 것은 단연 Intel문법입니다. 이 포스팅 또한 Intel문법의 사용법을 기준으로 작성되었습니다.
어셈블리에서 사용되는 레지스터 종류
레지스터란?
레지스터란 CPU의 요청을 처리하는 데이터의 임시공간입니다. 레지스터는 공간이 작고 가격은 비싸지만 CPU에 직접 연결되어 있어서 연산 속도가 매우 빠릅니다. (RAM 보다도 훨씬 더) CPU는 자체적으로 데이터를 저장할 수 없으므로 레지스터를 이용하여 연산처리와 번지지정을 도와줍니다. 32비트의 경우에는 레지스터의 처음이 E로 시작하고 64비트는 R로 시작합니다.
범용 레지스터
EAX : 사칙연산 등 산술 연산에 자동으로 사용되며, 함수의 반환 값을 처리할 때도 사용됩니다.
EBX : 간접 번지 지정에 사용됩니다. 산수, 변수를 저장합니다.
ECX : 반복(Loop)에서 반복 Count 역할을 수행합니다.
EDX : EAX를 보조하는 역할을 합니다. 예를 들어 나누기를 진행할 경우 몫은 EAX에 나머지는 EDX에 저장됩니다.
인덱스 레지스터
ESI : 복사나 비교를 할 경우 출발지 주소를 저장하는 레지스터입니다.
EDI : 복사나 비교를 할 경우 목적지 주소를 저장하는 레지스터입니다.
포인터 레지스터
EIP : 다음에 실행할 명령어의 주소를 가지고 있는 레지스터입니다. 현재 실행하고 있는 명령어가 종료되면 이 레지스터에 있는 명령어를 실행하게 됩니다.
ESP : Stack Pointer의 가장 최근에 저장된 공간의 주소를 저장하는 레지스터입니다.
EBP : Stack Pointer의 기준점(바닥 부분)을 저장하는 레지스터입니다.
어셈블리어 자주 사용하는 명령어
명령어 | 예제 | 설명 | 분류 |
push | push eax | eax의 값을 스택에 저장 | 스택 조작 |
pop | pop eax | 스택 가장 상위에 있는 값을 꺼내서 eax에 저장 | 스택 조작 |
mov | mov eax, ebx | 메모리나 레지스터의 값을 옮길때 사용 | 데이터 이동 |
inc | lnc eax | eax의 값을 1증가시킨다 (++) | 데이터 조작 |
dec | dec eax | eax의 값을 1감소시킨다 (--) | 데이터 조작 |
add | add eax, ebx | 레지스터나 메모리의 값을 덧셈할때 쓰인다. | 논리, 연산 |
sub | sub eax, ebx | 레지스터나 메모리의 값을 뺄셈할때 쓰인다. | 논리, 연산 |
call | call proc | 프로시저를 호출한다. | 프로시저 |
ret | ret | 호출했던 바로 다음 지점으로 이동 | 프로시저 |
cmp | cmp eax, ebx | 레지스터와 레지스터의 값을 비교 | 비교 |
jmp | jmp proc | 특정한 곳으로 분기 | 분기 |
int | int $0x80 | OS에 할당된 인터럽트 영역을 system call | 인터럽트 |
nop | nop | 아무 동작도 하지 않는다. (No Operation) |
주로 사용되는 건 위의 명령어들이며 이 정도만 알아두어도 어셈블리어를 해석하는데 큰 문제가 없습니다. 만약 좀 더 다양한 명령어를 보고 싶다면 아래 글을 참고해주시기 바랍니다.
어셈블리어 예제
#include<stdio.h>
int main() {
int a = 1;
int b = 2;
int c = a + b;
printf("%d", c);
}
예를 들어 위의 코드를 어셈블리어로 바꾸면 어떻게 될까요?
push ebp
mov ebp,esp
sub esp,0E4h
push ebx
push esi
push edi
lea edi,[ebp+FFFFFF1Ch]
mov ecx,39h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov ecx,9AC003h
call 009A1316
mov dword ptr [ebp-8],1
mov dword ptr [ebp-14h],2
mov eax,dword ptr [ebp-8]
add eax,dword ptr [ebp-14h]
mov dword ptr [ebp-20h],eax
mov eax,dword ptr [ebp-20h]
push eax
push 9A7D08h
call 009A10CD
add esp,8
xor eax,eax
pop edi
pop esi
pop ebx
add esp,0E4h
cmp ebp,esp
call 009A123F
mov esp,ebp
pop ebp
ret
어셈블리어로 코드를 변환하면 이렇게 됩니다. 그럼 차근차근 해석해볼까요? 우선 특정 함수에 접근하려면 사용할 스택의 공간을 만들어야 합니다. ebp를 push한뒤 esp의 값을 ebp에 넣어주어 기존에 사용하던 esp의 값(스택의 꼭대기)을 스택의 가장 밑 부분으로 만들었습니다. 그런 뒤 esp(스택의 꼭대기)에 0E4h(16진수)의 크기를 할당하여 ebp ~ esp까지 공간을 할당하여 사용할 스택의 크기를 만들었습니다.
그런 뒤 dword ptr [ebp-8]에 1이라는 값을 넣고 dword ptr[ebp-14h]에 2라는 값을 넣었습니다. 이것이 위의 코드에서 a의 변수에 1을 넣고 b라는 변수에 2를 넣은 것입니다. 이렇듯 어셈블리어에서는 변수의 이름 같은 것이 없습니다. 단지 메모리 주소만 있을 뿐입니다.
그런 다음 eax에 다시 [ebp-8]에 있는 값을 넣고 eax와 [ebp-14]의 값을 더했습니다. 이 부분이 바로 a+b입니다. 그런 뒤 mov 함수를 통해 [ebp-20h] 이 부분이 변수 c가 되겠습니다. 여기에 방금 add한 값을 넣어줍니다. 그런 뒤 call 009A10CD을 통해 print함수를 출력합니다. 그리고 마지막으로 사용이 끝난 레지스터들을 pop해줍니다.
간단한 C언어 코드인데도 어셈블리어로 번역하면 이렇게 됩니다. 명령어 위주로 되어있어 명령어만 안다면 해석이 어렵지는 않으나 전체적으로 해석해야 할 문장이 너무나도 길죠. 간단한 문장이니 이 정도로 끝났지 긴 프로그래밍이면 전체 번역은 불가능하다고 봐야 합니다. 그래서 결국 부분 부분 끊어서 봐야 합니다. 요즘에는 어셈블리어에 가독성이 좋게끔 고급 프로그래밍 언어를 위에 추가해준다거나 변수 이름을 보이게 하여서 가독성이 좋게 해주는 방법이 많이 나오고 있습니다. 이를 활용하는 것도 좋은 방법이 될 수 있겠습니다.
'Language > Assembly' 카테고리의 다른 글
[Assembly] 어셈블리어 명령어 총정리 (1) | 2021.01.26 |
---|---|
[Assembly] 어셈블리어란 무엇인가? (1) | 2019.02.01 |