Assembly - 2

신우빈·2022년 7월 3일
0

Dreamhack

목록 보기
4/7
post-thumbnail

Opcode

명령어. Operation Code


Stack

< 사용되는 명령어 >
1. push
2. pop

< 배경 지식 >
1. rsp : stack pointer. 사용 중인 스택의 위치를 가리키는 포인터
2. 메모리 피연산자는 [ ]에 둘러싸여 표현된다. 즉, [ ]에 들어있는 내용은 메모리 피연산자
3. rsp에 대한 연산은 16진수 연산이다.
4. rax : accumulator register. 함수의 반환 값

PUSH

형식
push val : val을 스택 최상단에 쌓음.
연산 과정
rsp -= 8
[rsp] = val

스택은 배열로 구현되며, 후입선출이라는 특징을 가진다.
그렇기 때문에, 스택의 최하단값은 배열의 마지막 인덱스가 된다.
따라서, push를 할 수록, 주소값이 줄어들어야하기 때문에 rsp -= 8이라는 식이 나온다.
[rsp] = val 는 새롭게 push 한 값의 주소에 val이라는 값을 대입하라는 뜻이다.

예제

< 출처 : https://learn.dreamhack.io/63#4 >

POP

형식
pop reg : 스택 최상단의 값을 꺼내서 reg에 대입

연산 과정
rsp += 8
reg = [rsp-8]

예제

예제 설명
rax = 0, rsp = ~c3f8이다.
pop rax 를 실행하면 rsp += 8 이 가장 먼저 실행돼서 rsp = fc400 이 된다.
이후, rax = [rsp-8] 의 과정을 거치는데, 이는 pop 하기 전 stack의 최상단 주소에 위치하는 값을 rax에 대입하는 것이다.
이런 과정을 거치면 옆의 결과가 나온다.

Procedure

개념

  • Procedure : 특정 기능을 수행하는 코드 조각을 말함
  • Procedure를 사용하면
    반복되는 연산을 프로시저 호출로 대체할 수 있어서 전체 코드의 크기를 줄일 수 있으며,
    기능별로 코드 조각에 이름을 붙일 수 있게 되어 코드의 가독성을 크게 높일 수 있다.
  • Call(호출) : Procedure를 부르는 행위
  • Return(반환) : Procedure에서 돌아오는 것

    Call을 할 때는 Procedure를 실행한 후 원래의 실행 흐름으로 돌아와야해서,
    call 다음의 명령어 주소(return address, 반환 주소) 를 스택에 저장하고
    Procedure로 rip를 이동시킨다.

사용되는 명령어
1. call
2. leave
3. ret

CALL

형식
call addr : addr에 위치한 Procedure 호출
연산
push return_address
jmp addr

앞서 설명한 것처럼, Call 실행 이후 원래 흐름으로 돌아오기 위해 return_address라는 값을 스택에 push 해준다. 그리고 addr로 rip를 이동

rsp : 프로세서가 읽고 있는 현재 명령의 위치를 가리키는 명령 포인터
return_address : Call 다음 명령어 주소

예제

LEAVE

형식
leave : 스택프레임 정리

Stack Frame이란?
Stack은 앞서 배웠다시피, 함수별로 자신의 지역변수 또는 연산과정에서 부차적으로 생겨나는 임시 값 들을 저장하는 영역이다. 이 영역에 구분이 없다면, 서로 다른 두 함수가 같은 메모리 영역을 사용할 수 있게 되고, 이는 정상적인 연산을 수행할 수 없게 합니다.

연산
mov rsp, rbp
pop rbp

rsp 는 사용 중인 스택의 위치를 가리키고, rbp는 스택의 최하단부를 가리킨다.
mov rsp, rbp 는 rsp에 rbp 값을 대입하라는 것이다. 그렇게 되면 rsp = ~fc480이 된다.
그리고 pop rbp 라는 연산을 실행할 때, rsp += 8 이 가장 먼저 실행된다.
따라서 rsp = ~fc488 이 된다.
이후, rbp = [rsp-8] 이므로, rbp에 주소 fc480 에 존재하는 값이 대입된다.
결론적으로 rsp = ~fc488 이고, rbp = fc500이 된다.

검증되지 않은 내 생각
leave 연산은 위와 같은 연산 과정을 거치면서 stack에 새로운 rsp와 rbp를 지정해준다.
즉, 새로운 형태의 stack을 만들어 주는 것이다. 그렇기 때문에 leave가 스택 프레임을 정리하는 명령어인 것 같다.

예시

RET

형식
ret : return address 로 반환

연산
pop rip

rsp += 8 -> rip = [rsp-8]

예시

System Call

배경지식
운영체제는 컴퓨터 자원의 효율적인 사용 + 사용자에게 편리한 경험을 제공하기 위해 내부적으로 매우 복잡한 동작을 함.
운영체제는 모든 HW 및 SW에 접근할 수 있고, 제어할 수 있다.
이런 막강한 권한을 보호하기 위해 커널 모드와 유저 모드로 권한을 나눔.

  • 커널 모드 : 운영체제가 전체 시스템을 제어하기 위해 시스템 SW에 부여하는 권한
    - 파일 시스템, 입/출력, 네트워크 통신, 메모리 관리 등 모든 저수준의 작업이 진행
    • 시스템의 모든 부분을 제어할 수 있는 곳
  • 유저 모드 : 운영체제가 사용자에게 부여하는 권한
    - 유튜브 시청, 리눅스에 root 권한으로 사용자 추가 등 일반적인 활동이 발생

개념

  • 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용
  • 유저 모드의 SW가 필요한 도움 요청 -> 커널이 요청한 동작 수행하여 유저에게 결과 반환
  • x64아키텍처에는 syscall 명령어 존재

syscall

시스템 콜은 함수
따라서, 필요한 기능 및 인자에 대한 정보를 레지스터로 전달 -> 커널이 읽고 요청 처리
리눅스에서는 x64 아키텍쳐에서 rax로 무슨 요청인지 나타내고, 순서대로 필요한 인자 전달

예시

요약

Stack

  • push var : rsp -=8 -> [rsp] = val
  • pop reg : rsp += 8 -> reg = [rsp-8]

Procedure

  • call addr : addr의 procedure를 호출
  • leave : Stack Frame 정리
  • ret : 호출자의 실행 흐름을 돌아감

System call

  • syscall : 커널에게 필요한 동작을 요청

0개의 댓글