CPU의 실행 흐름을 RIP, RSP, RBP 관점으로 파헤치기

eunduk.log·2026년 9월 3일

백엔드

목록 보기
1/5

개요

  • 아래 명령어의 흐름으로 RIP, RSP, RBP에 어떤 주소가 들어가는지를 파악한다.
    0x1000: call foo
    0x1005: mov rax, 1
    
    0x2000: foo:
    0x2000: push rbp
    0x2001: mov rbp, rsp
    0x2004: sub rsp, 16
    0x2008: mov DWORD PTR [rbp-4], 10
    0x200F: mov DWORD PTR [rbp-8], 20
    0x2016: mov rsp, rbp
    0x2019: pop rbp
    0x201A: ret
  • 파악할 흐름
    • foo() 호출
    • 함수 프레임 생성
    • 지역 변수 2개 정도 쓸 공간 확보
    • 함수 종료 시 프레임 정리
    • 원래 위치로 복귀

1. 시작 전 상태

우선 call foo를 실행하기 직전 상태를 가정

RIP = 0x1000
RSP = 0x7fff1000
RBP = 0x7fff1020

의미:

  • RIP = 0x1000 → 이제 call foo를 실행할 차례
  • RSP = 0x7fff1000 → 현재 스택 top 위치
  • RBP = 0x7fff1020 → 지금 이 코드를 호출한 함수(예: main)의 프레임 기준점

아직 foo의 프레임은 시작 전


2. call foo

코드:

0x1000: call foo
0x1005: mov rax, 1

call foo가 실행되면 CPU는 자동으로:

  1. 복귀 주소 0x1005를 스택에 저장
  2. RIPfoo 시작 주소 0x2000으로 변경

개념적으로는:

RSP = RSP - 8        ; 0x7fff0ff8
[RSP] = 0x1005
RIP = 0x2000
  • RSP - 8 : 스택에 8바이트 데이터를 넣기 위해 공간을 확보
  • [RSP] = 0x1005 : 확보된 공간의 주소에 복귀할 주소를 저장

상태:

RIP = 0x2000
RSP = 0x7fff0ff8
RBP = 0x7fff1020

스택 모습:

높은 주소
...
0x7fff1000
0x7fff0ff8 : [0x1005]   <- foo 끝나면 돌아갈 주소
낮은 주소

여기서 핵심:

  • RSP는 복귀 주소가 들어가면서 내려감
  • RBP는 아직 호출자 함수 기준점 그대로
  • foo는 아직 자기 프레임 기준점을 안 잡음

3. push rbp

foo 첫 줄 시작

0x2000: push rbp

현재 RBP에는 호출자 함수의 기준점 0x7fff1020이 들어 있다. (예를 들면 main함수)

이걸 저장해둬야 나중에 복구할 수 있음

동작:

RSP = RSP - 8        ; 0x7fff0ff0
[RSP] = RBP          ; 0x7fff1020 저장

상태:

RIP = 0x2001
RSP = 0x7fff0ff0
RBP = 0x7fff1020

스택:

높은 주소
...
0x7fff0ff8 : [0x1005]       <- 복귀 주소
0x7fff0ff0 : [0x7fff1020]   <- 이전 함수의 RBP 저장
낮은 주소

핵심:

  • RSP는 또 내려감
  • 같은 하나의 스택에
    • 복귀 주소
    • 이전 RBP 가 순서대로 쌓임

4. mov rbp, rsp

다음 줄:

0x2001: mov rbp, rsp

이건 스택에 뭘 쌓는 명령이 아닌 그냥 현재 RSP 값을 RBP에 복사한다.
→ 프레임의 기준점을 현재 함수의 시작점으로 갱신

즉:

RBP = RSP = 0x7fff0ff0

상태:

RIP = 0x2004
RSP = 0x7fff0ff0
RBP = 0x7fff0ff0
  • RSP = 현재 top
  • RBP = foo 프레임의 기준점

스택을 foo 입장에서 해석하면:

[RBP]     = 저장된 이전 RBP = 0x7fff1020
[RBP+8]   = 복귀 주소       = 0x1005

여기서 드디어 프레임 기준점이 생기게 된다.


