GDB Python API 입문 — Value·Type·Frame 객체 활용
GDB는 거의 모든 동작을 사용자 명령으로 제어할 수 있는 완전한 디버거 SDK입니다. 그 SDK의 핵심이 내장 Python 인터프리터. print 한 줄 매크로부터 STL pretty-printer, JIT 디버깅 지원, 자동화된 메모리 분석까지 — 모두 이 API 위에서 일어납니다.
이 시리즈는 GDB의 진짜 천장인 Python·MI·DAP·프런트엔드를 다룹니다. 첫 장은 Python API의 기본 — Value, Type, Frame, Symbol 같은 핵심 객체로 디버기 안의 데이터를 조작하는 법.
#활성화 확인
(gdb) python print("hello")hello
(gdb) python import sys; print(sys.version)3.11.5 (main, ...)GDB 빌드 시 --with-python 옵션이 켜져 있어야 합니다. 모든 배포판의 기본 패키지가 켭니다. 안 켜진 GDB면 python 명령이 에러.
내장 Python의 버전은 GDB 빌드 시점에 결정. 외부 시스템 Python의 venv·third-party 패키지는 별도 설정 필요.
(gdb) python>>> import sys>>> sys.path.insert(0, '/home/me/venv/lib/python3.11/site-packages')>>> import numpy # 이제 외부 패키지 사용 가능>>> end#실행 방법
(gdb) python print(1 + 2) # 한 줄3
(gdb) python # 여러 줄>import os>print(os.getpid())>end12345
(gdb) source my_script.py # 외부 파일.gdbinit에 자동 로드:
pythonimport syssys.path.insert(0, '/home/me/gdb-helpers')import myhelpersmyhelpers.register_all()end#핵심 객체 한 표
| 객체 | 역할 |
|---|---|
gdb.Value | 디버기의 값 (int/string/struct/포인터) |
gdb.Type | 타입 정보 (size, fields, target) |
gdb.Frame | 콜스택의 한 프레임 |
gdb.Symbol | 변수·함수 심볼 |
gdb.Block | 스코프 블록 |
gdb.Inferior | 프로세스 |
gdb.Thread | 스레드 |
gdb.Breakpoint | 브레이크포인트 |
gdb.Objfile | 로드된 ELF/공유라이브러리 |
gdb.Architecture | 아키텍처 (x86-64, ARM, …) |
gdb.Progspace | 프로그램 공간 |
#gdb.Value — 디버기의 값
가장 자주 다루는 객체. 디버기 메모리의 한 영역을 Python에서 다룰 수 있게 래핑한 핸들.
#만들기
import gdb
# 표현식 평가v = gdb.parse_and_eval("my_var")v2 = gdb.parse_and_eval("counter + 1")v3 = gdb.parse_and_eval("(int *)0x600000")
# 직접 생성n = gdb.Value(42)s = gdb.Value("hello")gdb.parse_and_eval("expr")이 핵심. GDB의 print 명령이 받는 어떤 표현식이든 받음 — C 문법 + GDB 확장 ($rax, $_thread, *((char *)0x1000)@10 등).
#멤버 접근
v = gdb.parse_and_eval("my_struct")
# 멤버x = v['x'] # struct.xy = v['y'] # struct.y
# 중첩nested = v['inner']['count']
# 포인터 derefp = gdb.parse_and_eval("ptr")target = p.dereference() # *ptr
# 배열 인덱스arr = gdb.parse_and_eval("my_array")first = arr[0] # my_array[0]slice = arr[0:10] # 10개#Python 타입으로
# 정수n = int(v)n = int(v['count'])
# 문자열 (C string)s = v['name'].string() # NUL까지s = v.string(encoding='utf-8', length=20)
# 부동 소수f = float(v['ratio'])
# byte bufferbuf = bytes(v['data'])string()은 C 스타일 문자열 (NUL terminated 또는 길이 지정). bytes()는 raw 메모리.
#산술
v = gdb.parse_and_eval("a")w = gdb.parse_and_eval("b")
result = v + w # 디버기 안에서 평가 안 됨, Python 측 산술result = v * 2
# 디버기 안에서 평가result = gdb.parse_and_eval(f"({v}) + ({w})")gdb.Value 끼리의 산술은 Python 측에서 일어남. 디버기 측 함수 호출은 gdb.parse_and_eval 안에서.
#Cast
p = gdb.parse_and_eval("(void *)0x600000")t = gdb.lookup_type("MyStruct")pp = p.cast(t.pointer()) # (MyStruct *)0x600000val = pp.dereference()print(val['field'])gdb.lookup_type("name")이 타입을 찾고, t.pointer()로 포인터 타입, .cast(t)로 변환.
#dynamic_cast (C++ vtable 기반)
base_ptr = gdb.parse_and_eval("base")derived_t = gdb.lookup_type("Derived").pointer()derived = base_ptr.dynamic_cast(derived_t) # 또는 Noneset print object on 효과의 프로그램적 버전.
#gdb.Type
t = gdb.lookup_type("MyStruct")print(t.sizeof) # 바이트 크기print(t.tag) # 태그 이름print(t.code) # gdb.TYPE_CODE_STRUCT 등
# 필드 순회for f in t.fields(): print(f.name, f.type, f.bitpos)#TYPE_CODE 카탈로그
gdb.TYPE_CODE_PTR # 포인터gdb.TYPE_CODE_ARRAY # 배열gdb.TYPE_CODE_STRUCT # structgdb.TYPE_CODE_UNIONgdb.TYPE_CODE_ENUMgdb.TYPE_CODE_TYPEDEFgdb.TYPE_CODE_FUNCgdb.TYPE_CODE_INTgdb.TYPE_CODE_FLTgdb.TYPE_CODE_BOOLgdb.TYPE_CODE_REF # T&gdb.TYPE_CODE_VOID타입 검사:
if v.type.code == gdb.TYPE_CODE_PTR: target = v.dereference()elif v.type.code == gdb.TYPE_CODE_STRUCT: for f in v.type.fields(): print(f.name, v[f.name])#typedef 풀기
t = gdb.lookup_type("size_t")print(t) # "size_t"print(t.strip_typedefs()) # "unsigned long"print(t.unqualified()) # const/volatile 제거print(t.target()) # 포인터/typedef의 basepretty-printer에서 자주 — 같은 컨테이너의 다양한 typedef 변형을 한 패턴으로 처리.
#template parameter
t = gdb.lookup_type("std::vector<int>")for i in range(t.target_type.fields().__len__()): pass
# 또는 template 인자 직접print(t.template_argument(0)) # 첫 template 인자 (예: int)std::vector<int> → template_argument(0) = int 타입. STL pretty-printer가 이걸로 원소 타입을 알아냄.
#gdb.Frame — 콜스택
frame = gdb.selected_frame() # 현재 프레임print(frame.name()) # 함수 이름 또는 Noneprint(frame.pc()) # PCprint(frame.function()) # gdb.Symbolprint(frame.find_sal()) # gdb.Symtab_and_line (file/line)#위/아래 이동
older = frame.older() # 호출자newer = frame.newer() # 피호출자순회.
f = gdb.newest_frame()depth = 0while f is not None: print(f"#{depth} {f.name() or '??'}") f = f.older() depth += 1#프레임 안의 변수
frame = gdb.selected_frame()var = frame.read_var("local_var") # 변수 readprint(var)또는 gdb.parse_and_eval이 현재 프레임 컨텍스트에서 평가.
#블록 순회
block = frame.block()while block: for sym in block: if sym.is_variable or sym.is_argument: try: value = sym.value(frame) print(f"{sym.name} = {value}") except gdb.error: pass # optimized out block = block.superblock스코프 nested 전부 — 함수 인자, 지역 변수, 정적, 전역. info locals/info args의 Python 버전.
#gdb.Inferior
inferior = gdb.selected_inferior()print(inferior.pid)print(inferior.num) # GDB의 인페리어 번호
# 모든 스레드for thread in inferior.threads(): print(thread.num, thread.name, thread.is_stopped())
# 메모리 read / writebuf = inferior.read_memory(0x600000, 100)print(bytes(buf).hex())
inferior.write_memory(0x600000, b'\x01\x02\x03')
# 메모리 검색found = inferior.search_memory(0x400000, 0x100000, b'hello')if found: print(f"found at {found:#x}")read_memory/write_memory가 디버기 메모리에 직접 접근. pretty-printer 같은 곳에서 struct 안의 raw 데이터를 빠르게 가져올 때.
#모든 인페리어
for inf in gdb.inferiors(): print(inf.num, inf.pid)set follow-fork-mode child + detach-on-fork off로 fork된 자식까지 추적할 때 여러 인페리어가 있음.
#gdb.Symbol
# 이름으로 lookupsym, is_field = gdb.lookup_symbol("main")print(sym.name, sym.type, sym.value())
# 전역 심볼sym = gdb.lookup_global_symbol("g_config")#속성
sym.name # 이름sym.linkage_name # C++ mangled namesym.print_name # 사용자 표시용 (demangled)sym.type # gdb.Typesym.symtab # 정의된 소스 파일sym.line # 정의 줄sym.addr_class # gdb.SYMBOL_LOC_REGISTER, ...
sym.is_argumentsym.is_functionsym.is_variablesym.is_constantsym.is_valid()#addr_class
gdb.SYMBOL_LOC_STATIC # 정적 (전역, .data/.bss)gdb.SYMBOL_LOC_REGISTER # 레지스터gdb.SYMBOL_LOC_ARG # 함수 인자gdb.SYMBOL_LOC_REF_ARG # reference 인자gdb.SYMBOL_LOC_LOCAL # 지역 변수gdb.SYMBOL_LOC_TYPEDEFgdb.SYMBOL_LOC_BLOCK # 함수gdb.SYMBOL_LOC_CONST # 상수gdb.SYMBOL_LOC_OPTIMIZED_OUTgdb.SYMBOL_LOC_COMPUTED # 위치가 DWARF expressionSYMBOL_LOC_OPTIMIZED_OUT이면 <optimized out>. pretty-printer에서 변수를 못 읽는 상황을 우아하게 처리.
#gdb.execute — 임의 명령
output = gdb.execute("info threads", to_string=True)print(output)
gdb.execute("break main")gdb.execute("continue")to_string=True로 출력을 캡처. 출력 분석이 필요한 자동화에 핵심.
#묶어 보기
def list_all_breakpoints(): out = gdb.execute("info breakpoints", to_string=True) for line in out.splitlines(): if line.strip().startswith(tuple("0123456789")): print(line)GDB 명령 출력을 Python에서 후처리.
#단순 출력
gdb.write("hello\n") # stdoutgdb.write("error\n", gdb.STDERR)gdb.flush()print()도 동작하지만 gdb.write가 GDB 출력 시스템과 매끄러움 (TUI 모드 등).
#Exception
try: v = gdb.parse_and_eval("nonexistent_var")except gdb.error as e: gdb.write(f"error: {e}\n")gdb.error가 모든 GDB API 오류의 base.
#무한 정지 방지
def safe_eval(expr): try: return gdb.parse_and_eval(expr) except gdb.error: return Nonepretty-printer에서 없는 멤버 접근이 흔하므로 wrapper로 보호.
#디버깅 — print-stack
(gdb) set python print-stack full이후 Python 예외의 전체 traceback이 콘솔에 출력. 자체 스크립트 디버깅에 필수.
(gdb) python my_buggy_code()Traceback (most recent call last): File "<string>", line 1, in <module> File "/path/to/script.py", line 42, in my_buggy_code x = v['nonexistent']gdb.error: There is no member named nonexistent.#한 예 — 짧은 진단 명령
import gdb
class WhereAmI(gdb.Command): """현재 위치 종합 정보."""
def __init__(self): super().__init__("whereami", gdb.COMMAND_USER)
def invoke(self, arg, from_tty): try: frame = gdb.selected_frame() except gdb.error: gdb.write("no frame (process not running?)\n") return
pc = int(frame.pc()) name = frame.name() or '??' sal = frame.find_sal() file = sal.symtab.filename if sal.symtab else '??' line = sal.line
thread = gdb.selected_thread() tid = thread.num
gdb.write(f"thread {tid}, PC = {pc:#x}\n") gdb.write(f"function: {name}\n") gdb.write(f"location: {file}:{line}\n")
# 인자 block = frame.block() gdb.write("args:\n") for sym in block: if sym.is_argument: try: val = sym.value(frame) gdb.write(f" {sym.name} = {val}\n") except gdb.error: gdb.write(f" {sym.name} = <optimized out>\n")
WhereAmI()(gdb) source whereami.py → (gdb) whereami → 한 화면에 모든 정보. 일상 디버깅에 매우 유용.
#메모리 검색 — 한 예
import gdb
class FindStr(gdb.Command): """프로세스 메모리에서 문자열 검색."""
def __init__(self): super().__init__("findstr", gdb.COMMAND_USER)
def invoke(self, arg, from_tty): inf = gdb.selected_inferior() pattern = arg.encode() # vmap에서 모든 R/W 영역 (info proc mappings 필요) mappings = gdb.execute("info proc mappings", to_string=True) for line in mappings.splitlines(): parts = line.split() if len(parts) < 5: continue try: start = int(parts[0], 16) end = int(parts[1], 16) except ValueError: continue size = end - start if size > 100*1024*1024: continue # skip huge try: found = inf.search_memory(start, size, pattern) if found: gdb.write(f"found at {found:#x}\n") except gdb.error: pass
FindStr()(gdb) findstr passwordfound at 0x7ffff7d2c8a0found at 0x55555556a280#정리
- GDB 내장 Python으로 디버기 데이터에 접근.
- 핵심 객체: Value, Type, Frame, Symbol, Inferior, Thread.
gdb.parse_and_eval(expr)이 만능 진입점.gdb.execute(..., to_string=True)로 명령 출력 캡처.inferior.read_memory/write_memory/search_memory로 raw 메모리.- 예외는
gdb.error.set python print-stack full로 디버깅. - 짧은 명령 한 두 개로도 일상 디버깅 효율이 크게 늘어남.
#다음 장 예고
Ch 2 — 커스텀 명령, Convenience function, Event hook, Breakpoint subclass.
#관련 항목
- Ch 2: 커스텀 명령과 이벤트
- GDB Python API 공식 문서
- Ch 6: 프런트엔드
set python print-stack full— 스크립트 디버깅
관련 글
GDB FrameDecorator·Unwinder — 콜스택 가공과 JIT 지원
Frame filter, FrameDecorator로 콜스택 변형, custom unwinder로 JIT 코드 추적.
GDB Pretty-Printer 심화 — STL·커스텀 타입 시각화
to_string / children / display_hint, MI 출력, auto-load, libstdc++ printer 분석.
GDB 커스텀 명령·Convenience Function·Event 훅·Breakpoint
사용자 정의 명령, $name(...) 함수, stop hook, 자동 BP 액션 패턴.
이 글을 참조하는 글 (5)
- 포스트모템 자동화 — debuginfod·Minidump 파이프라인— Postmortem Debugging
- GDB 프런트엔드 비교 — TUI·cgdb·dashboard·gef·IDE— GDB Extension and IDE
- GDB 커스텀 명령·Convenience Function·Event 훅·Breakpoint— GDB Extension and IDE
- GDB·LLDB Python 스크립팅 — Pretty-Printer·Custom Command— GDB and LLDB
- GDB vs LLDB 분석 — 두 디버거의 설치·차이·선택 기준— GDB and LLDB