기본구조 : 명령어 + 피연산자
상수(Immediate Value)
레지스터(Register)
메모리(Memory)
메모리 피연산자는 [ ]으로 둘러싸인 것으로 표현, 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있음
타입에는 BYTE(1바이트), WORD(2바이트), DWORD(4바이트), QWORD(8바이트)가 올 수 있음
ex ) WORD PTR [rax] : rax가 가르키는 주소에서 데이터를 2바이트 만큼 참조
데이터 이동(Data Transfer) : 어떤 값을 레지스터나 메모리에 옮기도록 지시
산술 연산(Arithmetic) : 덧셈, 뺄셈, 곱셈, 나눗셈 연산 지시
논리 연산(Logical) : 비트 연산 지시(비트 단위)
비교(Comparison) : 두 피연산자의 값을 비교하고, 플래그 설정
분기(Branch) : rip를 이동시켜 실행 흐름을 바꿈
스택(Stack)
push val : val을 스택 최상단에 쌓음
연산
rsp -= 8
[rsp] = val
pop reg : 스택 최상단의 값을 꺼내서 reg에 대입
연산
rsp += 8
reg = [rsp - 8]
프로시저(Procedure)
: 특정 기능을 수행하는 코드
호출(Call) : 프로시저를 부르는 행위
반환(Return) : 프로시저에서 돌아오는 것
프로시져를 호출할 때는 프로시저를 실행하고 나서 원래의 실행 흐름으로 돌아와야 하므로, call 다음의 명령어 주소(Return Address, 반환 주소)를 스택에 저장하고 프로시저로 rip를 이동시킴
call addr : addr에 위치한 프로시져 호출
연산
push return_address
jmp addr
leave : 스택프레임 정리
연산
mov rsp, rbp
pop rbp
ret : return address로 반환
연산
pop rip
시스템 콜(System call)
: 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용
커널 모드 : 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한
유저 모드 : 운영체제가 사용자에게 부여하는 권한
syscall : 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면, 커널이 이를 읽어서 요청을 처리
리눅스에서는 x64 아키텍처에서 rax로 무슨 요청인지 나타내고, 순서대로 필요한 인자를 전달
요청 : rax
인자 순서 : rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack
syscall 테이블
| syscall | rax | arg0(rdi) | arg1(rsi) | arg2(rdx) |
|---|---|---|---|---|
| read | 0x00 | unsigned int fd | char *buf | size_t count |
| write | 0x01 | unsigned int fd | const char *buf | size_t count |
| open | 0x02 | const char *filename | int flags | umode_t mode |
| close | 0x03 | unsigned int fd | ||
| mprotect | 0x0a | unsigned long start | size_t len | unsigned long prot |
| connect | 0x2a | int sockfd | struct sockaddr *addr | int addrlen |
| execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |