디버거 속성

디버거 속성

GDB나 WinDbg 같은 서드파티 디버거를 쓰다 보면 값이 그냥 숫자나 메모리 그대로 보여 답답할 때가 있어요. Rust는 그래서 디버깅 경험을 개선하기 위한 여러 속성을 제공해요.

출처: Rust Reference

본문

debugger_visualizer 속성

debugger_visualizer 속성디버거 시각화 파일(debugger visualizer file) 을 디버그 정보에 임베드하기 위해 사용해요. 이렇게 하면 값을 표시할 때 디버거 경험이 개선돼요.

#![debugger_visualizer(natvis_file = "Example.natvis")]
#![debugger_visualizer(gdb_script_file = "example.py")]

이 속성은 입력을 지정하기 위해 MetaListNameValueStr 문법을 사용해요. 다음 키 중 하나를 반드시 지정해야 해요.

  • natvis_file
  • gdb_script_file

debugger_visualizer 속성은 모듈 또는 크레이트 루트에만 적용할 수 있어요. 그리고 한 항목에 몇 번이든 사용할 수 있는데, 지정된 모든 시각화 파일이 로드돼요.

Natvis와 함께 사용하기

Natvis는 Microsoft 디버거(Visual Studio, WinDbg 같은)를 위한 XML 기반 프레임워크로, 선언적 규칙을 사용해 타입의 표시 방식을 사용자 정의해요. Natvis 형식에 대한 자세한 내용은 Microsoft의 Natvis 문서를 참고하면 돼요.

이 속성은 -windows-msvc 타깃에서만 Natvis 파일을 임베드하는 것을 지원해요. Natvis 파일의 경로는 natvis_file 키로 지정하는데, 소스 파일에 상대적인 경로예요.

#![debugger_visualizer(natvis_file = "Rectangle.natvis")]

struct FancyRect {
    x: f32,
    y: f32,
    dx: f32,
    dy: f32,
}

fn main() {
    let fancy_rect = FancyRect { x: 10.0, y: 10.0, dx: 5.0, dy: 5.0 };
    println!("set breakpoint here");
}

Rectangle.natvis는 다음과 같은 내용을 담아요.

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <Type Name="foo::FancyRect">
      <DisplayString>({x},{y}) + ({dx}, {dy})</DisplayString>
      <Expand>
        <Synthetic Name="LowerLeft">
          <DisplayString>({x}, {y})</DisplayString>
        </Synthetic>
        <Synthetic Name="UpperLeft">
          <DisplayString>({x}, {y + dy})</DisplayString>
        </Synthetic>
        <Synthetic Name="UpperRight">
          <DisplayString>({x + dx}, {y + dy})</DisplayString>
        </Synthetic>
        <Synthetic Name="LowerRight">
          <DisplayString>({x + dx}, {y})</DisplayString>
        </Synthetic>
      </Expand>
    </Type>
</AutoVisualizer>

WinDbg에서 보면 fancy_rect 변수는 다음과 같이 표시돼요.

> Variables:
  > fancy_rect: (10.0, 10.0) + (5.0, 5.0)
    > LowerLeft: (10.0, 10.0)
    > UpperLeft: (10.0, 15.0)
    > UpperRight: (15.0, 15.0)
    > LowerRight: (15.0, 10.0)

GDB와 함께 사용하기

GDBpretty printer라고 불리는 구조화된 Python 스크립트의 사용을 지원해요. 이 스크립트는 타입이 디버거 뷰에서 어떻게 시각화될지를 설명해요. pretty printer에 대한 자세한 내용은 GDB의 pretty printing 문서를 참고하면 돼요.

여기서 주의할 점이 있어요. 임베드된 pretty printer는 GDB에서 바이너리를 디버깅할 때 자동으로 로드되지 않아요. 자동 로드를 활성화하는 방법은 두 가지가 있어요.

  1. GDB에 추가 인자를 주고 auto-load safe path에 디렉터리나 바이너리를 명시적으로 추가하기: gdb -iex "add-auto-load-safe-path safe-path path/to/binary" path/to/binary (자세한 내용은 GDB의 auto-loading 문서 참조)
  2. $HOME/.config/gdb 아래에 gdbinit 파일을 만들어서(디렉터리가 없으면 만들어야 해요), 그 파일에 add-auto-load-safe-path path/to/binary 줄을 추가하기

이 스크립트들은 gdb_script_file 키로 임베드되는데, 이것도 소스 파일에 상대적인 경로예요.

#![debugger_visualizer(gdb_script_file = "printer.py")]

struct Person {
    name: String,
    age: i32,
}

fn main() {
    let bob = Person { name: String::from("Bob"), age: 10 };
    println!("set breakpoint here");
}

printer.py는 다음과 같은 내용을 담아요.

import gdb

class PersonPrinter:
    "Print a Person"

    def __init__(self, val):
        self.val = val
        self.name = val["name"]
        self.age = int(val["age"])

    def to_string(self):
        return "{} is {} years old.".format(self.name, self.age)

def lookup(val):
    lookup_tag = val.type.tag
    if lookup_tag is None:
        return None
    if "foo::Person" == lookup_tag:
        return PersonPrinter(val)

    return None

gdb.current_objfile().pretty_printers.append(lookup)

크레이트의 디버그 실행 파일이 GDB에 전달되면, print bob은 다음을 표시해요.

"Bob" is 10 years old.

collapse_debuginfo 속성

collapse_debuginfo 속성은 이 매크로를 호출하는 코드의 debuginfo를 생성할 때, 매크로 정의부의 코드 위치를 매크로 호출부(call site)와 연관된 단일 위치로 접을지 제어해요.

#![allow(unused)]
fn main() {
#[collapse_debuginfo(yes)]
macro_rules! example {
    () => {
        println!("hello!");
    };
}
}

디버거를 사용할 때 example 매크로를 호출하는 것은 마치 함수를 호출하는 것처럼 보일 수 있어요. 즉 호출 지점으로 스텝(step)하면 펼쳐진 코드 대신 매크로 호출이 표시될 수 있어요.

이 속성의 문법은 다음과 같아요.

CollapseDebuginfoAttribute → collapse_debuginfo ( CollapseDebuginfoOption )

CollapseDebuginfoOption →
      yes
    | no
    | external

collapse_debuginfo 속성은 macro_rules 정의에만 적용할 수 있어요. 그리고 매크로에 딱 한 번만 사용할 수 있어요.

이 속성이 받는 옵션을 볼게요.

  • #[collapse_debuginfo(yes)] — debuginfo의 코드 위치가 접혀요(collapse).
  • #[collapse_debuginfo(no)] — debuginfo의 코드 위치가 접히지 않아요.
  • #[collapse_debuginfo(external)] — 매크로가 다른 크레이트에서 왔을 때만 debuginfo의 코드 위치가 접혀요.

이 속성이 없는 매크로의 기본 동작은 external이에요. 단 내장 매크로(built-in macro) 는 예외로, 기본값이 yes예요.

참고로, rustc에는 기본 동작과 어떤 #[collapse_debuginfo] 속성의 값까지 모두 덮어쓰는 -C collapse-macro-debuginfo CLI 옵션이 있어요.

더 알아보기 (Learn more)