x86 vs x64
- x86 : 32bit 시스템. Intel 8086 프로세서 이후 80286, 80386 같은 모델들이 등장하면서 유래
- x64 : 64bit 시스템
(32bit) 어셈블리어 명령어
헷갈리는 mov 명령어 먼저 이해하기
// C Code
long exchange(long *xp, long y) {
long x = *xp;
*xp = y;
return x;
}
// Assembly code
// xp in %rdi, y in %rsi
exchange:
movq (%rdi), %rax # Get x at xp. Set as retrun value.
movq %rsi, (%rdi) # Store y at xp.
ret # Return.
x86 | C언어 | 설명 | x86-64 |
mov a, b | 괄호 유무에 따라 의미 상이 |
[참고 CS:APP 180p] 위 코드 블록 참고 괄호가 있는 경우는 메모리 주소를 의미하고, 없는 경우 레지스터 간의 값 이동을 의미한다. [예시] movq a_val, b_val a_val 값을 b_val 로 복사 movq (a_ptr), b_val a_ptr이 가리키는 주소에서 값을 읽어와 b_val에 저장함 movq a_val, (b_ptr) a_val 값을 b_ptr이 가리키는 메모리 주소에 저장함 [예시2] movq %rbp, (%rsp) b의 값(%rbp)을 a(%rsp가 가리키는 메모리 주소)에 복사한다. %rbp 값을 스택에 저장함 |
|
lea a, [b] | a = *b | b의 주소에 있는 값을 a에 복사 | leaq |
cmp a, b | if문에서 주로 사용 | a와 b를 비교 | |
add a, b | a += b | a와 b를 더해 a에 결과를 넣기 | |
sub a, b | a -= b | ||
mul a, b | a *= b | ||
xor | a ^= b | ||
je | if (! zero_flag) jump | 비교값이 같은 경우 jump | |
jne | if (zero_flag) jump | 비교값이 다른 경우 jump | |
call | 리턴 주소 (caller 다음 명령어 주소) 스택에 저장 > callee 주소로 이동 > 리턴 주소로 복귀 |
||
jmp | 해당 주소로 점프 / 전으로 돌아갈 수 없음 |
레지스터 종류
- CPU 아키텍셔체 따라 레지스터 종류가 다를 수 있음. 범용 레지스터 또한 규약(stdcall)에 따라 다르게 사용될 수도
- E : extended, R : Register
- 베이스 레지스터 : 메모리의 특정 영역에 대한 기준 주소 저장, 스택 프레임 관리
x86 | x86-64 | 설명 |
rdi rsi rdx rcx r8 r9 |
- 범용레지스터 - 함수 호출 시 첫번째 ~ 여섯번째 인수 전달 (6개 이상의 인수를 필요로 할 경우, 추가 인수는 스택을 통해 전달됨 - 함수 인수 전달 외에도 다른 작업에서 사용될 수 있음 |
|
r8 ~ r13 | 범용 레지스터, 함수 호출 시 인수 전달에 사용 | |
eax | rax | 함수 리턴값 저장됨 |
rbx | rbx | (base register) 종종 배열, 문자열과 같은 구조에 접근하기 위한 기준 포인터 |
rbp | (base pointer) 함수 호출 스택 프레임의 기준을 설정 | |
esi | rsi | (범용 레지스터 역할 외) 소스 인수 포인터, 문자열 및 배열 등의 데이터 소스 포인터 역할 |
edi | rdi | (범용 레지스터 역할 외) 대상 인수 포인터, 문자열 및 배열의 목적지 포인터 역할 |
esp | rsp | top of stack |
eip | rip | (instruction pointer, program counter) 다음에 실행될 명령어 주소 |
; rdi에 첫 번째 인수, rsi에 두 번째 인수를 저장
mov rdi, 5 ; 첫 번째 인수로 5 저장
mov rsi, 10 ; 두 번째 인수로 10 저장
call my_function ; my_function 호출
; 메모리에서 데이터를 복사하는 경우
mov rsi, source_address ; 원본 주소를 rsi에 저장
mov rdi, destination_address ; 목적지 주소를 rdi에 저장
mov eax, [rsi] ; 원본 주소에서 값 읽기
mov [rdi], eax ; 읽은 값을 목적지 주소에 저장
C 소스파일 ➡️ (GCC 컴파일) ➡️ 어셈블리어
$ gcc -S {file_name}.c // file_name.s 어셈블리 코드 생성 (컴파일 전처리 후 단계)
// 어셈블리어 주소 포함해서
$ gcc -g -o output_file source_file.c // 디버깅 정보 포함해 컴파일
$ objdump -d output_file // 디스어셈블: binary file to assembly
단순 덧셈 연산
int main() {
int a = 3;
int b = 4;
return a + b;
}
컴파일 결과
.file "test.c" // 파일명 지시자
.text
.globl main
.type main, @function
main: // main 함수 시작
.LFB0: // 함수 시작 레이블
.cfi_startproc // 스택 프레임 시작
endbr64
pushq %rbp // 현재 베이스 포인터를 스택에 푸시
.cfi_def_cfa_offset 16 // 스택 프레임의 현재 오프셋을 정의
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $3, -8(%rbp) // -8(%rbp) 위치에 정수 3 저장 (첫번째 변수)
movl $4, -4(%rbp) // -4(%rbp) 위치에 정수 4 저장 (두번째 변수)
movl -8(%rbp), %edx // 첫번째 변수 값을 edx에 로드
movl -4(%rbp), %eax // 두번째 변수 값을 edx에 로드
addl %edx, %eax // 두 변수의 합을 eax에 저장
popq %rbp // 스택에서 이전 베이스 포인터 복원
.cfi_def_cfa 7, 8
ret // 함수 종료
.cfi_endproc // 스택 프레임 처리 종료
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
Q. printf 문과 같이 함수 호출이 추가되면?
더보기
C 소스파일
#include <stdio.h>
int main() {
int a = 3;
int b = 4;
printf("%d\n", a + b);
return 0;
}
gcc -S 결과
.file "test.c" // 파일 지시자
.text
.section .rodata
.LC0: // 문자열 리터럴 레이블
.string "%d\n"
.text
.globl main
.type main, @function
main: // main 함수 시작
.LFB0: // 함수 시작 레이블
.cfi_startproc // 스택 프레임 시작, 디버깅과 스택 트레이스 지원을 위함 (함수의 매개변수, 지역변수, 리턴 주소 등 정보 저장)
endbr64
pushq %rbp // 현재 베이스 포인터를 스택에 저장
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp // 현재 스택 포인터를 베이스 포인터로 설정
.cfi_def_cfa_register 6
subq $16, %rsp // (로컬 변수용) 스택에서 16byte 공간 확보
movl $3, -8(%rbp) // (스택 -8byte 위치) 3 저장 (변수 a)
movl $4, -4(%rbp) // (스택 -4byte 위치) 4 저장 (변수 b)
movl -8(%rbp), %edx // 변수 a %edx 레지스터에 로드
movl -4(%rbp), %eax // 변수 b %eax 레지스터에 로드
addl %edx, %eax // 덧셈
movl %eax, %esi // 결과를 esi 레지스터에 저장
leaq .LC0(%rip), %rax // 문자열 리터럴 주소를 rax 레지스터에 로드
movq %rax, %rdi // rdi에 문자열 주소 저장
movl $0, %eax
call printf@PLT // printf 함수 호출
movl $0, %eax // 함수 반환값 0으로 설정
leave
.cfi_def_cfa 7, 8
ret // 함수 종료, 호출한 곳으로 복귀
.cfi_endproc // 스택 프레임 종료
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8 // 8byte 정렬
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
gcc -O2 -S 결과 (최적화 후 주요 코드만 남김)
.LC0:
.string "%d\n"
main
movl $7, %edx // 덧셈 결과인 7을 edx에 저장
movl $1, %edi // %d 형식 문자열의 주소를 edi에 저장
leaq .LC0(%rip), %rsi // %LC0 문자열 리터럴 주소를 rsi에 저장
call __printf_chk@PLT // printf 호출
Q. 레지스터 개수가 한정되었는데 어떻게 복잡한 연산을 진행하는지 궁금해서 여러 값을 넣어보았다.
더보기
C언어
#include <stdio.h>
int main() {
int a = 1 + 3 + 3 + 3 + 3;
printf("%d %d %s %s", a, 123, "ABC", "HELLO");
return 0;
}
어셈블리어 (생략 버전)
section .data
fmt db "%d %d %s %s", 0 ; 포맷 문자열 정의
str1 db "ABC", 0 ; 문자열 리터럴
str2 db "HELLO", 0 ; 문자열 리터럴
section .text
global main
extern printf
main:
; a = 1 + 3 + 3 + 3 + 3; 계산
mov eax, 1 ; eax에 1 저장
add eax, 3 ; eax에 3 추가
add eax, 3 ; eax에 3 추가
add eax, 3 ; eax에 3 추가
add eax, 3 ; eax에 3 추가
; eax = 13이 됨
; printf 호출
push str2 ; "HELLO" 문자열 주소 푸시
push str1 ; "ABC" 문자열 주소 푸시
push 123 ; 정수 123 푸시
push eax ; 계산된 a (13) 푸시
push fmt ; 포맷 문자열 주소 푸시
call printf ; printf 호출
add esp, 20 ; 스택 정리 (푸시한 인자의 크기만큼)
; 프로그램 종료
mov eax, 0 ; 종료 코드 0
ret
'2️⃣ 개발 지식 B+ > OS' 카테고리의 다른 글
[PintOS Project2] USER PROGRAMS (2) | 2024.10.08 |
---|---|
[PintOS Project1] THREADS (1) | 2024.10.08 |
[네트워크] echo 예제로 이해하는 소켓 인터페이스 (0) | 2024.09.16 |
[C] 문법 빠르게 훑기 (0) | 2024.08.27 |
[우분투 20.04] 인코딩 utf-8 (0) | 2021.03.30 |