Assembly 기초 이론 및 명령어
⭐⭐⭐ (핵심 섹션)
⭐⭐ (실용적인 내용)
⭐ (기초 이론)
⭐⭐ (실용적인 내용)
⭐ (기초 이론)
1.어셈블리어 개요⭐
1.1 어셈블리어란?
어셈블리어는 기계어와 일대일 대응되는 저수준 프로그래밍 언어다. CPU가 직접 이해할 수 있는 기계어를 사람이 읽기 쉬운 형태로 표현한 것으로, 하드웨어에 가장 가까운 프로그래밍 언어.
1.2 프로그래밍 언어 계층 구조
고급 언어 (C/C++, Python, Java)
↓ 컴파일러
어셈블리어 (MOV, ADD, JMP 등)
↓ 어셈블러
기계어 (0101101...)
↓ CPU 실행
1.3 어셈블리어 학습이 필요한 이유
- 시스템 프로그래밍: 운영체제, 드라이버 개발
- 임베디드 시스템: 마이크로컨트롤러 프로그래밍
- 성능 최적화: 병목 지점의 수동 최적화
- 리버스 엔지니어링: 바이너리 분석, 보안 연구
- 디버깅: 깊은 수준의 문제 해결
2. 컴퓨터 구조 기초 ⭐⭐⭐
2.1 CPU 구조와 동작 원리
┌─────────────┐ ┌──────────────┐
│ CPU │◄──►│ Memory │
│ ┌─────────┐ │ │ │
│ │ ALU │ │ │ Instructions │
│ │Register │ │ │ Data │
│ │ Control │ │ │ │
│ └─────────┘ │ └──────────────┘
└─────────────┘
▲
│
┌─────▼─────┐
│ I/O │
└───────────┘
Von neumann architecture
Fetch-Decode-Execute 사이클
- Fetch: 메모리에서 명령어를 가져옴
- Decode: 명령어를 해석
- Execute: 명령어를 실행
- Write-back: 결과를 저장
2.2 레지스터의 종류와 역할 (Intel x86-64 기준)
64비트: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP
32비트: EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP
16비트: AX, BX, CX, DX, SI, DI, BP, SP
8비트: AL, BL, CL, DL, SIL, DIL, BPL, SPL
AH, BH, CH, DH
범용 레지스터 (General Purpose Registers)
각 레지스터의 관습적 역할:
- RAX (Accumulator): 산술 연산, 함수 반환값
- RBX (Base): 베이스 포인터, 데이터 참조
- RCX (Counter): 루프 카운터, 문자열 연산
- RDX (Data): 산술 연산 확장, I/O 포트 주소
- RSI (Source Index): 문자열 연산 소스
- RDI (Destination Index): 문자열 연산 대상
- RBP (Base Pointer): 스택 프레임 베이스
- RSP (Stack Pointer): 스택 최상단 주소
해당 레지스터는 전통적으로 저렇게 사용되었다는 역할일뿐 레지스터 사용의 결정 주체는 주로 컴파일러이며 최적화 또는 함수 호출 규약등의 이유로 자유롭게 변경될 수 있음, C/C++ 같은 고수준 언어 내에서 __asm
또는 asm
키워드를 사용하여 어셈블리 코드를 직접 삽입하는 경우에도 특정 레지스터 사용을 지정할 수 있음.
특수 목적 레지스터
- RIP (Instruction Pointer): 다음 실행할 명령어 주소
- RFLAGS: 상태 플래그들을 저장
확장 레지스터 (x86-64)
- R8~R15: 추가 범용 레지스터
- XMM0~XMM15: SSE 연산용 128비트 레지스터
- YMM0~YMM15: AVX 연산용 256비트 레지스터
2.3 메모리 구조와 주소 지정
높은 주소 (64비트 = 0xFFFFFFFFFFFFFFFF)
┌─────────────┐ 0xFFFFFFFF80000000
│ 커널 공간 │ ← 운영체제 전용 (사용자 접근 불가)
│ │ - 커널 코드/데이터
│ │ - 디바이스 드라이버
│ │ - 시스템 호출 처리
├─────────────┤ 0x00007FFFFFFFFFFF (47비트 주소 공간)
│ 스택 │ ← RSP 레지스터가 가리킴
│ ↓ │ - 함수 호출 시 매개변수/지역변수
│ │ - 함수 반환 주소
│ │ - 아래 방향으로 성장 (주소 감소)
├─────────────┤ 약 0x00007FFFFFFFE000 (스택 시작)
│ ... │
│ 미사용 │ ← 가상 주소 공간의 여유분
│ 영역 │ - 메모리 매핑 파일
│ │ - 공유 라이브러리 (.so)
├─────────────┤
│ ↑ │
│ 힙 │ ← malloc(), new 등의 동적 할당
│ │ - 프로그램 실행 중 크기 변화
│ │ - 위 방향으로 성장 (주소 증가)
├─────────────┤ 약 0x0000000000602000 (힙 시작)
│ BSS 영역 │ ← 초기화되지 않은 전역/정적 변수
│ │ - 프로그램 시작 시 0으로 초기화
│ │ - 실행 파일에는 크기 정보만 저장
├─────────────┤ 약 0x0000000000601000
│ 데이터 영역 │ ← 초기화된 전역/정적 변수
│ (.data) │ - 실행 파일에 초기값이 저장됨
│ │ - 읽기/쓰기 가능
├─────────────┤ 약 0x0000000000600000
│ 코드 영역 │ ← 실행 가능한 기계어 코드
│ (.text) │ - 읽기 전용 (보안상 중요!)
│ │ - 프로그램의 명령어들
└─────────────┘ 0x0000000000400000 (전통적인 시작 주소)
낮은 주소
가상 메모리 레이아웃 (Linux x86-64)
주소 지정 모드 (Addressing Modes)
; 즉시 주소 지정 (Immediate)
mov rax, 100 ; RAX = 100
; 레지스터 주소 지정 (Register)
mov rax, rbx ; RAX = RBX
; 직접 주소 지정 (Direct)
mov rax, [0x401000] ; RAX = memory[0x401000]
; 간접 주소 지정 (Indirect)
mov rax, [rbx] ; RAX = memory[RBX]
; 베이스 + 오프셋 (Base + Displacement)
mov rax, [rbx + 8] ; RAX = memory[RBX + 8]
; 인덱스 주소 지정 (Indexed)
mov rax, [rbx + rcx*4] ; RAX = memory[RBX + RCX*4]
; 복합 주소 지정 (Complex)
mov rax, [rbx + rcx*4 + 8] ; RAX = memory[RBX + RCX*4 + 8]
Virtual Memory 심화 이론
💡Basic Assembly의 범위를 넘어선 학습내용 정리. 가상 메모리(VM)레이아웃의 상세 특성 1. 커널 영역 (Kernel Space) * 주소 범위: 0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF * 특징: * 사용자 프로그램에서 직접 접근 불가 * 시스템 호출을 통해서만 간접 접근 * 모든 프로세스가 동일한 커널 공간을 공유 * 용도: 운영체제 코드, 디바이스 드라이버, 시스템 자료구조 2. 스택 (Stack) * 주소 범위:
3. 어셈블리 개발 환경⭐⭐
3.1 주요 어셈블러
- NASM (Netwide Assembler): Intel 문법, 크로스 플랫폼
- GAS (GNU Assembler): AT&T 문법, GCC 도구체인
- MASM (Microsoft Macro Assembler): Windows 전용
- YASM: NASM 호환 어셈블러
3.2 Intel vs AT&T 문법 비교
; Intel 문법 (NASM)
mov rax, rbx
mov rax, [rbx + 8]
add rax, 100
; AT&T 문법 (GAS)
movq %rbx, %rax
movq 8(%rbx), %rax
addq $100, %rax
3.3 개발 도구 체인
# NASM 기반 개발 과정
nasm -f elf64 program.asm -o program.o # 어셈블
ld program.o -o program # 링킹
./program # 실행
# 디버깅
gdb ./program # GDB 디버거
objdump -d program # 디스어셈블
4. 기본 명령어 체계 ⭐⭐⭐
4.1 명령어 형식과 구조
일반적인 명령어 형식
[접두사] 연산코드 목적지, 소스 [; 주석]
접두사 (Prefixes)
- REP/REPE/REPNE: 문자열 연산 반복
- LOCK: 메모리 잠금 (멀티프로세싱)
- 접근 크기: BYTE PTR, WORD PTR, DWORD PTR, QWORD PTR
4.2 데이터 이동 명령어
MOV 명령어 패밀리
; 기본 이동
mov rax, rbx ; RAX = RBX
mov rax, 0x1234 ; RAX = 0x1234
mov [rax], rbx ; memory[RAX] = RBX
; 크기별 이동
mov al, bl ; 8비트 이동
mov ax, bx ; 16비트 이동
mov eax, ebx ; 32비트 이동 (상위 32비트 자동 클리어)
mov rax, rbx ; 64비트 이동
; 부호 확장 이동
movsx rax, eax ; 32비트를 64비트로 부호 확장
movzx rax, ax ; 16비트를 64비트로 제로 확장
LEA (Load Effective Address) - 강력한 주소 계산
; 주소 계산 (실제 메모리 접근 없음)
lea rax, [rbx + rcx*4 + 8] ; RAX = RBX + RCX*4 + 8
; 복잡한 산술 연산을 한 번에 처리
lea rax, [rax + rax*2] ; RAX = RAX * 3 (빠른 곱셈)
lea rax, [rax + rax*4] ; RAX = RAX * 5
스택 조작 명령어
push rax ; RSP -= 8, [RSP] = RAX
pop rbx ; RBX = [RSP], RSP += 8
pushfq ; 플래그 레지스터를 스택에 저장
popfq ; 스택에서 플래그 레지스터 복원
4.3 산술 연산 명령어
기본 산술 연산
; 덧셈
add rax, rbx ; RAX += RBX
add rax, 10 ; RAX += 10
adc rax, rbx ; RAX += RBX + carry (다정밀도 연산)
; 뺄셈
sub rax, rbx ; RAX -= RBX
sub rax, 10 ; RAX -= 10
sbb rax, rbx ; RAX -= RBX - borrow (다정밀도 연산)
; 증가/감소
inc rax ; RAX++
dec rax ; RAX--
곱셈 연산
; 부호 없는 곱셈
mul rbx ; RDX:RAX = RAX * RBX (128비트 결과)
; 부호 있는 곱셈
imul rax, rbx ; RAX *= RBX
imul rax, rbx, 10 ; RAX = RBX * 10
imul rax, [mem], 5 ; RAX = memory[mem] * 5
나눗셈 연산
; 나눗셈 전 준비 (중요!)
xor rdx, rdx ; RDX = 0 (상위 64비트 클리어)
; 또는
cqo ; RAX를 RDX:RAX로 부호 확장
; 부호 없는 나눗셈
div rbx ; RAX = RDX:RAX / RBX, RDX = 나머지
; 부호 있는 나눗셈
idiv rbx ; RAX = RDX:RAX / RBX, RDX = 나머지
4.4 논리 연산 명령어
비트 연산
; 논리곱
and rax, rbx ; RAX &= RBX
and rax, 0xFF ; 하위 8비트만 유지
; 논리합
or rax, rbx ; RAX |= RBX
or rax, 0x80 ; 특정 비트 설정
; 배타적 논리합
xor rax, rbx ; RAX ^= RBX
xor rax, rax ; RAX = 0 (자기 자신과의 XOR)
; 논리 부정
not rax ; RAX = ~RAX (모든 비트 반전)
비트 테스트
test rax, rbx ; RAX & RBX (결과는 버리고 플래그만 설정)
test rax, rax ; RAX가 0인지 확인 (자주 사용되는 패턴)
4.5 시프트 연산 명령어
논리 시프트
shl rax, 1 ; RAX <<= 1 (왼쪽으로 1비트, *2 효과)
shl rax, cl ; CL 레지스터 값만큼 시프트
shr rax, 1 ; RAX >>= 1 (오른쪽으로 1비트, /2 효과)
산술 시프트 (부호 고려)
sal rax, 1 ; 산술 왼쪽 시프트 (SHL과 동일)
sar rax, 1 ; 산술 오른쪽 시프트 (부호 비트 유지)
순환 시프트
rol rax, 1 ; 왼쪽 순환 시프트
ror rax, 1 ; 오른쪽 순환 시프트
rcl rax, 1 ; 캐리를 포함한 왼쪽 순환 시프트
rcr rax, 1 ; 캐리를 포함한 오른쪽 순환 시프트
4.6 플래그 레지스터와 상태 관리
주요 플래그 비트
RFLAGS 레지스터 (64비트)
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│0│0│0│0│O│D│I│T│S│Z│0│A│0│P│1│C│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
F E D C B A 9 8 7 6 5 4 3 2 1 0
C (Carry): 캐리/보로우 발생
P (Parity): 결과의 1비트 개수가 짝수
A (Auxiliary): BCD 연산 보조 캐리
Z (Zero): 결과가 0
S (Sign): 결과가 음수
O (Overflow): 부호 있는 오버플로우
D (Direction): 문자열 연산 방향
I (Interrupt): 인터럽트 허용
플래그 조작
clc ; 캐리 플래그 클리어
stc ; 캐리 플래그 설정
cmc ; 캐리 플래그 반전
cld ; 방향 플래그 클리어 (문자열 연산 순방향)
std ; 방향 플래그 설정 (문자열 연산 역방향)
cli ; 인터럽트 비활성화
sti ; 인터럽트 활성화
5. 기본 프로그램 작성⭐⭐
5.1 Hello World 프로그램
section .data
msg db 'Hello, World!', 0xA ; 문자열 + 개행
msg_len equ $ - msg ; 문자열 길이
section .text
global _start
_start:
; sys_write 시스템 콜
mov rax, 1 ; sys_write 번호
mov rdi, 1 ; stdout
mov rsi, msg ; 메시지 주소
mov rdx, msg_len ; 메시지 길이
syscall ; 시스템 콜 실행
; sys_exit 시스템 콜
mov rax, 60 ; sys_exit 번호
mov rdi, 0 ; 종료 코드
syscall ; 시스템 콜 실행
Linux x86-64
5.2 간단한 계산 프로그램
section .data
num1 dq 15 ; 64비트 정수
num2 dq 25
result dq 0
section .text
global _start
_start:
; 덧셈 연산
mov rax, [num1] ; RAX = num1
add rax, [num2] ; RAX += num2
mov [result], rax ; result = RAX
; 곱셈 연산 예제
mov rax, [num1] ; RAX = num1
imul rax, [num2] ; RAX *= num2
; 종료
mov rax, 60
mov rdi, 0
syscall
5.3 입출력 처리 기초
section .bss
buffer resb 64 ; 64바이트 버퍼
section .data
prompt db 'Enter a number: ', 0
prompt_len equ $ - prompt
section .text
global _start
_start:
; 프롬프트 출력
mov rax, 1
mov rdi, 1
mov rsi, prompt
mov rdx, prompt_len
syscall
; 입력 받기
mov rax, 0 ; sys_read
mov rdi, 0 ; stdin
mov rsi, buffer ; 버퍼 주소
mov rdx, 64 ; 최대 읽을 바이트
syscall
; 입력받은 데이터 그대로 출력 (echo)
mov rdx, rax ; 읽은 바이트 수
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, buffer ; 버퍼 주소
syscall
; 종료
mov rax, 60
mov rdi, 0
syscall
핵심 포인트 요약
반드시 기억해야 할 핵심 개념
- 레지스터 활용: RAX, RBX, RCX, RDX의 관습적 역할과 64/32/16/8비트 접근
- 주소 지정 모드: 특히
[base + index*scale + displacement]
형태 - MOV vs LEA: LEA는 주소 계산만, MOV는 실제 데이터 이동
- 플래그의 영향: 연산 결과가 플래그에 미치는 영향 이해
- 시스템 콜: Linux에서 rax에 시스템 콜 번호, 매개변수는 rdi, rsi, rdx 순서
흔한 실수 방지
- 나눗셈 전 RDX 초기화:
xor rdx, rdx
또는cqo
필수 - 32비트 연산 시 상위 비트 자동 클리어:
mov eax, ebx
는 RAX의 상위 32비트를 0으로 만듦 - 스택 정렬: x86-64에서는 함수 호출 시 RSP가 16바이트 경계에 정렬되어야 함
- 주소 지정 시 크기 명시:
mov byte [rax], 10
vsmov qword [rax], 10