관리 메뉴

맨땅에 코딩

CPU 동작 원리 핵심 정리(QEMU·플러그인 관점) 본문

낙서장

CPU 동작 원리 핵심 정리(QEMU·플러그인 관점)

나는 푸딩 2025. 10. 26. 19:32

분명 컴퓨터구조 시간에 배웠던 것 같은데, 하나도 기억이 나지 않아서 정리해봅니다.

 

1. CPU의 역할

CPU는 명령어를 하나씩 읽고 실행하는 "두뇌"이다. 

 

기본 반복 구조는 다음과 같다.

1. Fetch → 메모리에서 명령어 가져오기
2. Decode → 어떤 연산인지 해석하기
3. Execute → 실제 연산 수행하기 (레지스터/메모리 변경)
4. Repeat → 다음 명령어로 이동

 

이를 명령어 사이클이라고 하는데,

QEMU는 이 사이클을 소프트웨어적으로 시뮬레이션 하는 것이다.

 

2. 레지스터

CPU 내부의 초고속 저장공간으로
메모리보다 훨씬 빠르고, 명령어 대부분이 레지스터 간 연산이다.
이름 역할
RIP (Instruction Pointer) 다음에 실행할 명령어의 주소 (Program Counter)
RSP (Stack Pointer) 현재 스택의 최상단 주소
RBP (Base Pointer) 현재 함수 프레임의 기준 주소
RAX, RBX, RCX, RDX 범용 레지스터 (데이터 연산용)
FLAGS (EFLAGS/RFLAGS) 연산 결과 상태(Zero, Carry 등) 저장
CR3 (Page Table Base) 현재 페이지 테이블의 물리주소 (메모리 매핑 기준)

 

플러그인은 이 레지스터 값을 "명령어 실행 시점마다" 읽어서 로그로 남길 수 있다.

[Exec] RIP=0x401000 | RAX=0x00000001 | RBX=0x00000002

 

3. 명령어의 실제 예시

CPU는 다음과 같은 기계어 명령을 실행한다.

어셈블리 의미 결과
mov eax, 5 5를 레지스터 eax에 넣음 eax = 5
add eax, ebx eax = eax + ebx eax 값이 바뀜
cmp eax, ebx 두 값 비교 FLAGS 업데이트
jmp 0x401000 특정 주소로 점프 RIP 바뀜
call 0x404000 함수 호출 스택에 리턴주소 푸시
ret 함수 리턴 스택에서 주소 팝

 

플러그인은 이런 "명령어 단위 이벤트"가 일어날 때마다 콜백을 받게 된다.

즉, "어떤 주소에서 어떤 명령이 실행됐는가"를 추적할 수 있는 구조다.

 

4. 메모리 접근(Memory Access)

CPU가 데이터를 쓸 때는 항상 다음 단계로 작동한다.

레지스터 → 가상 주소 → 물리 주소 → 실제 메모리

 

이것을 MMU가 담당하고,

CR3 레지스터에 있는 페이지 테이블을 기준으로 주소를 변환한다.

mov [0x7ffdf000], eax

 

위 한 줄을 실행하면,

  • CPU는 0x7ffdf000 이라는 가상 주소를 받음
  • MMU가 페이지 테이블을 보고 물리주소(예: 0x12345000)로 변환
  • 실제 메모리에 쓰기(write) 발생

QEMU 플러그인은 이때 "메모리 접근 이벤트"를 잡을 수 있다.

[MEM_WRITE] VA=0x7ffdf000 → PA=0x12345000 | Size=4

 

5. 인터럽트와 예외

CPU가 원래 실행하던 일을 멈추고 "다른 중요한 일"을 처리하는 순간
이벤트 설명
타이머 인터럽트 일정 주기로 CPU 깨워 스케줄러 동작
I/O 인터럽트 키보드 입력, 네트워크 수신 등
시스템콜 인터럽트 프로그램이 커널 기능을 요청할 때 (int 0x80, syscall)
페이지 폴트 예외 없는 페이지 접근 시 발생

 

플러그인은 이런 이벤트 발생 시점에 "IRQ 번호, 발생 주소, 핸들러 진입 주소"를 기록함

[IRQ] #14 Page Fault @ 0xfffff80212345678

 

6. 함수 호출 스택

 함수가 호출될 때, CPU는 현재 상태를 스택에 저장
call func_A  →  [리턴주소] push → RIP 이동
ret          →  [리턴주소] pop  → 복귀

 

7. 전체 CPU 실행 사이클 흐름

while (1) {
    Fetch:   instr = Memory[RIP];
    Decode:  type = parse(instr);
    Execute: do_operation(instr);
    Update:  RIP += instr.length;
}

 

QEMU는 이 과정을 "TCG(Tiny Code Generator)"로 소프트웨어화해서, 실제 CPU처럼 동작하게 만든다.

 

8. QEMU와 CPU의 연결

게스트 OS  →  명령어 실행
     ↓
QEMU (TCG) →  명령어를 해석·에뮬레이트
     ↓
플러그인   →  콜백 실행 (on_exec)

 

즉, 실제 CPU는 전자회로로 명령을 처리하지만, QEMU는 C언어로 그 과정을 재현한다.

우리가 만들고자 하는 플러그인은 "그 시점의 상태를 옆에서 기록"하는 구조이다.