5. sub rsp, 16

이제 지역 변수/임시값 공간을 확보한다.

0x2004: sub rsp, 16

뜻:

  • 스택 top을 16바이트 더 아래로 내림
  • 즉 16바이트 공간을 이 함수가 쓰겠다고 확보

동작 후:

RSP = 0x7fff0fe0
RBP = 0x7fff0ff0

상태:

RIP = 0x2008
RSP = 0x7fff0fe0
RBP = 0x7fff0ff0

스택:

높은 주소
...
0x7fff0ff8 : [0x1005]       <- 복귀 주소
0x7fff0ff0 : [0x7fff1020]   <- 이전 RBP   <- RBP
0x7fff0fe8 : [지역변수 공간]
0x7fff0fe0 : [지역변수 공간] <- RSP
낮은 주소

이제부터 foo는 이 확보한 공간을 자기 지역 변수/임시값 저장용으로 사용한다.

핵심:

  • RBP는 그대로
  • RSP만 내려감
  • RBP는 기준점, RSP는 현재 사용 중인 top

6. 지역 변수 저장

예시:

0x2008: mov DWORD PTR [rbp-4], 10
0x200F: mov DWORD PTR [rbp-8], 20

이건 각각:

  • [rbp-4] 위치에 10 저장
  • [rbp-8] 위치에 20 저장

입니다.

주소로 보면:

  • RBP = 0x7fff0ff0
  • [rbp-4] = 0x7fff0fec
  • [rbp-8] = 0x7fff0fe8

즉 지역 변수는 이런 식으로 놓일 수 있다.

스택:

높은 주소
...
0x7fff0ff8 : [0x1005]       <- 복귀 주소
0x7fff0ff0 : [0x7fff1020]   <- 이전 RBP
0x7fff0fec : [10]           <- 지역 변수 a
0x7fff0fe8 : [20]           <- 지역 변수 b
0x7fff0fe0 : [남은 임시 공간] <- RSP
낮은 주소

여기서 중요한 점:

  • 지역 변수 접근은 RSP가 아니라 RBP 기준
  • 그래서 함수 중간에 RSP가 흔들려도 [rbp-4], [rbp-8]은 안정적 (→ RBP의 존재 이유 중 하나)

7. 함수 끝: mov rsp, rbp

이제 함수를 끝내기 위한 작업에 돌입한다.

0x2016: mov rsp, rbp

현재:

RSP = 0x7fff0fe0
RBP = 0x7fff0ff0

이 명령을 수행하면:

RSP = 0x7fff0ff0

즉 지역 변수/임시값으로 잡았던 공간을 한 번에 정리한다.
→ RSP를 함수 기준점으로 보냄으로서 함수에서 사용했던 공간을 정리할 수 있다.

상태:

RIP = 0x2019
RSP = 0x7fff0ff0
RBP = 0x7fff0ff0

핵심:

  • [rbp-4], [rbp-8]에 있던 값들을 하나하나 pop하는 게 아님
  • 그냥 RSP를 프레임 기준점으로 되돌려서 그 아래 영역을 더 이상 현재 함수 공간으로 안 쓰겠다고 하는 것

8. pop rbp

다음:

0x2019: pop rbp

현재 [RSP]에는 저장해두었던 이전 RBP가 있음.

즉:

  • [0x7fff0ff0] = 0x7fff1020

pop rbp 수행:

RBP = [RSP] = 0x7fff1020
RSP = RSP + 8 = 0x7fff0ff8

상태:

RIP = 0x201A
RSP = 0x7fff0ff8
RBP = 0x7fff1020

이제 호출자 함수의 프레임 기준점이 복구되었음.


9. ret

마지막:

0x201A: ret

현재 [RSP]에는 복귀 주소가 있다.

  • [0x7fff0ff8] = 0x1005

ret 수행:

RIP = [RSP] = 0x1005
RSP = RSP + 8 = 0x7fff1000

최종 상태:

RIP = 0x1005
RSP = 0x7fff1000
RBP = 0x7fff1020

call foo 하기 전 호출자 상태로 복원되었고, 이제 0x1005: mov rax, 1부터 이어서 실행한다.

0개의 댓글