| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 코틀린
- 보안
- 학부연구생
- 후기
- CPU
- 개발
- 정보보안
- 소프트웨어
- 프로젝트
- Kotlin
- IT
- 산학
- 플러그인
- 앱
- Android Studio
- 악성코드
- 정보보호
- 보안제품개발
- 해킹
- 코딩
- BOB
- 애플리케이션
- 프론트엔드
- 프로그래밍
- bob 14기
- 앱 개발
- React Native
- 소프트웨어학과
- 화이트햇스쿨
- QEmu
- Today
- Total
맨땅에 코딩
인터럽트와 예외(QEMU·플러그인 관점) 본문
1. 기본 개념
인터럽트(Interrupt)는 CPU에게 “지금 당장 처리해야 할 일이 생겼다!” 라고 알리는 신호이고,
예외(Exception)는 프로그램 실행 중 CPU가 오류를 감지했을 때 발생하는 이벤트다.
공통점: CPU가 현재 실행 중인 명령어를 "일시 중단"하고, 정해진 "핸들러 함수"로 점프한다.
2. 인터럽트의 종류
| 구분 | 이름 | 예시 | 발생 주체 |
| 하드웨어 인터럽트 | Hardware Interrupt | 키보드 입력, 네트워크 패킷 도착 | 외부 장치 |
| 소프트웨어 인터럽트 | Software Interrupt | int 0x80, syscall (시스템콜) | 프로그램 |
| 예외(Exception) | Fault / Trap / Abort | 0으로 나누기, 페이지 폴트 | CPU 내부 |
- 하드웨어: 외부 장치가 "CPU 나 좀 봐달라."
- 소프트웨어: 프로그램이 "OS, 이거 대신 처리해달라."
- 예외: CPU가 '야, 이건 오류다."
3. 인터럽트 발생 시 흐름
CPU는 인터럽트를 받으면 다음 순서로 동작한다.
① 현재 실행 중인 명령어 일시 중단
② 현재 RIP(명령어 위치)와 FLAGS를 스택에 저장
③ IDT(Interrupt Descriptor Table)에서 해당 번호의 핸들러 주소 찾음
④ RIP을 핸들러 주소로 교체 (점프)
⑤ 핸들러 실행 (OS 코드)
⑥ 완료 후 iret 명령으로 원래 위치로 복귀
그래서 인터럽트는 "함수 호출"처럼 보이지만, OS 입장에서는 "예상치 못한 강제 분기"이다.
4. 인터럽트 벡터 테이블 (IDT)
IDT는 각 인터럽트 번호(0~255)에 대한 "핸들러 주소"를 저장하는 테이블이다.
| 번호 | 이름 | 설명 |
| 0 | Divide Error | 0으로 나눔 |
| 6 | Invalid Opcode | 존재하지 않는 명령어 |
| 13 | General Protection Fault | 권한 오류 |
| 14 | Page Fault | 페이지 매핑 없음 |
| 32~47 | 하드웨어 IRQ | 타이머, 키보드, 마우스 등 |
| 128 | System Call (int 0x80) | 유저모드 → 커널모드 진입 |
QEMU 플러그인은 이 번호를 그대로 받을 수 있다.
[IRQ] #14 PAGE_FAULT @0xFFFFF80200001000
5. 하드웨어 인터럽트(IRQ)
외부 장치(디스크, 키보드 등)가 CPU에게 "작업 완료" 신호를 보내는 것이다.
하드웨어 인터럽트는 “PIC(Programmable Interrupt Controller)”나 “APIC(Advanced PIC)”를 통해 전달된다.
타이머 칩 → APIC → CPU(IRQ#0)
네트워크 카드 → APIC → CPU(IRQ#11)
CPU는 해당 IRQ를 받으면 IDT의 0x20~0x2F 영역을 찾아간다.
플러그인으로 이 이벤트를 잡으면
- CPU가 언제 외부 이벤트로 깨어났는지
- IRQ 발생 빈도, 지연 시간, 핸들러 처리 시간을 분석할 수 있다.
6. 소프트웨어 인터럽트(System Call)
프로그램이 운영체제 커널 기능을 요청할 때 CPU 명령어 int 0x80 또는 syscall을 실행한다.
User Mode 프로그램
↓
int 0x80 / syscall
↓
Ring 0 커널 코드로 진입
↓
시스템 콜 처리 후 복귀
이 때, 권한이 Ring 3 → Ring 0 으로 바뀐다.
유저 모드 → 커널 모드 전환 시점 추적 가능하다.
플러그인은 이것을 IRQ #128 (혹은 Syscall 이벤트)으로 감지할 수 있다.
7. 예외(Exception)
CPU가 "이건 명령 실행 중 오류다"라고 판단했을 때 발생한다.
| 예외 번호 | 이름 | 원인 |
| 0 | Divide Error | 0으로 나누기 |
| 6 | Invalid Opcode | 잘못된 명령어 실행 |
| 13 | General Protection | 접근권한 위반 |
| 14 | Page Fault | 메모리 매핑 안 됨 |
디버거가 가장 중요하게 보는 이벤트로, 실행 중 크래시나 접근 위반을 탐지할 수 있다.
플러그인은 이것을 로그로 남길 수 있다.
[EXC] Page Fault @ 0xfffff80212345678 | Reason: Write to non-present page
8. 인터럽트 처리 후 복귀
핸들러가 끝나면 iretq 명령으로 복귀:
pop RIP
pop FLAGS
resume previous instruction
이것이 OS가 "원래 하던 일로 돌아가는" 과정이다.
→ QEMU 플러그인에서 이 순간을 감지하면 "스케줄링 사이클"을 재구성할 수 있다.
9. QEMU에서 인터럽트 시뮬레이션 구조
QEMU는 하드웨어 인터럽트도 가상화해서 처리함:
[가상 디바이스] ─→ qemu_irq_pulse()
└→ CPU 내부 irq_pending flag set
→ QEMU main loop가 이 플래그를 확인
→ guest CPU에서 handle_interrupt() 실행
즉, 실제 하드웨어 없이도 "IRQ 이벤트"가 일어나는 것처럼 흉내낸다.
우리가 해야할 일은 플러그인에서 이때 호출되는 "IRQ 핸들러 진입 시점"을 hook → 로그 남기기 이다.
10. QEMU 플러그인 예시(IRQ 추적)
static void irq_cb(qemu_plugin_id_t id, unsigned int vcpu_index, int irq_num)
{
printf("[IRQ] vCPU=%u | IRQ=%d | Timestamp=%ld\n",
vcpu_index, irq_num, clock());
}
void qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, int argc, char **argv)
{
qemu_plugin_register_vcpu_interrupt_cb(id, irq_cb);
}
위와 같이 콜백을 등록하면, 게스트 OS에서 발생하는 모든 인터럽트 이벤트가 터미널에 찍힌다.
11. 인터럽트 타임라인 시각화 예시
Time →
│
├── IRQ#0 (Timer Tick) → 스케줄러 실행
├── IRQ#14 (Page Fault) → MMU 핸들러 진입
├── IRQ#11 (Network RX) → 패킷 처리
├── IRQ#0 (Timer Tick) → 다음 스레드로 전환
│
└── ...
→ 이런 식으로 CPU가 외부 신호로 얼마나 자주 context switch 하는지 분석이 가능하다.
