x64 아키텍쳐에서 다음의 명령어로 스택을 조작한다.
push val : val을 스택 최상단에 쌓음
연산 rsp -= 8 [rsp] = val
예제 [register] rsp = 0x7fffffffc400 [stack] 0x7fffffffc400 | 0x0 <=rsp 0x7fffffffc408 | 0x0 [code] push 0x31337
결과 [register] rsp = 0x7fffffffc3f8 [stack] 0x7fffffffc3f8 | 0x31337 <=rsp 0x7fffffffc400 | 0x0 0x7fffffffc408 | 0x0
밸류 0x31337을 0x7fffffffc400에서 연산인 8을 뺀 0x7fffffffc3f8(주소)에 넣는다.
push reg : 스택 최상단의 값을 꺼내서 reg에 대입
연산 rsp += 8 reg = [rsp-8]
예제 [register] rax = 0 rsp = 0x7fffffffc3f8 [stack] 0x7fffffffc3f8 | 0x31337 <=rsp 0x7fffffffc400 | 0x0 0x7fffffffc408 | 0x0 [code] pop rax
결과 [register] rax = 0x31337 rsp = 0x7fffffffc400 [stack] 0x7fffffffc3f8 | 0x0 <=rsp 0x7fffffffc400 | 0x0
주소 0x7fffffffc3f8에 있는 0x31337(밸류)를 rax에 대입한다. 자연스럽게 그 아래 스택인 0x7fffffffc400이 rsp가 된다.
프로시저는 특정 기능을 수행하는 코드 조각을 말한다. 프로시저를 사용하면 반복되는 연산을 프로시저 호출로 대체할 수 있어서 코드의 크기를 줄일수있고 코드조각에 이름을 붙일수 있어 코드의 가독성을 높일 수 있다.
프로시저를 부르는 행위를 호출(call)이라 부르며 프로시저에서 돌아오는 것을 반환(Return)이라고 부른다.
call addr : addr에 위치한 프로시져 호출
연산 push return_address jmp addr
예제 [register] rip = 0x400000 rsp = 0x7fffffffc400 [stack] 0x7fffffffc3f8 | 0x0 0x7fffffffc400 | 0x0 <= rsp [code] 0x400000 | call 0x401000 <= rip 0x400005 | mov esi, eax ... 0x401000 | push rbp
결과 [register] rip = 0x401000 rsp = 0x7fffffffc3f8 [stack] 0x7fffffffc3f8 | 0x400005 <= rsp 0x7fffffffc400 | 0x0 [code] 0x400000 | call 0x401000 0x400005 | mov esi, eax ... 0x401000 | push rbp <= rip
leave : 스택 프레임 정리
연산 mov rsp, rbp pop rbp #pop은 스택을 빼는 함수
예제 [register] rsp = 0x7fffffffc400 rbp = 0x7fffffffc480 [stack] 0x7fffffffc400 | 0x0 <= rsp ... 0x7fffffffc480 | 0x7fffff500 <= rbp 0x7fffffffc488 | 0x31337 [code] leave
결과 [register] rsp = 0x7fffffffc488 rbp = 0x7fffffffc500 [stack] 0x7fffffffc400 | 0x0 ... 0x7fffffffc480 | 0x7fffffffc500 0x7fffffffc488 | 0x31337 <= rsp 0x7fffffffc500 | 0x7fffffffc550 <= rbp
ret : return address로 반환
연산 pop rip
예제 [register] rip = 0x401000 rsp = 0x7fffffffc3f8 [stack] 0x7fffffffc3f8 | 0x400005 <= rsp [code] 0x400000 | call 0x401000 0x400005 | mov esi, eax ... 0x401000 | mov rbp, rsp .. 0x401007 | leave 0x401008 | ret <= rip
결과 [register] rip = 0x401005 rsp = 0x7fffffffc400 [stack] 0x7fffffffc3f8 | 0x400005 0x8fffffffc400 | 0x0 <= rsp [code] 0x400000 | call 0x401000 0x400005 | mov esi, eax <= rip ... 0x401000 | mov rbp, rsp .. 0x401007 | leave 0x401008 | ret
용어
데이터 레지스터 RAX(Extended Accmulator Register) : 산술, 논리 연산을 수행하며 함수의 반환값이 이 레지스터에 저장된다. 누산기 레지스터 RBX(Extended Base Pointer) : 메모리의 주소를 저장하는 용도로 사용된다. 베이스 레지스터 RCX(Extended Counter Register) : 반복문에 루프카운터로 사용된다. 카운터 레지스터 RDX(Extended Data Register) : RAX와 같이 사용된다. 데이터 레지스터
포인터 레지스터 RSP(Extended Stack Pointer) : 현재 스택 주소, 스택의 맨윗쪽 주소, 스택은 쌓아가는 구조. 스택 포인터 레지스터 RBP(Extended Base Pointer) : 스택복귀 주소, 고급언어에서 스택에 있는 함수 매개 변수와 지역변수를 참조 하기 위해서 사용된다. 베이스 포인터 레지스터 RSI(Extended Source Index)/RDI(Extended Destination Index) : 각각 메모리 출발지와 목적지를 나타낸다. 근원지 인덱스 레지스터목적지 인덱스 레지스터
RIP : 현재 실행되고 있는 명령의 실행 주소이다. 명령 포인터 레지스터 r8~r15 : 함수의 매개변수로 많이 사용된다.일반 적인 범용 레지스터
운영 체제는 컴퓨터 자원의 효율적인 사용과 사용자의 편리한 경험을 제공하기 위해, 내부적으로 매우 복잡한 동작을 한다. 운영체제는 연결된 모든 하드웨어와 소프트 웨어에 접근 할 수 있으며, 이들을 제어 할 수도 있다. 그리고 보안을 위해 커널모드와 유저모드로 권한을 나눈다.
커널 모드는 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한이다. 파일 시스템, 입력/출력, 네트워크 통신,. 메모리 관리등 모든 저수준의 작업은 사용자 모르게 커널모드에서 진행된다.
유저모드는 운영체제가 사용자에게 부여하는 권한이다. 브라우저를 이용하거나, 영상을 시청하는것, 게임과 프로그래밍을 하는 것 등이 모두 유저 모드에서 이루어진다.
시스템 콜(system call, syscall)은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용된다. 소프트웨어 대부분은 커널의 도움이 필요합니다.
x64아키텍쳐에서는 시스템 콜을 위해 syscall 명령어가 있다.
시스템 콜은 함수다. 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면, 커널이 이를 읽어서 요청을 처리한다. 리눅스에서는 x64아키텍쳐에서 rax로 무슨요청인지를 나타내고, 아래의 순서대로 필요한 인자를 전달한다.
syscall
요청 : rax 인자순서 : rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> stack (64bit 함수 호출 규약 / fastcall 방식으로 리눅스 기준 정수의 파라민터 순서에 대한 레지스터가 위와 같다.)
예제 [register] rax = 0x1 rdi = 0x1 rsi = 0x401000 rdx = 0xb [memory] 0x401000 | "Hello Wo" 0x401008 | "rld" [code]
결과 Hello World
해석
syscall table을 보면 rax가 0x01일때 write 시스템 콜을 요청한다. 이때 rdi, rsi, rdx가 0x1, 0x401000, 0xb이므로 커널은 write(0x1, 0x401000, 0xb)를 수행한다.
write함수의 형식은 write(출력 스트림/rdi, 출력 버퍼/rsi, 출력 길이/rdx)인데 여기서 0x01은 stdout이며 이는 일반적으로 화면을 으미한다. 0x401000에는 Hello World가 저장되어 있고, 길이는 0xb로 지정되어 있으니까 화면에 Hello World가 출력된다.
syscall 테이블은 총갯수가 300에 달하지만, 검색하면 쉽게 찾을 수 있으므로 외울 필요는 없다.
드림핵에서 모든 내용을 확인 할수 있습니다.