디버거(Debugger) 브레이크 포인트(Break Point)의 구현 원리
글 작성자: cjwoov
반응형
0. 요약
디버거의 소프트웨어 브레이크 포인트는 메모리 패칭(Memory Patching)과 CPU의 예외 처리 메커니즘을 이용해 구현합니다.
- 유저가 특정 라인에 브레이크를 걸면, 해당 주소의 원래 기계어 1바이트를 디버거 메모리에 백업하고, 그 자리를 0xCC (INT 3) 명령어로 덮어씁니다.
- 타겟 프로세스의 스레드(CPU)가 실행되다 0xCC를 만나면 디버그 익셉션(Trap)이 발생하고, OS는 스레드를 블로킹한 뒤 디버거에게 이벤트를 전달합니다.
- 디버거는 이때 CPU 레지스터(Context)와 메모리를 읽어 유저에게 보여줍니다.
- 유저가 실행을 재개(F5)하면, 덮어썼던 0xCC를 백업해 둔 원래 기계어로 복원합니다. 그리고 CPU의 명령어 포인터(RIP)를 1바이트 감소시켜 원래 명령어를 정상적으로 실행하도록 한 뒤, 스레드 블로킹을 해제합니다.
1. 핵심 개념: 0xCC (INT 3)
C++ 코드를 컴파일하면 CPU가 읽을 수 있는 기계어(숫자)로 변합니다.
예를 들어 int a = 1; 이라는 코드는 메모리에 B8 01 00 00 00 (어셈블리어로는 mov eax, 1)처럼 올라갑니다.
여기서 디버거가 사용하는 궁극의 마법 주문이 딱 하나 있습니다.
바로 0xCC 라는 1바이트짜리 숫자입니다. 어셈블리어로는 INT 3 (Interrupt 3)라고 부릅니다.
CPU는 코드를 주르륵 읽고 실행하다가 메모리에서 0xCC를 마주치는 순간,
"헉! 함정이다! 모든 동작을 멈추고 OS에게 보고해!"라며 얼어붙습니다.
2. 브레이크 포인트의 동작 순서 (기차 길 비유)
CPU를 '기차', 메모리에 깔린 코드를 '기차 선로', 디버거(Visual Studio)를 '관제탑'이라고 상상해 보십시오.
Step 1. 함정 설치 (F9 키를 눌렀을 때)
- 유저가 150번 줄에 브레이크 포인트(빨간 점)를 찍습니다.
- 디버거는 150번 줄의 메모리 주소를 찾아갑니다.
- 거기에 원래 깔려있던 기차 선로(원래 기계어, 예: B8)를 뜯어서 자기 주머니에 백업해 둡니다.
- 그리고 그 자리에 0xCC (정지 표지판)를 몰래 끼워 넣습니다. (메모리 덮어쓰기)
Step 2. 멈춤과 보고 (기차가 표지판을 밟았을 때)
- 프로그램이 쌩쌩 돌아갑니다. 기차(CPU)가 선로를 따라 150번 줄까지 옵니다.
- 기차가 0xCC를 밟는 순간, CPU는 OS에게 "디버그 예외 발생(Trap)!"을 외치고 기차를 급정거시킵니다.
- OS는 이 기차를 멈춰 세우고, 관제탑(디버거)에게 "야, 네가 설치한 함정에 기차 걸렸어"라고 알려줍니다.
- 이때 화면에 노란색 화살표가 멈춰있고, 우리는 변수 값(기차 안의 승객들)을 구경할 수 있습니다.
Step 3. 원상 복구와 재출발 (F5 키를 눌렀을 때)
여기가 제일 중요합니다. 기차를 다시 출발시키려면 어떻게 해야 할까요?
- 디버거는 아까 끼워 넣었던 0xCC를 뽑아내고, 주머니에 숨겨뒀던 원래 선로(B8)를 다시 깔아줍니다.
- 근데 기차(CPU의 현재 위치를 나타내는 RIP 레지스터)는 이미 0xCC를 밟고 1칸 앞으로 가 있는 상태입니다. 이대로 출발하면 원래 코드를 건너뛰게 됩니다.
- 그래서 디버거는 기차를 뒤로 딱 1칸(1바이트) 후진시킵니다. (RIP = RIP - 1)
- 그리고 "자, 이제 다시 원래 선로 밟고 출발해(F5)!"라고 명령합니다.
이것이 소프트웨어 브레이크 포인트의 완벽한 쌩얼입니다. 어셈블리어를 몰라도 흐름이 완벽히 이해되시죠?
3. 추가 지식: 1바이트(0xCC)여야만 하는 이유
면접관이 꼬리 질문으로 "왜 굳이 INT 3는 1바이트입니까?"라고 물어볼 수 있습니다.
- 만약 정지 표지판이 2바이트 이상이라면?
- 원래 선로가 1바이트짜리 짧은 코드였을 경우, 표지판을 박아넣다가 그다음 줄에 있는 무고한 선로(코드)까지 덮어써서 파괴해 버리는 대참사가 일어납니다. 그래서 무조건 가장 작은 단위인 1바이트여야 완벽한 치환이 가능합니다.
반응형
'Development > System' 카테고리의 다른 글
| Windows DPC(Deferred Procedure Call), ISR(Interrupt Service Routine) (0) | 2026.02.27 |
|---|---|
| Windows 64-bit Calling Convention (호출 규약) (0) | 2026.02.27 |
| 컨텍스트 스위칭(Context Switching)에 대한 고찰, 정리 (0) | 2025.11.06 |
| [윈도우즈 시스템 프로그래밍] 3. 프로그램 구현 관점에서의 32비트와 64비트 & 오류의 확인 (0) | 2019.06.30 |
| [윈도우즈 시스템 프로그래밍] 3. WIN32 vs WIN64 (0) | 2019.06.27 |
댓글
이 글 공유하기
다른 글
-
Windows DPC(Deferred Procedure Call), ISR(Interrupt Service Routine)
Windows DPC(Deferred Procedure Call), ISR(Interrupt Service Routine)
2026.02.27 -
Windows 64-bit Calling Convention (호출 규약)
Windows 64-bit Calling Convention (호출 규약)
2026.02.27 -
컨텍스트 스위칭(Context Switching)에 대한 고찰, 정리
컨텍스트 스위칭(Context Switching)에 대한 고찰, 정리
2025.11.06 -
[윈도우즈 시스템 프로그래밍] 3. 프로그램 구현 관점에서의 32비트와 64비트 & 오류의 확인
[윈도우즈 시스템 프로그래밍] 3. 프로그램 구현 관점에서의 32비트와 64비트 & 오류의 확인
2019.06.30