어셈블리 언어 기초
어셈블리 언어 기초¶
개요¶
어셈블리 언어는 기계어와 1:1로 대응되는 저수준 프로그래밍 언어입니다. 프로세서가 직접 이해하는 명령어를 사람이 읽을 수 있는 형태로 표현합니다. 이 레슨에서는 x86과 ARM 어셈블리의 기초, 주요 명령어 종류, 그리고 간단한 프로그램 작성 방법을 학습합니다.
난이도: ⭐⭐⭐
선수 지식: 명령어 집합 아키텍처(ISA), CPU 구조 기초
목차¶
1. 어셈블리 언어 개념¶
1.1 어셈블리 언어란?¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 프로그래밍 언어 계층 구조 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 고수준 언어 (High-Level) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ C, C++, Java, Python, JavaScript, ... │ │ │
│ │ │ │ │ │
│ │ │ int sum = a + b; │ │ │
│ │ │ │ │ │
│ │ │ 장점: 가독성, 이식성, 생산성 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 컴파일 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 어셈블리 언어 (Assembly) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ MOV EAX, [a] ; a를 EAX에 로드 │ │ │
│ │ │ ADD EAX, [b] ; b를 더함 │ │ │
│ │ │ MOV [sum], EAX ; 결과를 sum에 저장 │ │ │
│ │ │ │ │ │
│ │ │ 장점: 하드웨어 직접 제어, 최적화 가능 │ │ │
│ │ │ 단점: 이식성 없음, 생산성 낮음 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 어셈블 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 기계어 (Machine Code) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ A1 00 10 00 00 ; MOV EAX, [0x1000] │ │ │
│ │ │ 03 05 04 10 00 00 ; ADD EAX, [0x1004] │ │ │
│ │ │ A3 08 10 00 00 ; MOV [0x1008], EAX │ │ │
│ │ │ │ │ │
│ │ │ CPU가 직접 실행하는 이진 코드 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
1.2 어셈블리어의 기본 구조¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 어셈블리 명령어 형식 │
│ │
│ [레이블:] 명령어 [피연산자1 [, 피연산자2 [, 피연산자3]]] [; 주석] │
│ │
│ 예시: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ loop: ; 레이블 (점프 대상) │ │
│ │ mov eax, ebx ; eax = ebx │ │
│ │ add eax, 10 ; eax = eax + 10 │ │
│ │ cmp eax, 100 ; eax와 100 비교 │ │
│ │ jl loop ; eax < 100이면 loop로 │ │
│ │ │ │
│ │ end: │ │
│ │ ret ; 반환 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 구성 요소: │
│ ┌───────────┬─────────────────────────────────────────────────────┐ │
│ │ 레이블 │ 메모리 주소에 이름 부여 (점프/데이터 참조용) │ │
│ ├───────────┼─────────────────────────────────────────────────────┤ │
│ │ 명령어 │ CPU가 수행할 연산 (MOV, ADD, JMP 등) │ │
│ ├───────────┼─────────────────────────────────────────────────────┤ │
│ │ 피연산자 │ 연산 대상 (레지스터, 메모리, 즉시값) │ │
│ ├───────────┼─────────────────────────────────────────────────────┤ │
│ │ 주석 │ 코드 설명 (실행에 영향 없음) │ │
│ └───────────┴─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
1.3 어셈블러와 어셈블 과정¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 어셈블 과정 │
│ │
│ 소스 파일 (.asm/.s) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 어셈블러 │ NASM, GAS, MASM, ARMASM 등 │
│ │ (Assembler) │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ 오브젝트 파일 (.o/.obj) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 링커 │ ld, link.exe 등 │
│ │ (Linker) │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ 실행 파일 (.exe, ELF, Mach-O 등) │
│ │
│ 주요 어셈블러: │
│ ┌───────────────┬──────────────────────────────────────────────┐ │
│ │ NASM │ Netwide Assembler (x86, 크로스 플랫폼) │ │
│ │ GAS │ GNU Assembler (AT&T 문법) │ │
│ │ MASM │ Microsoft Macro Assembler (Windows) │ │
│ │ FASM │ Flat Assembler (x86) │ │
│ │ ARMASM/as │ ARM 어셈블러 │ │
│ └───────────────┴──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
1.4 어셈블리 문법 스타일¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Intel 문법 vs AT&T 문법 │
│ │
│ ┌──────────────────────────┬──────────────────────────────────────┐ │
│ │ Intel 문법 │ AT&T 문법 │ │
│ │ (NASM, MASM) │ (GAS, GCC) │ │
│ ├──────────────────────────┼──────────────────────────────────────┤ │
│ │ mov eax, 10 │ movl $10, %eax │ │
│ │ (목적지, 소스) │ (소스, 목적지) │ │
│ ├──────────────────────────┼──────────────────────────────────────┤ │
│ │ mov eax, ebx │ movl %ebx, %eax │ │
│ ├──────────────────────────┼──────────────────────────────────────┤ │
│ │ mov eax, [ebx] │ movl (%ebx), %eax │ │
│ ├──────────────────────────┼──────────────────────────────────────┤ │
│ │ mov eax, [ebx+ecx*4+10] │ movl 10(%ebx,%ecx,4), %eax │ │
│ ├──────────────────────────┼──────────────────────────────────────┤ │
│ │ 즉시값: 숫자 │ 즉시값: $접두사 │ │
│ │ 레지스터: 이름 │ 레지스터: %접두사 │ │
│ │ 크기: 명령어로 추론 │ 크기: 접미사 (b/w/l/q) │ │
│ └──────────────────────────┴──────────────────────────────────────┘ │
│ │
│ AT&T 크기 접미사: │
│ - b (byte): 8비트 │
│ - w (word): 16비트 │
│ - l (long): 32비트 │
│ - q (quad): 64비트 │
│ │
│ 이 문서에서는 Intel 문법을 주로 사용합니다. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2. x86 어셈블리 기초¶
2.1 x86 레지스터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86-64 레지스터 │
│ │
│ 범용 레지스터 (64비트): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 64비트 32비트 16비트 8비트(H/L) 용도 │ │
│ │ ──────────────────────────────────────────────────────────── │ │
│ │ RAX EAX AX AH/AL 누산기 │ │
│ │ RBX EBX BX BH/BL 베이스 │ │
│ │ RCX ECX CX CH/CL 카운터 │ │
│ │ RDX EDX DX DH/DL 데이터 │ │
│ │ RSI ESI SI SIL 소스 인덱스 │ │
│ │ RDI EDI DI DIL 목적지 인덱스 │ │
│ │ RBP EBP BP BPL 베이스 포인터 │ │
│ │ RSP ESP SP SPL 스택 포인터 │ │
│ │ R8 R8D R8W R8B 범용 (x64 추가) │ │
│ │ R9 R9D R9W R9B 범용 (x64 추가) │ │
│ │ R10-R15 R10D-R15D R10W-R15W R10B-R15B 범용 (x64 추가) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 레지스터 크기 관계: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 63 31 15 7 0 │ │
│ │ ├───────────────────────────────┼───────────────┼────┼─────┤ │ │
│ │ │ RAX │ │ │ │ │ │
│ │ │ │ EAX │ │ │ │ │
│ │ │ │ │ AX │ │ │ │
│ │ │ │ │ AH │ AL │ │ │
│ │ └───────────────────────────────┴───────────────┴────┴─────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 특수 레지스터: │
│ ┌───────────┬─────────────────────────────────────────────────────┐ │
│ │ RIP │ 명령어 포인터 (다음 명령어 주소) │ │
│ │ RFLAGS │ 상태/조건 플래그 │ │
│ └───────────┴─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 플래그 레지스터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ RFLAGS 레지스터 │
│ │
│ 주요 상태 플래그: │
│ ┌────────┬────────────────────────────────────────────────────────┐ │
│ │ 플래그 │ 설명 │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ CF │ Carry Flag: 부호 없는 연산에서 올림/내림 발생 │ │
│ │ │ 예: 255 + 1 = 0 (CF=1) │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ ZF │ Zero Flag: 결과가 0이면 1 │ │
│ │ │ 예: 5 - 5 = 0 (ZF=1) │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ SF │ Sign Flag: 결과의 최상위 비트 (부호) │ │
│ │ │ 예: -1 (SF=1), 1 (SF=0) │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ OF │ Overflow Flag: 부호 있는 연산에서 오버플로우 │ │
│ │ │ 예: 127 + 1 = -128 (OF=1) │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ PF │ Parity Flag: 결과의 하위 8비트에서 1의 개수가 짝수 │ │
│ ├────────┼────────────────────────────────────────────────────────┤ │
│ │ AF │ Auxiliary Carry: BCD 연산용 │ │
│ └────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 제어/시스템 플래그: │
│ ┌────────┬────────────────────────────────────────────────────────┐ │
│ │ DF │ Direction Flag: 문자열 연산 방향 (0=증가, 1=감소) │ │
│ │ IF │ Interrupt Flag: 인터럽트 허용 여부 │ │
│ │ TF │ Trap Flag: 단일 스텝 디버깅 │ │
│ └────────┴────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2.3 x86 기본 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 기본 명령어 │
│ │
│ 데이터 이동: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ mov dst, src ; dst = src │ │
│ │ mov eax, 10 ; eax = 10 (즉시값) │ │
│ │ mov eax, ebx ; eax = ebx (레지스터) │ │
│ │ mov eax, [ebx] ; eax = Memory[ebx] (메모리) │ │
│ │ mov [eax], ebx ; Memory[eax] = ebx │ │
│ │ │ │
│ │ movzx eax, bl ; 0 확장 (8비트 → 32비트) │ │
│ │ movsx eax, bl ; 부호 확장 │ │
│ │ │ │
│ │ lea eax, [ebx+ecx*4] ; 주소 계산 결과를 eax에 │ │
│ │ ; (메모리 접근 없음) │ │
│ │ │ │
│ │ xchg eax, ebx ; eax와 ebx 교환 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 스택 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ push eax ; 스택에 푸시 (ESP -= 4, [ESP] = eax) │ │
│ │ pop ebx ; 스택에서 팝 (ebx = [ESP], ESP += 4) │ │
│ │ │ │
│ │ 스택 구조 (x86): │ │
│ │ ┌──────────────┐ 높은 주소 │ │
│ │ │ ... │ │ │
│ │ ├──────────────┤ │ │
│ │ │ 이전 값 │ │ │
│ │ ├──────────────┤ │ │
│ │ │ PUSH된 값 │ ◄─── ESP (스택 포인터) │ │
│ │ ├──────────────┤ │ │
│ │ │ (빈 공간) │ │ │
│ │ └──────────────┘ 낮은 주소 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. ARM 어셈블리 기초¶
3.1 ARM 레지스터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 레지스터 (AArch64) │
│ │
│ 범용 레지스터: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 64비트 32비트 용도 │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ X0-X7 W0-W7 인자 전달 / 반환값 │ │
│ │ X8 W8 간접 결과 레지스터 │ │
│ │ X9-X15 W9-W15 임시 (Caller-saved) │ │
│ │ X16-X17 W16-W17 인트라-프로시저 호출 │ │
│ │ X18 W18 플랫폼 레지스터 │ │
│ │ X19-X28 W19-W28 Callee-saved │ │
│ │ X29 (FP) W29 프레임 포인터 │ │
│ │ X30 (LR) W30 링크 레지스터 (복귀 주소) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 특수 레지스터: │
│ ┌───────────┬─────────────────────────────────────────────────────┐ │
│ │ SP │ 스택 포인터 │ │
│ │ PC │ 프로그램 카운터 (직접 접근 제한) │ │
│ │ XZR/WZR │ 제로 레지스터 (읽으면 0, 쓰면 버림) │ │
│ │ NZCV │ 조건 플래그 (N, Z, C, V) │ │
│ └───────────┴─────────────────────────────────────────────────────┘ │
│ │
│ ARM32 (레거시) 레지스터: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ R0-R12 : 범용 레지스터 │ │
│ │ R13 (SP) : 스택 포인터 │ │
│ │ R14 (LR) : 링크 레지스터 │ │
│ │ R15 (PC) : 프로그램 카운터 │ │
│ │ CPSR : 상태 레지스터 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.2 ARM 조건 코드¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 조건 코드 │
│ │
│ ARM의 대부분의 명령어는 조건부 실행 가능: │
│ │
│ ┌────────┬────────────────┬──────────────────────────────────────┐ │
│ │ 접미사 │ 조건 │ 설명 │ │
│ ├────────┼────────────────┼──────────────────────────────────────┤ │
│ │ EQ │ Z == 1 │ Equal (같음) │ │
│ │ NE │ Z == 0 │ Not Equal (다름) │ │
│ │ CS/HS│ C == 1 │ Carry Set / Unsigned >= │ │
│ │ CC/LO│ C == 0 │ Carry Clear / Unsigned < │ │
│ │ MI │ N == 1 │ Minus (음수) │ │
│ │ PL │ N == 0 │ Plus (양수 또는 0) │ │
│ │ VS │ V == 1 │ Overflow │ │
│ │ VC │ V == 0 │ No Overflow │ │
│ │ HI │ C==1 & Z==0 │ Unsigned > │ │
│ │ LS │ C==0 | Z==1 │ Unsigned <= │ │
│ │ GE │ N == V │ Signed >= │ │
│ │ LT │ N != V │ Signed < │ │
│ │ GT │ N==V & Z==0 │ Signed > │ │
│ │ LE │ N!=V | Z==1 │ Signed <= │ │
│ │ AL │ (항상) │ Always (기본값) │ │
│ └────────┴────────────────┴──────────────────────────────────────┘ │
│ │
│ 예시 (ARM32): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ CMP R0, R1 ; R0 - R1, 플래그 설정 │ │
│ │ ADDEQ R2, R2, #1 ; R0 == R1이면 R2++ │ │
│ │ SUBNE R2, R2, #1 ; R0 != R1이면 R2-- │ │
│ │ │ │
│ │ ; if-else 없이 조건부 실행! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.3 ARM 기본 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 기본 명령어 (AArch64) │
│ │
│ 데이터 이동: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ MOV X0, X1 ; X0 = X1 │ │
│ │ MOV X0, #100 ; X0 = 100 (즉시값) │ │
│ │ MVN X0, X1 ; X0 = ~X1 (비트 반전) │ │
│ │ │ │
│ │ ; 큰 즉시값 로드 │ │
│ │ MOVZ X0, #0x1234 ; X0 = 0x1234 │ │
│ │ MOVK X0, #0x5678, LSL #16 ; X0 = 0x56781234 │ │
│ │ │ │
│ │ ; PC-relative 로드 │ │
│ │ ADR X0, label ; X0 = label의 주소 │ │
│ │ ADRP X0, label ; 페이지 단위 주소 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 메모리 접근: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; Load │ │
│ │ LDR X0, [X1] ; X0 = Memory[X1] │ │
│ │ LDR X0, [X1, #8] ; X0 = Memory[X1 + 8] │ │
│ │ LDR X0, [X1, X2] ; X0 = Memory[X1 + X2] │ │
│ │ LDR X0, [X1, X2, LSL #3] ; X0 = Memory[X1 + X2*8] │ │
│ │ │ │
│ │ ; Store │ │
│ │ STR X0, [X1] ; Memory[X1] = X0 │ │
│ │ STR X0, [X1, #8]! ; X1 += 8; Memory[X1] = X0 (pre-index) │ │
│ │ STR X0, [X1], #8 ; Memory[X1] = X0; X1 += 8 (post-idx) │ │
│ │ │ │
│ │ ; 크기 지정 │ │
│ │ LDRB W0, [X1] ; 바이트 로드 │ │
│ │ LDRH W0, [X1] ; 하프워드 (16비트) 로드 │ │
│ │ LDRSW X0, [X1] ; 부호 확장 워드 로드 │ │
│ │ │ │
│ │ ; 다중 레지스터 │ │
│ │ LDP X0, X1, [X2] ; 레지스터 쌍 로드 │ │
│ │ STP X0, X1, [X2] ; 레지스터 쌍 저장 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4. 산술/논리 명령어¶
4.1 x86 산술/논리 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 산술/논리 명령어 │
│ │
│ 산술 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 덧셈 │ │
│ │ add eax, ebx ; eax = eax + ebx │ │
│ │ add eax, 10 ; eax = eax + 10 │ │
│ │ adc eax, ebx ; eax = eax + ebx + CF (캐리 포함) │ │
│ │ inc eax ; eax++ (CF 변경 안 함) │ │
│ │ │ │
│ │ ; 뺄셈 │ │
│ │ sub eax, ebx ; eax = eax - ebx │ │
│ │ sbb eax, ebx ; eax = eax - ebx - CF (빌림 포함) │ │
│ │ dec eax ; eax-- (CF 변경 안 함) │ │
│ │ neg eax ; eax = -eax (2의 보수) │ │
│ │ │ │
│ │ ; 곱셈 │ │
│ │ mul ebx ; EDX:EAX = EAX * EBX (부호 없음) │ │
│ │ imul eax, ebx ; EAX = EAX * EBX (부호 있음) │ │
│ │ imul eax, ebx, 10 ; EAX = EBX * 10 │ │
│ │ │ │
│ │ ; 나눗셈 │ │
│ │ div ebx ; EAX = EDX:EAX / EBX, EDX = 나머지 │ │
│ │ idiv ebx ; 부호 있는 나눗셈 │ │
│ │ │ │
│ │ 주의: div/idiv 전에 EDX를 설정해야 함 │ │
│ │ cdq ; EAX를 EDX:EAX로 부호 확장 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 논리 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ and eax, ebx ; eax = eax & ebx │ │
│ │ or eax, ebx ; eax = eax | ebx │ │
│ │ xor eax, ebx ; eax = eax ^ ebx │ │
│ │ not eax ; eax = ~eax │ │
│ │ test eax, ebx ; eax & ebx (결과 저장 안 함, 플래그만) │ │
│ │ │ │
│ │ ; XOR을 이용한 레지스터 초기화 (최적화) │ │
│ │ xor eax, eax ; eax = 0 (mov eax, 0보다 효율적) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 시프트/회전: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ shl eax, 2 ; eax <<= 2 (왼쪽 시프트, *4) │ │
│ │ shr eax, 1 ; eax >>= 1 (오른쪽 시프트, 논리) │ │
│ │ sar eax, 1 ; eax >>= 1 (산술 시프트, 부호 유지) │ │
│ │ shl eax, cl ; CL 레지스터 값만큼 시프트 │ │
│ │ │ │
│ │ rol eax, 4 ; 왼쪽으로 4비트 회전 │ │
│ │ ror eax, 4 ; 오른쪽으로 4비트 회전 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.2 ARM 산술/논리 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 산술/논리 명령어 │
│ │
│ 산술 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 3-주소 형식: dst = src1 op src2 │ │
│ │ ADD X0, X1, X2 ; X0 = X1 + X2 │ │
│ │ ADD X0, X1, #100 ; X0 = X1 + 100 │ │
│ │ ADDS X0, X1, X2 ; X0 = X1 + X2, 플래그 갱신 │ │
│ │ ADC X0, X1, X2 ; X0 = X1 + X2 + Carry │ │
│ │ │ │
│ │ SUB X0, X1, X2 ; X0 = X1 - X2 │ │
│ │ SUBS X0, X1, X2 ; X0 = X1 - X2, 플래그 갱신 │ │
│ │ SBC X0, X1, X2 ; X0 = X1 - X2 - !Carry │ │
│ │ NEG X0, X1 ; X0 = -X1 (= SUB X0, XZR, X1) │ │
│ │ │ │
│ │ MUL X0, X1, X2 ; X0 = X1 * X2 │ │
│ │ MADD X0, X1, X2, X3 ; X0 = X3 + (X1 * X2) │ │
│ │ MSUB X0, X1, X2, X3 ; X0 = X3 - (X1 * X2) │ │
│ │ │ │
│ │ SDIV X0, X1, X2 ; X0 = X1 / X2 (부호 있음) │ │
│ │ UDIV X0, X1, X2 ; X0 = X1 / X2 (부호 없음) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 논리 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ AND X0, X1, X2 ; X0 = X1 & X2 │ │
│ │ ORR X0, X1, X2 ; X0 = X1 | X2 │ │
│ │ EOR X0, X1, X2 ; X0 = X1 ^ X2 │ │
│ │ BIC X0, X1, X2 ; X0 = X1 & ~X2 (비트 클리어) │ │
│ │ ORN X0, X1, X2 ; X0 = X1 | ~X2 │ │
│ │ TST X0, X1 ; X0 & X1, 플래그만 설정 │ │
│ │ │ │
│ │ ; 시프트 연산자 (두 번째 피연산자에 적용 가능) │ │
│ │ ADD X0, X1, X2, LSL #2 ; X0 = X1 + (X2 << 2) │ │
│ │ SUB X0, X1, X2, LSR #1 ; X0 = X1 - (X2 >> 1) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 시프트/회전: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ LSL X0, X1, #4 ; X0 = X1 << 4 (논리 왼쪽) │ │
│ │ LSR X0, X1, #4 ; X0 = X1 >> 4 (논리 오른쪽) │ │
│ │ ASR X0, X1, #4 ; X0 = X1 >> 4 (산술 오른쪽) │ │
│ │ ROR X0, X1, #4 ; 오른쪽 회전 │ │
│ │ │ │
│ │ LSL X0, X1, X2 ; X0 = X1 << X2 (레지스터 값만큼) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5. 분기 명령어¶
5.1 x86 분기 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 분기 명령어 │
│ │
│ 비교 명령어: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ cmp eax, ebx ; eax - ebx, 결과 버리고 플래그만 설정 │ │
│ │ test eax, ebx ; eax & ebx, 결과 버리고 플래그만 설정 │ │
│ │ │ │
│ │ 예시: │ │
│ │ cmp eax, 0 ; eax가 0인지 확인 │ │
│ │ test eax, eax ; eax가 0인지 확인 (더 효율적) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 조건 점프: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 부호 없는 비교 │ │
│ │ je label ; Jump if Equal (ZF=1) │ │
│ │ jne label ; Jump if Not Equal (ZF=0) │ │
│ │ ja label ; Jump if Above (CF=0, ZF=0) │ │
│ │ jae label ; Jump if Above or Equal (CF=0) │ │
│ │ jb label ; Jump if Below (CF=1) │ │
│ │ jbe label ; Jump if Below or Equal (CF=1 or ZF=1) │ │
│ │ │ │
│ │ ; 부호 있는 비교 │ │
│ │ jg label ; Jump if Greater (signed) │ │
│ │ jge label ; Jump if Greater or Equal │ │
│ │ jl label ; Jump if Less │ │
│ │ jle label ; Jump if Less or Equal │ │
│ │ │ │
│ │ ; 플래그 기반 │ │
│ │ jz label ; Jump if Zero (= je) │ │
│ │ jnz label ; Jump if Not Zero (= jne) │ │
│ │ js label ; Jump if Sign (SF=1, 음수) │ │
│ │ jns label ; Jump if Not Sign (SF=0) │ │
│ │ jo label ; Jump if Overflow │ │
│ │ jno label ; Jump if No Overflow │ │
│ │ jc label ; Jump if Carry (= jb) │ │
│ │ jnc label ; Jump if No Carry (= jae) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 무조건 점프 및 호출: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ jmp label ; 무조건 점프 │ │
│ │ jmp eax ; 레지스터에 저장된 주소로 점프 │ │
│ │ jmp [eax] ; 메모리에 저장된 주소로 점프 │ │
│ │ │ │
│ │ call function ; 함수 호출 (복귀 주소 push 후 점프) │ │
│ │ ret ; 반환 (스택에서 복귀 주소 pop) │ │
│ │ ret 8 ; 반환 + 스택 정리 (8바이트 pop) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5.2 ARM 분기 명령어¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 분기 명령어 │
│ │
│ 비교 명령어: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ CMP X0, X1 ; X0 - X1, 플래그만 설정 │ │
│ │ CMP X0, #100 ; X0 - 100 │ │
│ │ CMN X0, X1 ; X0 + X1, 플래그만 설정 (Compare Neg.) │ │
│ │ TST X0, X1 ; X0 & X1, 플래그만 │ │
│ │ TEQ X0, X1 ; X0 ^ X1, 플래그만 (같은지 테스트) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 조건 분기 (AArch64): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ B.EQ label ; Branch if Equal (Z=1) │ │
│ │ B.NE label ; Branch if Not Equal (Z=0) │ │
│ │ B.GT label ; Branch if Greater Than (signed) │ │
│ │ B.GE label ; Branch if Greater or Equal │ │
│ │ B.LT label ; Branch if Less Than │ │
│ │ B.LE label ; Branch if Less or Equal │ │
│ │ B.HI label ; Branch if Higher (unsigned >) │ │
│ │ B.HS label ; Branch if Higher or Same (unsigned >=) │ │
│ │ B.LO label ; Branch if Lower (unsigned <) │ │
│ │ B.LS label ; Branch if Lower or Same (unsigned <=) │ │
│ │ B.MI label ; Branch if Minus (N=1) │ │
│ │ B.PL label ; Branch if Plus (N=0) │ │
│ │ B.VS label ; Branch if Overflow Set │ │
│ │ B.VC label ; Branch if Overflow Clear │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 무조건 분기 및 호출: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ B label ; 무조건 분기 │ │
│ │ BR X0 ; 레지스터로 분기 │ │
│ │ │ │
│ │ BL function ; Branch with Link │ │
│ │ ; X30(LR) = 복귀주소, PC = function │ │
│ │ BLR X0 ; Branch with Link to Register │ │
│ │ │ │
│ │ RET ; Return (= BR X30) │ │
│ │ RET X1 ; Return (X1의 주소로) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 조건 선택 (분기 없이): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 조건부 선택 (분기 페널티 없음) │ │
│ │ CSEL X0, X1, X2, EQ ; X0 = (EQ) ? X1 : X2 │ │
│ │ CSINC X0, X1, X2, NE ; X0 = (NE) ? X1 : X2+1 │ │
│ │ CSINV X0, X1, X2, LT ; X0 = (LT) ? X1 : ~X2 │ │
│ │ CSNEG X0, X1, X2, GE ; X0 = (GE) ? X1 : -X2 │ │
│ │ │ │
│ │ ; 조건부 비교 │ │
│ │ CCMP X0, X1, #0, EQ ; if (EQ) then CMP X0,X1 else flags=0 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5.3 분기 예제¶
┌─────────────────────────────────────────────────────────────────────────┐
│ if-else 구현 비교 │
│ │
│ C 코드: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ if (a > b) { │ │
│ │ c = a; │ │
│ │ } else { │ │
│ │ c = b; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ x86 어셈블리: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ mov eax, [a] ; eax = a │ │
│ │ mov ebx, [b] ; ebx = b │ │
│ │ cmp eax, ebx ; a와 b 비교 │ │
│ │ jle else_part ; a <= b이면 else로 │ │
│ │ mov [c], eax ; c = a │ │
│ │ jmp end_if │ │
│ │ else_part: │ │
│ │ mov [c], ebx ; c = b │ │
│ │ end_if: │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ARM 어셈블리 (AArch64): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ LDR W0, [X8] ; W0 = a │ │
│ │ LDR W1, [X9] ; W1 = b │ │
│ │ CMP W0, W1 ; a와 b 비교 │ │
│ │ CSEL W2, W0, W1, GT ; W2 = (a>b) ? a : b │ │
│ │ STR W2, [X10] ; c = W2 │ │
│ │ │ │
│ │ ; 또는 조건부 분기 사용: │ │
│ │ LDR W0, [X8] │ │
│ │ LDR W1, [X9] │ │
│ │ CMP W0, W1 │ │
│ │ B.LE else_part │ │
│ │ STR W0, [X10] ; c = a │ │
│ │ B end_if │ │
│ │ else_part: │ │
│ │ STR W1, [X10] ; c = b │ │
│ │ end_if: │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6. 메모리 접근 명령어¶
6.1 x86 메모리 접근¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 메모리 접근 │
│ │
│ 주소 지정 형식: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [base + index * scale + displacement] │ │
│ │ │ │
│ │ - base: 레지스터 (선택) │ │
│ │ - index: 레지스터 (선택) │ │
│ │ - scale: 1, 2, 4, 8 (배율) │ │
│ │ - displacement: 상수 (선택) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 메모리 접근 예시: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 직접 주소 │ │
│ │ mov eax, [0x1000] ; eax = Memory[0x1000] │ │
│ │ │ │
│ │ ; 레지스터 간접 │ │
│ │ mov eax, [ebx] ; eax = Memory[ebx] │ │
│ │ │ │
│ │ ; 변위 │ │
│ │ mov eax, [ebx + 8] ; eax = Memory[ebx + 8] │ │
│ │ │ │
│ │ ; 인덱스 │ │
│ │ mov eax, [ebx + ecx] ; eax = Memory[ebx + ecx] │ │
│ │ │ │
│ │ ; 스케일 인덱스 (배열 접근에 유용) │ │
│ │ mov eax, [ebx + ecx*4] ; array[i] (int 배열) │ │
│ │ │ │
│ │ ; 전체 형식 │ │
│ │ mov eax, [ebx + ecx*4 + 100] ; struct.array[i] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 크기 지정: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ mov byte [eax], 0x41 ; 1바이트 저장 │ │
│ │ mov word [eax], 0x4142 ; 2바이트 저장 │ │
│ │ mov dword [eax], 0x41424344 ; 4바이트 저장 │ │
│ │ mov qword [rax], rbx ; 8바이트 저장 (64비트) │ │
│ │ │ │
│ │ ; 크기 확장 로드 │ │
│ │ movzx eax, byte [ebx] ; 0 확장 (8→32비트) │ │
│ │ movsx eax, byte [ebx] ; 부호 확장 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 문자열 연산: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; ESI = 소스, EDI = 목적지, ECX = 카운터 │ │
│ │ movsb ; [EDI] = [ESI], ESI++, EDI++ │ │
│ │ rep movsb ; ECX번 반복 (memcpy) │ │
│ │ cmpsb ; [ESI]와 [EDI] 비교 │ │
│ │ scasb ; AL과 [EDI] 비교 │ │
│ │ stosb ; [EDI] = AL (memset) │ │
│ │ lodsb ; AL = [ESI] │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6.2 ARM 메모리 접근¶
┌─────────────────────────────────────────────────────────────────────────┐
│ ARM 메모리 접근 │
│ │
│ 기본 Load/Store: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; Load │ │
│ │ LDR X0, [X1] ; X0 = Memory[X1] (64비트) │ │
│ │ LDR W0, [X1] ; W0 = Memory[X1] (32비트) │ │
│ │ LDRH W0, [X1] ; W0 = Memory[X1] (16비트, 0확장) │ │
│ │ LDRB W0, [X1] ; W0 = Memory[X1] (8비트, 0확장) │ │
│ │ LDRSH W0, [X1] ; 부호 확장 하프워드 │ │
│ │ LDRSB W0, [X1] ; 부호 확장 바이트 │ │
│ │ │ │
│ │ ; Store │ │
│ │ STR X0, [X1] ; Memory[X1] = X0 │ │
│ │ STRH W0, [X1] ; Memory[X1] = 하위 16비트 │ │
│ │ STRB W0, [X1] ; Memory[X1] = 하위 8비트 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 주소 지정 모드: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 오프셋 │ │
│ │ LDR X0, [X1, #8] ; X0 = Memory[X1 + 8] │ │
│ │ LDR X0, [X1, #-8] ; X0 = Memory[X1 - 8] │ │
│ │ │ │
│ │ ; 레지스터 오프셋 │ │
│ │ LDR X0, [X1, X2] ; X0 = Memory[X1 + X2] │ │
│ │ LDR X0, [X1, X2, LSL #3] ; X0 = Memory[X1 + X2*8] │ │
│ │ │ │
│ │ ; Pre-indexed (계산 후 베이스 갱신) │ │
│ │ LDR X0, [X1, #8]! ; X1 += 8; X0 = Memory[X1] │ │
│ │ │ │
│ │ ; Post-indexed (접근 후 베이스 갱신) │ │
│ │ LDR X0, [X1], #8 ; X0 = Memory[X1]; X1 += 8 │ │
│ │ │ │
│ │ ; Literal (PC-relative, 상수 로드에 유용) │ │
│ │ LDR X0, =0x12345678 ; X0 = 0x12345678 (리터럴 풀 사용) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 다중 레지스터 Load/Store: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; 레지스터 쌍 │ │
│ │ LDP X0, X1, [X2] ; X0=Mem[X2], X1=Mem[X2+8] │ │
│ │ STP X0, X1, [X2] ; Mem[X2]=X0, Mem[X2+8]=X1 │ │
│ │ │ │
│ │ ; 스택 프레임 설정 (일반적인 패턴) │ │
│ │ STP X29, X30, [SP, #-16]! ; 프레임/링크 레지스터 저장 │ │
│ │ MOV X29, SP ; 새 프레임 포인터 │ │
│ │ ... │ │
│ │ LDP X29, X30, [SP], #16 ; 복원 │ │
│ │ RET │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7. 간단한 어셈블리 프로그램 예제¶
7.1 Hello World (x86-64 Linux)¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Hello World (x86-64 Linux, NASM) │
│ │
│ ; hello.asm │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ section .data │ │
│ │ msg db "Hello, World!", 10 ; 문자열 + 개행 │ │
│ │ len equ $ - msg ; 문자열 길이 │ │
│ │ │ │
│ │ section .text │ │
│ │ global _start │ │
│ │ │ │
│ │ _start: │ │
│ │ ; write(1, msg, len) │ │
│ │ mov rax, 1 ; syscall: write │ │
│ │ mov rdi, 1 ; fd: stdout │ │
│ │ mov rsi, msg ; 버퍼 주소 │ │
│ │ mov rdx, len ; 길이 │ │
│ │ syscall │ │
│ │ │ │
│ │ ; exit(0) │ │
│ │ mov rax, 60 ; syscall: exit │ │
│ │ xor rdi, rdi ; 종료 코드: 0 │ │
│ │ syscall │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 컴파일 및 실행: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ $ nasm -f elf64 hello.asm -o hello.o │ │
│ │ $ ld hello.o -o hello │ │
│ │ $ ./hello │ │
│ │ Hello, World! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.2 두 수의 합 (x86-64)¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 두 수의 합 계산 (x86-64) │
│ │
│ ; sum.asm - C에서 호출 가능한 함수 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ; int add(int a, int b); │ │
│ │ ; x86-64 호출 규약: 인자는 RDI, RSI, RDX, RCX, R8, R9 │ │
│ │ ; 반환값은 RAX │ │
│ │ │ │
│ │ section .text │ │
│ │ global add │ │
│ │ │ │
│ │ add: │ │
│ │ ; 프롤로그 (간단한 함수는 생략 가능) │ │
│ │ mov eax, edi ; eax = 첫 번째 인자 (a) │ │
│ │ add eax, esi ; eax += 두 번째 인자 (b) │ │
│ │ ret ; 결과는 eax에 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ C에서 호출: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ // main.c │ │
│ │ extern int add(int a, int b); │ │
│ │ │ │
│ │ int main() { │ │
│ │ int result = add(10, 20); │ │
│ │ printf("10 + 20 = %d\n", result); │ │
│ │ return 0; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 컴파일: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ $ nasm -f elf64 sum.asm -o sum.o │ │
│ │ $ gcc main.c sum.o -o program │ │
│ │ $ ./program │ │
│ │ 10 + 20 = 30 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.3 반복문 (ARM AArch64)¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 반복문 (ARM AArch64) │
│ │
│ C 코드: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ // 배열 합계 계산 │ │
│ │ int sum_array(int *arr, int n) { │ │
│ │ int sum = 0; │ │
│ │ for (int i = 0; i < n; i++) { │ │
│ │ sum += arr[i]; │ │
│ │ } │ │
│ │ return sum; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ARM 어셈블리: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ // X0 = arr (배열 포인터), W1 = n (배열 크기) │ │
│ │ // 반환: W0 = 합계 │ │
│ │ │ │
│ │ sum_array: │ │
│ │ MOV W2, #0 ; sum = 0 │ │
│ │ MOV W3, #0 ; i = 0 │ │
│ │ CMP W1, #0 ; n == 0 체크 │ │
│ │ B.LE done ; n <= 0이면 종료 │ │
│ │ │ │
│ │ loop: │ │
│ │ LDR W4, [X0, W3, SXTW #2] ; W4 = arr[i] │ │
│ │ ; (i를 4배하여 오프셋) │ │
│ │ ADD W2, W2, W4 ; sum += arr[i] │ │
│ │ ADD W3, W3, #1 ; i++ │ │
│ │ CMP W3, W1 ; i < n ? │ │
│ │ B.LT loop ; 참이면 반복 │ │
│ │ │ │
│ │ done: │ │
│ │ MOV W0, W2 ; 반환값 = sum │ │
│ │ RET │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 최적화 버전 (post-increment 사용): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ sum_array_opt: │ │
│ │ MOV W2, #0 ; sum = 0 │ │
│ │ CBZ W1, done ; n == 0이면 바로 종료 │ │
│ │ │ │
│ │ loop: │ │
│ │ LDR W3, [X0], #4 ; W3 = *arr++ │ │
│ │ ADD W2, W2, W3 ; sum += *arr │ │
│ │ SUBS W1, W1, #1 ; n-- (플래그 설정) │ │
│ │ B.NE loop ; n != 0이면 반복 │ │
│ │ │ │
│ │ done: │ │
│ │ MOV W0, W2 │ │
│ │ RET │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.4 함수 호출과 스택 프레임¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 함수 호출과 스택 프레임 │
│ │
│ x86-64 스택 프레임: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 높은 주소 │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ 이전 프레임 │ │ │
│ │ ├─────────────────────┤ │ │
│ │ │ 7번째+ 인자 │ (스택으로 전달) │ │
│ │ ├─────────────────────┤ │ │
│ │ │ 반환 주소 │ ◄─── CALL에 의해 push │ │
│ │ ├─────────────────────┤ │ │
│ │ │ 저장된 RBP │ ◄─── 이전 프레임 포인터 │ │
│ │ ├─────────────────────┤ ◄─── RBP (현재 프레임 시작) │ │
│ │ │ 지역 변수 1 │ │ │
│ │ ├─────────────────────┤ │ │
│ │ │ 지역 변수 2 │ │ │
│ │ ├─────────────────────┤ │ │
│ │ │ 저장된 레지스터 │ (callee-saved) │ │
│ │ ├─────────────────────┤ ◄─── RSP (스택 탑) │ │
│ │ │ │ │ │
│ │ 낮은 주소 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 함수 프롤로그/에필로그 (x86-64): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ my_function: │ │
│ │ ; 프롤로그 │ │
│ │ push rbp ; 이전 프레임 포인터 저장 │ │
│ │ mov rbp, rsp ; 새 프레임 설정 │ │
│ │ sub rsp, 32 ; 지역 변수 공간 할당 │ │
│ │ push rbx ; callee-saved 레지스터 저장 │ │
│ │ │ │
│ │ ; 함수 본문 │ │
│ │ ... │ │
│ │ │ │
│ │ ; 에필로그 │ │
│ │ pop rbx ; 레지스터 복원 │ │
│ │ mov rsp, rbp ; 스택 정리 │ │
│ │ pop rbp ; 프레임 포인터 복원 │ │
│ │ ret │ │
│ │ │ │
│ │ ; 또는 leave 명령어 사용 │ │
│ │ leave ; mov rsp, rbp + pop rbp │ │
│ │ ret │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ARM AArch64 함수: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ my_function: │ │
│ │ ; 프롤로그 │ │
│ │ STP X29, X30, [SP, #-32]! ; FP, LR 저장 │ │
│ │ MOV X29, SP ; 새 프레임 설정 │ │
│ │ STP X19, X20, [SP, #16] ; callee-saved 저장 │ │
│ │ │ │
│ │ ; 함수 본문 │ │
│ │ ... │ │
│ │ │ │
│ │ ; 에필로그 │ │
│ │ LDP X19, X20, [SP, #16] ; 레지스터 복원 │ │
│ │ LDP X29, X30, [SP], #32 ; FP, LR 복원 │ │
│ │ RET │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
8. 연습 문제¶
기초 문제¶
-
어셈블리 언어와 기계어의 관계를 설명하시오.
-
x86에서 다음 레지스터의 용도를 설명하시오:
- (a) EAX
- (b) ESP
-
(c) EIP/RIP
-
Intel 문법과 AT&T 문법의 주요 차이점 2가지를 쓰시오.
x86 어셈블리 문제¶
-
다음 x86 어셈블리 코드의 실행 후 EAX의 값은?
asm mov eax, 10 mov ebx, 5 add eax, ebx shl eax, 1 -
다음 C 코드를 x86-64 어셈블리로 변환하시오:
c int abs(int x) { if (x < 0) return -x; return x; }
ARM 어셈블리 문제¶
-
ARM에서 조건부 실행의 장점을 설명하시오.
-
다음 ARM 코드의 실행 후 X0의 값은?
asm MOV X0, #10 MOV X1, #3 MUL X0, X0, X1 SUB X0, X0, #5
메모리 접근 문제¶
-
x86에서
[EBX + ECX*4 + 100]주소 지정의 각 요소가 의미하는 것을 설명하시오. -
ARM의 pre-indexed와 post-indexed 주소 지정의 차이를 예시와 함께 설명하시오.
심화 문제¶
- 다음 C 코드를 x86-64 또는 ARM AArch64 어셈블리로 변환하시오:
c int factorial(int n) { int result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result; }
정답
1. 어셈블리 언어는 기계어(0과 1)를 사람이 읽을 수 있는 니모닉(ADD, MOV 등)으로 표현한 것입니다. 어셈블러가 어셈블리 코드를 기계어로 1:1 변환합니다. 2. 레지스터 용도: - (a) EAX: 누산기, 산술 연산 결과 저장, 함수 반환값 - (b) ESP: 스택 포인터, 스택의 최상단 주소 - (c) EIP/RIP: 명령어 포인터, 다음 실행할 명령어의 주소 3. Intel vs AT&T: - 피연산자 순서: Intel은 (dst, src), AT&T는 (src, dst) - 접두사: Intel은 없음, AT&T는 레지스터에 %, 즉시값에 $ 4. 코드 실행 결과: - mov eax, 10 → eax = 10 - add eax, ebx → eax = 15 - shl eax, 1 → eax = 30 5. abs 함수 (x86-64): ```asm abs: mov eax, edi ; eax = x test eax, eax ; x 부호 확인 jns positive ; x >= 0이면 점프 neg eax ; x = -x positive: ret ``` 6. ARM 조건부 실행 장점: - 짧은 분기 시 파이프라인 플러시 방지 - 코드 크기 감소 - 분기 예측 실패 페널티 없음 7. ARM 코드 결과: - MOV X0, #10 → X0 = 10 - MUL X0, X0, X1 → X0 = 30 - SUB X0, X0, #5 → X0 = 25 8. x86 주소 지정 요소: - EBX: 베이스 레지스터 (배열 시작 주소) - ECX: 인덱스 레지스터 (배열 인덱스) - 4: 스케일 (요소 크기, int = 4바이트) - 100: 변위 (구조체 내 오프셋) 9. ARM 인덱싱: - Pre-indexed `LDR X0, [X1, #8]!`: X1 += 8 후 로드 - Post-indexed `LDR X0, [X1], #8`: 로드 후 X1 += 8 10. factorial (x86-64): ```asm factorial: mov eax, 1 ; result = 1 cmp edi, 1 jle done ; n <= 1이면 종료 mov ecx, 2 ; i = 2 loop: imul eax, ecx ; result *= i inc ecx ; i++ cmp ecx, edi jle loop ; i <= n이면 반복 done: ret ```다음 단계¶
- 11_Pipelining.md - 파이프라인 단계, 해저드, 포워딩
참고 자료¶
- x86 Instruction Set Reference
- ARM Developer Documentation
- NASM Documentation
- Godbolt Compiler Explorer - 다양한 아키텍처 어셈블리 확인
- Computer Organization and Design (Patterson & Hennessy)