[PintOS] GDB Debugging Tool 사용 방법

GDB 디버깅하기

 

운영체제를 직접 구현하는 PintOS 프로젝트는 많은 시행착오를 겪게 됩니다. 간단한 코드 수정 하나에도 KERNEL PANIC🔥.. 문구를 만나거나 아무런 출력 없이 프로세스가 종료되는 경험을 하게 됩니다. 이럴때 가장 막막한 건, “대체 어디서 잘못된 걸까?”를 알 수 없다는 점입니다.

 

이럴 때 사용할 디버깅 도구는 GDB(GNU Debugger) 입니다. 저 또한 GUI 기반의 디버깅에 익숙하여 CLI 기반의 디버깅 툴에 익숙해지기 쉽지 않았는데요. 단순한 printf 문 로그 출력만으로 확인하기 어려운 메모리 접근 에러나 페이지 폴트 같은 문제의 원인을 정확하게 추적하는데 많은 도움을 받았습니다!

 

GDB를 이용해 PintOS 테스트 케이스를 디버깅 하는 방법을 차근차근 정리해보았습니다. 앞으로의 PintOS 여정에 많은 도움이 되시길 바랍니다.

 

GDB 실행 방법

  1. pintos-kaist 폴더에서 source ./activate
  2. 프로젝트 디렉토리로 이동 후 make 실행 : cd pintos-kaist/threads
  3. 빌드 디렉토리 진입 : cd build
  4. GDB 디버깅용 테스트 실행 : pintos —gdb — -q run \[테스트명\]
    • Project 1 의 경우 테스트케이스 c 파일의 파일명만 넣어도 문제 없이 돌아간다
    • 예) pintos --gdb -- -q run alarm-multiple
    • roject 2 의 경우 테스트 실행 명령이 복잡하므로 make tests/userprog/exec-once.result 으로 개별 테스트를 먼저 실행하면 내부 실행 명령을 확인할 수 있다
    • 개별 테스트 시 출력 예시 :
    • c pintos -v -k -T 60 -m 20 --fs-disk=10 -p tests/userprog/exec-once:exec-once -p tests/userprog/child-simple:child-simple -- -q -f run exec-once < /dev/null 2>
    • 👉 이를 아래처럼 수정해서 사용 :
      • pintos 부터 < /dev/null 2> 전까지 복사
      • -v -k -T 60 옵션을 제거한 후, —gdb 옵션을 넣어서 테스트를 돌리면 된다
    • pintos --gdb 60 -m 20 --fs-disk=10 -p tests/userprog/exec-once:exec-once -p tests/userprog/child-simple:child-simple -- -q -f run exec-once
  5. qemu-system-x86_64: warning: TCG doesn't support requested feature:
    • 해당 문구 출력 후 터미널이 멈춘다면 정상입니다. 이 시점에서 GDB가 QEMU와 연결되기를 기다리는 중입니다.
  6. 새로운 터미널 창 열기 (2번)
  7. cd pintos-kaist/threads/build 안에서 kernel.o 생성 확인
  8. gdb kernel.o 로 gdb 접속
  9. target remote localhost:1234 로 연결
  10. breakpoint 설정 : break thread.c:99 👉 thread.c 라는 파일의 99번째 줄에 브레이크포인트 설정
  11. continue
    • QEMU가 실행되면서 테스트 출력이 1번 터미널에 출력되고
    • 2번 터미널에는 gdb 옵션을 추가하며 디버깅한다

 

GDB 디버깅 옵션

GDB 명령어 설명
break <위치> 또는 b 해당 위치(함수명, 라인 번호, 주소 등)에 중단점(Breakpoint) 설정
info breakpoints 또는 i b 설정된 모든 중단점 정보 확인
run 또는 r 프로그램 실행 시작 (breakpoint까지 실행됨)
continue 또는 c 현재 중단점에서 계속 실행
stepi 또는 si 어셈블리 한 줄씩(step into) 실행
nexti 또는 ni 어셈블리 한 줄씩(step over) 실행 (함수 호출은 내부로 들어가지 않음)
x/i <주소> 해당 주소의 어셈블리 명령어 확인 (x/i $rip 등)
x/s <주소> 해당 주소의 문자열(string) 출력
print <변수> 또는 p 현재 스코프에서 변수의 값 출력
print/x <레지스터> 변수 또는 레지스터의 값을 16진수로 출력 (print/x $rax 등)
info registers 모든 레지스터의 현재 값 확인
bt (backtrace) 함수 호출 스택(Backtrace) 출력 – 어디서 왔는지 추적
set <변수>=값 변수나 레지스터 값을 수동으로 변경
delete <번호> 특정 breakpoint 삭제
disable <번호> 특정 breakpoint 비활성화
enable <번호> 특정 breakpoint 다시 활성화
layout asm 터미널을 어셈블리 보기로 전환 (GDB TUI 모드에서 사용)