디버거
771f63d 기준입니다.raddebugger는 Windows와 초기 단계의 Linux x64를 대상으로 하는 사용자 모드·다중 프로세스 그래픽 디버거입니다. 사용자 모드는 일반 응용프로그램을 다룬다는 뜻입니다. 운영체제 커널을 디버깅하는 도구로 소개하면 범위를 벗어납니다. README가 명시하는 현재 지원 범위는 로컬 컴퓨터의 x64 디버깅입니다.
프로그램을 실행하거나 이미 실행 중인 프로세스에 붙고, 중단점에서 멈춘 뒤 변수·레지스터·메모리·호출 스택을 살펴봅니다. 소스 한 줄씩 이동하는 동작 뒤에는 기계어 명령 해석과 주소를 소스에 대응시키는 작업이 필요합니다. 이 저장소에서는 화면의 중단점 하나가 운영체제의 실행 제어로 바뀌는 전 과정을 따라갈 수 있습니다.
중단점은 특정 공정에서 설비를 멈추는 센서이고, 변수와 메모리는 그 순간의 재료와 창고 상태입니다. 호출 스택은 “누가 이 작업을 요청했는가”를 거슬러 올라가는 작업 지시서입니다.
공장을 멈췄을 때 계기판까지 얼어붙으면 조사할 수 없습니다. 그래서 설비를 제어하는 담당자와 화면을 갱신하는 담당자 사이에 명령과 보고를 주고받는 통로가 필요합니다. 이 비유를 기억하면 4절의 메시지 큐가 자연스럽게 읽힙니다.
src/raddbg/raddbg_main.c의 entry_point()는 명령행 인자에 따라 일반 GUI, IPC 명령 전송, 도움말, 이진 파일 유틸리티 모드로 분기합니다. 특히 --bin이면 rb_entry_point()로 들어갑니다. 같은 실행 파일이 화면과 디버그 정보 변환 도구의 입구를 함께 갖는 셈입니다.
# 아래는 사용 흐름을 보여주는 명령 예시
raddbg.exe # 일반 그래픽 디버거
raddbg.exe --help # 도움말 모드
raddbg.exe --bin --help # radbin 이진 파일 유틸리티 모드
코드 조각에서 생략한 부분과 학습용 예시는 주석으로 구분합니다. 빌드·실행 명령에 필요한 환경은 7절에서 설명합니다.
| 구성요소 | 하는 일 | 학습 관점 |
|---|---|---|
| RAD Debugger | 실행 제어와 상태 시각화 | 운영체제 API·동시성·GUI를 하나의 흐름으로 연결 |
| RDI | PDB·DWARF에서 변환해 사용하는 자체 디버그 정보 형식 | 서로 다른 입력을 공통 데이터 모델로 정규화 |
| RAD Linker | x64 PE/COFF 실행 파일과 PDB, 선택적으로 RDI 생성 | 오브젝트 파일·심벌·재배치·대규모 병렬 처리 |
화면과 엔진의 경계, 엔진과 운영체제의 경계, 외부 심벌과 RDI의 경계를 찾으세요. 링커까지 모두 이해해야 시작할 수 있는 프로젝트는 아닙니다.
2026-10-23 분석 기준 GitHub Trending C Monthly 4위이며, 스타는 8,638개입니다. 다만 순위만으로 유입 경로나 인기를 만든 단일 원인을 증명할 수는 없습니다. 아래는 최근 변경과 구현을 토대로 해석한 주목할 만한 이유입니다.
CHANGELOG.md의 v0.9.29-alpha는 네이티브 Linux x64 디버깅의 예비 지원을 주요 변경으로 내세웁니다. src/linux/demon/linux_demon.c에도 dmn_ctrl_launch()·dmn_ctrl_attach()·dmn_ctrl_run()과 실제 ptrace() 호출이 있습니다. 선언만 존재하는 이식 계획에서 실제 구현으로 옮겨 온 점이 관심 요인이라고 해석할 수 있습니다.
동시에 변경 기록은 Linux가 매우 초기 단계라고 밝힙니다. 지원 플랫폼이 늘었다는 사실과 Windows 수준의 안정성이 확보됐다는 주장은 다릅니다. Linux 사용자는 기능 표와 제한 사항을 함께 읽어야 합니다.
src/ui의 위젯, src/render의 그래픽 백엔드, src/eval의 표현식 처리, src/lib_rdi의 파일 형식이 한 저장소에 있습니다. 라이브러리를 조립한 결과만 보는 대신, 도구를 구성하는 추상화 자체가 어떻게 만들어지는지 배울 수 있습니다.
이는 자체 구현이 언제나 유리하다는 뜻은 아닙니다. 제어권이 넓은 만큼 창 관리·글꼴·입력·접근성·플랫폼 차이를 직접 유지해야 합니다. 학습할 때는 “무엇을 직접 만들었는가”와 “그 선택으로 어떤 부담을 얻었는가”를 함께 보세요.
| 도구 | 두드러지는 방향 | raddebugger에서 배울 차이 |
|---|---|---|
| GDB | 다양한 언어·플랫폼, 네이티브·원격 대상 조사 | 지원 범위를 좁힌 자체 GUI와 엔진의 연결을 추적하기 좋음 |
| LLDB | LLVM 기반 디버거, API·Python·IDE 연동 | 자체 표현식 처리와 RDI 중심의 내부 데이터 모델을 관찰 |
| WinDbg | Windows 사용자 모드·커널·덤프 분석 | 일반 응용프로그램의 실행 상태를 시각화하는 C 구현에 집중 |
| RAD Debugger | 로컬 x64 중심의 자체 네이티브 GUI, 알파 단계 | 동시성·메모리·심벌 변환·렌더링을 연속해서 공부 |
각 도구의 기능은 GDB 공식 문서, LLDB 공식 문서, WinDbg 공식 문서에서 확인할 수 있습니다. 이 표는 기능 방향의 비교이며 동일 조건의 속도 측정표가 아닙니다.
README는 디버그 정보가 수 GiB인 테스트 사례에서 RAD Linker의 링크 성능을 “50% faster link times”라고 설명합니다. 이는 링커에 대한 저자 측 결과이며, 소요 시간이 50% 줄었다는 뜻으로 단정할 수 없습니다. 디버거의 모든 작업이 50% 빠르거나, 모든 프로젝트에서 같은 차이가 난다는 근거로 확대하면 안 됩니다.
물류센터의 분류 설비가 빨라졌다고 매장 계산대까지 같은 비율로 빨라지는 것은 아닙니다. 링커는 실행 파일을 준비하는 단계이고, 디버거는 실행 중 상태를 조사하는 단계입니다. 성능을 읽을 때는 대상 작업과 입력 크기부터 찾으세요.
이 프로젝트에서 백엔드·프론트엔드는 학습을 위한 역할 구분입니다. 백엔드에 해당하는 것은 디버깅 엔진과 운영체제 제어이고, 프론트엔드는 자체 C GUI입니다. 일반적인 웹 API 서버나 JavaScript 프레임워크를 필수 구성요소로 두지 않습니다.
| 역할 | 실제 경로 | 읽을 지점 |
|---|---|---|
| 디버깅 엔진 | src/dbg_engine | d_init(), d_tick(), 제어 스레드와 이벤트 |
| 플랫폼 제어 | src/demon, src/win32/demon, src/linux/demon | 공통 dmn_ API를 각 운영체제 API로 구현 |
| 디버그 정보 | src/dbg_info, src/lib_rdi, src/rdi_from_* | 변환 작업 예약·별도 프로세스 실행·RDI 파싱 |
| 표현식 평가 | src/eval, src/eval2 | 토큰·구문 트리·형식 정보·평가 단계 |
| 내용과 캐시 | src/content, src/artifact_cache, src/file_stream | 데이터 해시, 비동기 계산 결과, 파일 내용 공급 |
| 기계어 해석 | src/disasm, src/x64/disasm | dasm_inst_from_code()에서 x64 해석으로 위임 |
src/raddbg 사용자 명령·창·탭·디버거 화면
↓
src/ui 박스 트리·레이아웃·입력 신호
↓
src/draw 상위 그리기 명령과 묶음 처리
↓
src/render D3D11 / OpenGL / 테스트용 stub
src/font_cache ← src/font_provider 글자 모양과 글꼴 래스터화
src/window_manager 운영체제 창과 입력 이벤트
src/render/render_inc.h는 Windows 기본값을 R_BACKEND_D3D11, Linux 기본값을 R_BACKEND_OPENGL로 고릅니다. src/font_provider/font_provider_inc.h는 Windows에서 DirectWrite, Linux에서 FreeType을 선택합니다. 같은 상위 코드에 다른 하위 구현을 끼우는 구조입니다.
이 커밋에는 package.json·Cargo.toml·pyproject.toml·go.mod·Dockerfile·CMakeLists.txt가 없습니다. 중심 빌드 파일은 build.bat와 build.sh입니다. 따라서 버전 근거는 동봉된 헤더와 CI 설정에서 찾아야 합니다.
| 항목 | 확인한 버전·값 | 근거와 의미 |
|---|---|---|
| BLAKE3 | 1.8.7 | src/third_party/blake3/blake3.h의 BLAKE3_VERSION_STRING |
| xxHash | 0.8.2 | src/third_party/xxHash/xxhash.h의 major·minor·release 매크로 |
| Zydis | 4.1.0.0 | src/third_party/zydis/zydis.h의 0x0004000100000000; 기계어 해석에 사용 |
| stb_sprintf | 1.10 기반 수정본 | src/third_party/stb/stb_sprintf.h 머리말: 디버거용 서식 지정자 추가 |
| RDI 인코딩 | 23 | src/lib_rdi/rdi.h의 RDI_ENCODING_VERSION; 제품 버전과 별개 |
| Linux CI | ubuntu-24.04, clang-17, clang-22, gcc | .github/workflows/builds.yml; GCC의 정확한 패치 버전은 고정하지 않음 |
| Windows CI | windows-2022, windows-2025 | MSVC·Clang 조합; Visual Studio 2022 및 경로상 18 계열 환경 |
| GitHub Actions | checkout@v6, upload-artifact@v7 | 후자는 비활성화된 실행 테스트 작업 안의 설정 |
FreeType·X11·OpenGL·EGL은 Linux 시스템 라이브러리로 연결하며, 빌드 설정에 정확한 패치 버전이 잠겨 있지 않습니다. README의 Clang 18.1.3·GCC 13.3.0 출력은 환경 예시입니다. CI의 컴파일러 목록이나 공식 최소 버전과 같은 뜻으로 읽으면 안 됩니다.
build.sh의 디버그 설정은 -O0 -DBUILD_DEBUG=1, 릴리스 설정은 -O2 -DBUILD_DEBUG=0이며, 공통으로 디버그 정보 생성 옵션 -g가 있습니다. Windows MSVC 설정은 /Od와 /O2로 갈립니다. 릴리스라는 이름만 보고 심벌까지 없앤다고 생각하지 마세요.
raddbg_main.c는 여러 레이어의 .c 구현을 직접 포함합니다. 흔히 unity build라고 부르는 방식으로, 그래픽 디버거의 주요 C 코드를 하나의 번역 단위에 모읍니다. BLAKE3 어셈블리처럼 따로 만들어 링크하는 입력도 있으므로, 모든 파일을 오직 한 번의 컴파일로 처리한다고 설명하면 부정확합니다.
// src/raddbg/raddbg_main.c의 구현 포함 방식 발췌
#include "base/base_inc.c"
#include "dbg_info/dbg_info.c"
#include "demon/demon_inc.c"
#include "dbg_engine/dbg_engine_inc.c"
#include "ui/ui_inc.c"
#include "raddbg/raddbg_inc.c"
빌드 앞단에는 C로 작성한 metagen이 있습니다. .mdesk 파일의 표를 읽고 generated/*.meta.h·*.meta.c를 만듭니다. 이름·열거형·보조 표를 한 선언에서 생성해, 같은 사실을 여러 곳에 수동으로 맞추는 부담을 줄입니다.
CI에는 여러 대상의 디버그·릴리스 빌드가 정의되어 있습니다. 반면 run-ubuntu-24-04와 run-windows-2022 작업은 if: false입니다. 테스트 실행 파일과 스크립트가 있다는 사실만으로 CI에서 실행 테스트가 매번 통과한다고 말할 수 없습니다.
먼저 전체 지도를 봅시다. 아래 도식에서 세로 방향은 사용자 조작이 실행 제어로 내려가는 흐름이고, 오른쪽은 그 상태를 사람이 읽을 수 있도록 해석하는 흐름입니다. 같은 화면에 보이는 값이라도 운영체제에서 읽는 바이트와 디버그 정보에서 찾는 형식·이름은 서로 다른 출처입니다.
src/dbg_engine/dbg_engine_core.c의 d_init()는 사용자→제어 통로 u2c와 제어→사용자 통로 c2u를 각각 KB(64)로 할당합니다. 각 통로에는 뮤텍스와 조건 변수가 붙습니다. 이름에 링 버퍼가 들어간다고 잠금 없는 구조인 것은 아닙니다.
src/dbg_engine/dbg_engine_ctrl.c의 d_u2c_push_msgs()는 명령 목록을 직렬화하고, 빈 공간이 있으면 길이와 본문을 기록합니다. 공간이 부족하면 기한까지 기다리며, 실패 여부를 반환합니다. 반대쪽 d_u2c_pop_msgs()는 바이트를 읽어 다시 D_MsgList로 복원합니다.
// d_u2c_push_msgs()의 공간 판단 부분 발췌
U64 unconsumed_size =
d_ctrl_state->u2c_ring_write_pos -
d_ctrl_state->u2c_ring_read_pos;
U64 available_size = d_ctrl_state->u2c_ring_size - unconsumed_size;
U64 needed_size = sizeof(msgs_srlzed_baked.size)
+ msgs_srlzed_baked.size;
// available_size가 needed_size 이상이면 길이와 본문 기록
// 부족하면 기한 확인 후 조건 변수로 대기
제어 스레드의 입구는 d_ctrl_thread__entry_point()입니다. 여기서 메시지를 꺼내 D_MsgKind_Launch·Attach·Run·SingleStep별로 처리합니다. 보고는 d_c2u_push_events(), 사용자 측 수신은 d_c2u_pop_events()가 맡습니다.
src/dbg_engine/dbg_engine_user.c는 보낼 명령을 별도 아레나에 깊은 복사하고, 전송이 성공한 경우에 목록과 아레나를 비웁니다. 이 순서가 중요합니다. 임시 포인터를 넘긴 뒤 발신 측 메모리를 먼저 지우면 수신 측이 이미 사라진 데이터를 읽을 수 있기 때문입니다.
비동기 설계의 핵심은 스레드 개수보다, 명령의 소유권과 결과가 돌아오는 경로를 명확히 정하는 데 있습니다. 이 코드에서는 메시지 직렬화와 복사가 그 경계를 드러냅니다.
식당 접수대가 주방에 손님 메모지를 잠깐 빌려주는 대신 주문서를 복사해 넘기는 상황입니다. 접수대 메모지를 지워도 주방의 주문서는 남아 있어야 합니다. 완료된 음식은 별도의 전달대에서 알려 줍니다.
여기서 스레드 사이의 링 버퍼와 프로그램 인스턴스 사이의 IPC를 구분하세요. raddbg_main.c의 IPC는 기본 포트 7423을 쓰는 소켓 경로입니다. 변경 기록의 “IPC가 소켓으로 바뀌었다”는 문장은 엔진 내부의 두 링 버퍼가 없어졌다는 뜻이 아닙니다.
PDB와 DWARF는 변수·함수·소스 줄 등 디버깅용 정보를 표현하지만 구조가 다릅니다. 화면의 모든 기능이 두 형식을 직접 처리한다면 분기와 중복이 늘어납니다. 이 프로젝트는 입력 형식별 변환기 → RDI → 공통 조회로 문제를 나눕니다.
PDB → src/rdi_from_pdb ┐
├→ RDI → src/lib_rdi → src/dbg_info → 화면·평가
DWARF → src/rdi_from_dwarf ┘
RDI_ENCODING_VERSION = 23
제품 릴리스 = v0.9.29-alpha
src/dbg_info/dbg_info.c는 기존 RDI 헤더의 인코딩 버전을 비교해 오래된 결과인지 판단합니다. 필요할 때는 현재 실행 파일을 --bin --quiet --rdi 인자와 함께 별도 프로세스로 실행하고, 출력 경로·스레드 수·완료 신호 인자를 전달합니다. UI에서 PDB 전체를 매번 즉시 해석하는 방식과 다른 경계입니다.
로딩 요청 입구인 di_open(), 결과 조회인 di_rdi_from_key(), 실제 RDI 파서인 rdi_parse()를 연결해서 읽어 보세요. 특히 결과를 기다리는 상태와 실제 데이터가 준비된 상태를 나눠 다루는 부분이 캐시와 비동기 API 학습에 좋습니다.
src/lib_rdi/rdi_parse.c의 파서 첫 단계는 파일 크기와 매직 값, 인코딩 버전, 섹션 표가 파일 범위 안에 있는지를 검사합니다. 아래는 헤더 버전 검사 발췌입니다.
if(hdr != 0 && hdr->encoding_version != RDI_ENCODING_VERSION)
{
hdr = 0;
result = RDI_ParseStatus_UnsupportedVersionNumber;
}
추가로 주소에는 기준점이 있습니다. 모듈 기준 오프셋이 0x1234이고 실제 적재 기준 주소가 0x140000000이면 실행 중 주소는 0x140001234입니다. 이 계산 예시는 심벌의 위치와 프로세스의 실제 주소를 연결해야 하는 이유를 보여 줍니다. 디스크 파일 오프셋까지 같은 숫자로 취급하면 안 됩니다.
src/base/base_arena.h는 기본 예약 크기를 MB(64), 기본 커밋 크기를 KB(64)로 둡니다. arena_push()는 정렬된 공간을 내주고 필요하면 영역을 확장하며, temp_begin()은 현재 위치를 저장하고 temp_end()는 그 위치까지 되돌립니다.
// 수명 규칙을 보여주는 교육용 예시
Temp checkpoint = temp_begin(arena);
U8 *buffer = push_array(arena, U8, 256);
// 여기서 buffer를 사용한다.
temp_end(checkpoint);
// 이제 buffer를 계속 쓰면 안 된다.
개별 객체마다 해제 코드를 흩뿌리는 대신, “이 작업이 끝날 때 함께 버린다”는 수명을 코드로 드러냅니다. 문자열·파싱 결과·프레임 중간 계산처럼 묶음으로 사라지는 데이터에 잘 맞습니다. 오래 살아야 하는 결과는 그 수명에 맞는 다른 아레나로 옮겨야 합니다.
src/base/base_thread_context.c는 스레드별 임시 아레나 두 개를 준비합니다. tctx_get_scratch()는 전달된 충돌 목록과 겹치지 않는 것을 고릅니다. scratch_begin(&arena, 1) 같은 호출은 출력용 아레나를 임시 작업 때문에 되감지 않으려는 의도를 보여 줍니다.
한 요리를 만들 때 재료를 큰 쟁반 하나에 놓고, 요리가 끝나면 쟁반을 한 번에 치우는 방식입니다. 완성된 음식까지 그 쟁반에 두고 치우면 안 됩니다. 아레나에서 가장 중요한 질문도 “누가 해제하나”와 함께 “언제까지 살아야 하나”입니다.
예약 주소 공간과 실제 메모리 사용도 구분해야 합니다. metagen_main.c는 GB(64) 예약·MB(64) 커밋을 요청합니다. 이 한 줄을 “최소 RAM 64GB가 필요하다”고 읽을 수는 없습니다.
src/ui/ui_core.c의 ui_begin_build()와 ui_end_build() 사이에서 박스 트리를 구성합니다. 버튼·레이블 같은 위젯은 그리기와 클릭 등의 플래그를 가진 박스를 만들고, ui_signal_from_box()로 입력 결과를 얻습니다. src/ui/ui_basic_widgets.c의 ui_button()부터 읽으면 구조가 작아 접근하기 쉽습니다.
// ui_button()의 핵심만 남긴 축약
UI_Box *box = ui_build_box_from_string(
UI_BoxFlag_Clickable | UI_BoxFlag_DrawText, string);
UI_Signal interact = ui_signal_from_box(box);
return interact;
이 방식은 호출 흐름에서 UI를 기술하는 즉시 모드 스타일입니다. 그렇다고 내부 상태가 전혀 없는 것은 아닙니다. UI_Key로 박스를 찾고, last_touched_build_index로 이번 구성에서 사용했는지 기록합니다. ui_end_build()는 사용하지 않은 박스를 정리하고 축별 레이아웃을 계산합니다.
웹의 가상 DOM과 같다고 외우기보다, 이번 화면의 선언과 이전 화면의 상호작용 상태를 무엇으로 연결하는가를 보세요. 이름·키·캐시가 그 연결을 맡습니다. 안정적인 식별자가 중요한 이유를 실제 C 코드로 확인할 수 있습니다.
771f63d의 변경 대상은 src/linux/window_manager/linux_window_manager.c입니다. wm_rect_from_window()와 wm_client_rect_from_window()는 결과를 0으로 초기화하고 창 포인터가 0이 아닐 때만 창 정보를 읽습니다. 거대한 아키텍처도 이런 작은 입력 경계 위에 서 있습니다.
// 두 함수에 공통으로 나타나는 형태를 축약
LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0];
Rng2F32 result = {0};
if(w != 0)
{
// 유효한 창의 사각형을 계산한다.
}
return result;
이 수정은 0 핸들을 방어합니다. 임의의 잘못된 0이 아닌 포인터나 모든 X11 호출 실패까지 해결한다고 확대하지 마세요. 버그 수정의 보장 범위를 입력 조건과 함께 읽는 습관도 중요한 학습입니다.
저장소는 src 아래에 비교적 평평한 레이어를 둡니다. 아래 트리는 저장소 루트를 기준으로 학습에 필요한 경로를 추린 것입니다. build와 local은 빌드 과정에서 만들어지는 경로입니다.
| 접두사 | 주요 레이어 | 예시 |
|---|---|---|
RD_ / rd_ | 디버거 애플리케이션 | RD_WindowState, rd_frame() |
D_ / d_ | 디버깅 엔진 | D_MsgList, d_tick() |
DMN_ / dmn_ | 저수준 제어 | DMN_CtrlCtx, dmn_ctrl_run() |
DI_ / di_ | 디버그 정보 관리 | DI_Key, di_rdi_from_key() |
UI_ / ui_ | 사용자 인터페이스 | UI_Box, ui_button() |
E_ / e_ | 표현식 평가 | E_Parse, e_interpret() |
C에 C++식 이름공간 문법이 없어도 이런 규칙으로 소속을 표현할 수 있습니다. 타입과 함수 이름을 보고 다음에 열 폴더를 예상해 보세요. 헤더 선언에서 구현으로 이동하는 속도가 빨라집니다.
| 종류 | 파일 수 | 물리적 줄 수 |
|---|---|---|
C .c | 281 | 278,824 |
C 헤더 .h | 261 | 103,584 |
C++ .cpp | 6 | 7,207 |
어셈블리 .S·.asm | 9 | 19,876 |
Metadesk .mdesk | 25 | 9,290 |
셸·배치 .sh·.bat | 4 | 591 |
YAML .yml | 1 | 218 |
계수는 커밋 771f63d의 저장소 전체에서 .git을 제외하고 파일별 개행 문자 수를 합친 값입니다. 빈 줄·주석·테스트·외부 소스·생성 코드·obsolete를 포함하며, 헤더를 모두 C 항목으로 묶었습니다. GitHub의 언어 비율이나 순수 구현 줄 수와는 다릅니다.
C·헤더 합계 382,408줄 중 third_party가 96,929줄, 그 밖의 generated가 31,317줄입니다. 두 범주를 제외하면 254,162줄이며, 여전히 주석·테스트·과거 코드가 포함됩니다. src 내부 C·헤더는 370,522줄, obsolete의 C·헤더는 11,886줄입니다.
레이어별 C·헤더 합계는 raddbg 56,832줄, linker 43,447줄, eval 12,776줄, base 12,027줄, dbg_engine 11,813줄입니다. 이 레이어 수치는 내부 생성 코드를 포함합니다. 파일 크기를 보고 진입점을 고르되, 크기를 품질 점수로 바꾸지는 마세요.
README의 레이어 목록에는 ctrl과 os/core·os/gfx 같은 이름이 남아 있습니다. 이 커밋에서 제어 코드는 src/dbg_engine/dbg_engine_ctrl.c, 운영체제 구현은 src/win32·src/linux 등에서 찾을 수 있습니다. 설명의 개념은 참고하되 파일 이동은 현재 경로를 기준으로 해야 합니다.
저장소를 공부할 때는 폴더 전체보다, 작은 함수 하나에서 시작해 호출자와 피호출자를 한 단계씩 연결하는 편이 길을 잃지 않습니다. 첫 경로로 ui_button(), 두 번째 경로로 d_u2c_push_msgs()를 권합니다.
이 코드에서는 String8과 String8List를 자주 만납니다. 문자열을 0 종료 포인터 하나로만 보지 않고 바이트 주소와 길이가 있는 값으로 다룹니다. src/base/base_strings.h에서 타입을 확인하고, src/base/base_strings.c의 복사·부분 문자열·결합 함수를 따라가 보세요.
str8_substr()와 str8_copy()의 구현을 비교해 메모리 배치도를 그립니다.Windows의 src/win32/demon/win32_demon.c에는 WaitForDebugEvent()가, Linux의 대응 파일에는 PTRACE_SEIZE·PTRACE_CONT·레지스터 읽기 호출이 나타납니다. 상위 함수 이름은 같아도 이벤트와 스레드를 다루는 절차는 다릅니다.
dmn_ctrl_attach()를 읽고 공통 단계와 운영체제별 단계를 표로 만듭니다.정상 흐름에서 메시지를 한 번 보내는 것은 쉽습니다. 더 좋은 질문은 공간이 부족하거나 받는 쪽이 늦을 때의 동작입니다. 엔진은 송신 기한과 성공 여부를 다루고, 성공 전까지 보낼 목록을 유지합니다. 처리량과 응답성은 이런 경계 조건에서 갈립니다.
조사창의 player.health + 1을 표시하려면 이름을 해석하고, 멤버의 형식과 위치를 찾고, 값을 읽어 연산해야 합니다. src/eval/eval_parse.h의 e_token_array_from_text()·e_push_parse_from_string(), src/eval/eval_ir.c, src/eval/eval_interpret.c를 연결해 보세요.
학습용 개념 흐름: 실제 함수 전체의 호출 순서를 복제한 것은 아님
표현식 문자열
→ 토큰
→ 구문 트리
→ 형식·이름 해석
→ 중간 표현과 평가용 코드
→ 대상 메모리·레지스터를 이용한 값
일반 프로그램 안에서 같은 수식을 계산하는 것과도 다릅니다. 변수의 값이 디버거 자신의 메모리에 있는 것이 아니기 때문입니다. 표현식의 문법을 이해하는 일과, 다른 프로세스에서 그 값이 어디 있는지 찾는 일은 별도의 문제입니다.
1 + 2 * 3의 트리를 그린 뒤 파서의 우선순위 처리와 대조합니다.ui_button()의 반환값은 입력 신호이며 GPU 명령 그 자체가 아닙니다. UI 레이어는 박스의 의미와 배치를, draw·render 레이어는 실제 그리기를 맡습니다. 글꼴도 font_provider와 font_cache로 나눠져 있습니다.
src/ui/ui.mdesk의 UI_StackTable에는 PrefWidth·FontSize 같은 항목과 기본값이 있습니다. 아래 @gen·@expand 선언이 이를 C 구조와 보조 코드로 펼칩니다. 생성된 헤더만 읽으면 같은 모양의 함수가 왜 많은지 놓치기 쉽습니다.
PrefWidth 한 행이 생성 코드의 어느 이름들로 이어지는지 찾아 대응표를 만듭니다.src/mule는 디버거 기능을 시험할 대상 프로그램들을 담고, src/torture/torture_main.c는 테스트 실행기의 입구입니다. src/base/tests/base_tests.c·src/raddbg/tests/raddbg_tests.c·src/linker/tests/linker_tests.c도 있습니다. 테스트가 있다는 사실, 실행 대상이 있다는 사실, CI가 실제로 실행한다는 사실은 따로 확인해야 합니다.
어떤 값을 받는지, 무엇을 돌려주는지, 메모리를 누가 유지하는지, 어디서 기다릴 수 있는지 적어 보세요. 코드를 한국어로 한 줄씩 옮기는 것보다 구조를 오래 기억하는 데 도움이 됩니다.
| 항목 | 확인한 조건 | 근거·해석 |
|---|---|---|
| CPU·대상 | 로컬 x64 디버깅 | README의 지원 범위. 다른 아키텍처 자료가 있어도 제품 지원을 뜻하지 않음 |
| 운영체제 | Windows x64, 초기 Linux x64 | macOS·ARM64를 현재 지원 환경으로 제시하지 않음 |
| Windows 빌드 | Build Tools v15(2017) 이상과 Windows SDK; 또는 SDK를 갖춘 Clang | README 기준. CI는 별도의 더 최신 환경 조합을 정의 |
| Linux 빌드 | GCC 또는 Clang, 빌드 도구, 시스템 개발 라이브러리 | build.sh와 CI 기준; 정확한 GCC 최소 버전은 명시하지 않음 |
| Windows 화면 | D3D11 기본, 기능 수준 11_0 요청 | render_d3d11.c의 r_init(); 하드웨어 실패 시 WARP 재시도 경로 |
| Linux 화면 | X11과 OpenGL 3.3 계열 경로 | GLX·EGL 구현의 3.3 컨텍스트 요청, 셰이더의 #version 330 core |
| RAM·디스크 | 일반적인 최소 수치는 명시되지 않음 | 대상 프로세스, PDB·DWARF, 변환 RDI와 빌드 산출물 크기에 좌우됨 |
Linux 빌드는 pkg-config로 freetype2, x11 xext xfixes xrandr, gl egl을 찾습니다. pkg-config 실행 파일이 없을 때 기본 경로와 라이브러리 옵션으로 대체하며, 패키지 조회 실패 시 대체하는 분기는 없습니다. X11 의존성이 있으므로 순수 Wayland 전용 네이티브 구현이라고 소개해서는 안 됩니다.
학습용으로는 작은 C 프로그램과 심벌부터 다루면 메모리 부담을 줄일 수 있습니다. 16GiB RAM이나 SSD가 있는 환경은 여유를 잡는 예시일 뿐 공식 최소 사양이 아닙니다. 거대한 게임 프로젝트의 심벌 변환·링크에 필요한 자원을 이 예시로 보장할 수는 없습니다.
변수명과 소스 줄을 보려면 대상 프로그램의 디버그 정보가 필요합니다. 최적화된 프로그램에서는 변수가 사라지거나 줄 이동이 예상과 달라질 수 있습니다. 첫 실습은 디버그 정보가 있는 작은 프로그램으로 하고, 다음에 최적화 설정만 바꿔 비교하면 원인을 분리하기 쉽습니다.
# Windows: x64 개발 도구 명령 프롬프트, 저장소 루트에서
build raddbg msvc debug
build raddbg msvc release
build radbin msvc release
# Linux: 필요한 개발 라이브러리를 준비한 뒤
./build.sh raddbg clang debug
./build.sh raddbg clang release
./build.sh radbin clang release
첫 빌드에는 metagen의 컴파일·실행과 생성 파일 갱신이 포함됩니다. build뿐 아니라 src 내부 생성 파일도 바뀔 수 있습니다. 읽기 전용 학습 단계와 직접 빌드하는 단계를 구분하고, 실습용 사본에서 변경 사항을 확인하는 습관을 들이세요.
Oodle은 oodle 옵션으로 활성화하는 선택 경로입니다. 빌드 스크립트는 OODLE_SDK_DIR 또는 local/oodle2*에서 별도 SDK를 찾습니다. 기본 빌드의 필수 동봉 의존성이나 버전이 고정된 패키지로 취급하지 마세요.
과제의 완료 기준을 기능 추가량보다, 관찰한 동작을 코드의 근거와 연결해 설명할 수 있는지에 두세요. 처음 두 과제는 큰 기능을 구현하지 않아도 시작할 수 있고, 뒤의 과제는 동시성·생성기·파일 형식으로 넓어집니다.
목표: 커밋 771f63d의 작은 수정을 읽고, 무엇을 보장하는지 정확히 설명합니다.
src/linux/window_manager/linux_window_manager.c에서 두 wm_*rect_from_window() 함수를 찾습니다.resize_draw가 켜진 경우의 분기를 나눕니다.완료 기준: “0 입력은 0 사각형을 반환하며 창 정보 조회를 건너뛴다”를 소스에 근거해 설명하고, 창 위치를 포함한 사각형과 원점이 (0, 0)인 클라이언트 사각형을 구분한 표를 만듭니다.
한 단계 더: X11 호출의 실패 반환값까지 처리하는지 별도 질문으로 남깁니다. 0 핸들 수정이 모든 오류 처리를 끝냈다고 결론 내리지 않습니다.
목표: 디버거의 값 표시가 소스 줄·호출 스택·실행 위치와 어떻게 연결되는지 직접 관찰합니다. 별도의 실습용 파일에 아래 코드를 준비합니다.
int main(void)
{
int values[] = {1, 2, 3, 4};
int total = 0;
for(int i = 0; i < 3; ++i)
{
total += values[i];
}
return total == 10 ? 0 : 1;
}
cl /Zi /Od sum.c /Fe:sum.exe, Linux 예시는 cc -g -O0 sum.c -o sum입니다.i와 total을 Locals 또는 Watch에서 확인합니다.i < 4로 고친 뒤 다시 컴파일하고 결과를 비교합니다.완료 기준: 수정 전 합계는 6, 수정 후 합계는 10입니다. 각 반복의 i·total과 정지 위치를 표로 정리합니다. 최적화 설정을 바꿨을 때 관찰이 달라질 수 있는 이유도 적어 보세요.
목표: d_u2c_push_msgs()의 길이·본문·공간 판단을 작은 숫자로 재현합니다. 실제 엔진의 64KiB 대신 32바이트 버퍼를 가정합니다.
처음: read = 0, write = 0, 빈 공간 = 32
첫 기록: read = 0, write = 28, 빈 공간 = 4
둘째 요구: 8 + 16 = 24 → 지금은 기록 불가
첫 소비: read = 28, write = 28, 빈 공간 = 32
둘째 기록: read = 28, write = 52, 빈 공간 = 8
실제 다음 쓰기 인덱스: 52 % 32 = 20
완료 기준: 논리적인 읽기·쓰기 위치와 물리적인 배열 인덱스의 차이를 설명하고, “버퍼 전체보다 큰 메시지는 어떤 정책으로 다뤄야 할까?”라는 질문에 분할·거부·확장 중 설계안을 제시합니다. 현재 구현의 모든 경로가 그 정책을 갖췄다고 추정하지 마세요.
목표: 코드를 수정하기 전에 생성기의 원본을 식별합니다. 읽기만으로 완성할 수 있는 과제입니다.
src/ui/ui.mdesk에서 UI_StackTable의 PrefWidth 행을 찾습니다.src/ui/generated/ui.meta.h에서 UI_PrefWidthNode·ui_top_pref_width()·ui_push_pref_width()를 찾습니다.@expand의 치환값과 생성된 타입·함수 이름을 연결합니다.src/metagen/metagen_main.c에서 입력 검색과 출력 파일 쓰기 부분을 찾습니다.완료 기준: 원본 한 행 → 생성된 구조체·스택·함수 선언의 대응표를 만듭니다. 생성 결과만 수동 수정하면 다음 생성 때 수정이 사라질 수 있는 이유도 설명합니다.
목표: src/dbg_info/dbg_info.c에서 비동기 변환의 조건과 완료 처리를 읽습니다. 처음에는 별도 파서를 만들거나 원본 코드를 수정하지 않아도 됩니다.
di_open()·di_rdi_from_key()와 rdi_is_stale 주변을 읽습니다.완료 기준: 준비 전 → 대기 → 변환 중 → 로딩 → 사용 가능 또는 실패로 이어지는 학습용 상태도를 만듭니다. 이 이름들이 실제 열거형과 모두 일치하는 것은 아니므로, 각 화살표에 근거 함수·조건을 따로 붙입니다.
확장: 독립적인 작은 파일 형식을 설계하고 매직 값·버전·길이를 검사하는 파서를 작성합니다. 파일 길이를 벗어나는 입력에 대해 예상 오류를 표로 정한 뒤 검증합니다.
처음부터 링커와 그래픽 코드를 동시에 읽으면 등장하는 개념이 너무 많습니다. 매주 작은 결과물을 만들고, 그 결과물을 다음 주의 소스 읽기에 사용하는 순서로 공부하세요. 아래 일정은 C의 조건문·반복문·함수·포인터 기초를 알고 있다는 가정입니다.
| 주차 | 핵심 개념 | 소스와 실습 | 남길 결과물 |
|---|---|---|---|
| 1주 | 포인터·길이·메모리 수명 | base_strings, base_arena, 임시 데이터 되감기 | 문자열 뷰와 복사본의 메모리 그림 |
| 2주 | 프로세스·스레드·정지 이벤트 | demon의 공통 선언과 Windows 또는 Linux 구현 | 연결·정지·재개의 단계표, 합계 프로그램 관찰 기록 |
| 3주 | 뮤텍스·조건 변수·메시지 | dbg_engine_core·dbg_engine_ctrl, 링 버퍼 계산 | 큐 포화·시간 제한·복사 수명 설명서 |
| 4주 | 심벌·파일 배치·주소 | lib_rdi·dbg_info, RDI 헤더와 변환 흐름 | 파일 오프셋·모듈 오프셋·실행 주소 비교표 |
| 5주 | 토큰·구문 트리·형식 | eval_parse·eval_ir·eval_interpret | 산술식과 멤버 접근식의 처리 단계도 |
| 6주 | UI 키·레이아웃·코드 생성 | ui_button(), ui_end_build(), UI_StackTable | 중단점 조작에서 결과 표시까지의 한 장 지도 |
명령이 복사되는 이유, 심벌 변환이 별도 작업인 이유, 임시 메모리를 되감는 시점, UI 키가 필요한 이유를 먼저 한국어로 설명해 보세요. 이후 실제 함수와 경로를 붙일 수 있다면 구조를 이해한 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 사용자 모드 | 일반 응용프로그램이 실행되는 영역. 커널 내부를 조사하는 기능과 범위가 다릅니다. |
| 프로세스 / 스레드 | 프로세스는 실행 중 프로그램의 자원 단위, 스레드는 그 안에서 명령을 실행하는 흐름입니다. |
| 디버기 | 디버거가 조사하는 대상 프로그램입니다. |
| 중단점 | 지정한 위치나 조건에서 실행을 멈추도록 설정한 지점입니다. |
| 단계 실행 | 소스 또는 명령 단위로 실행을 조금씩 진행하며 상태를 살펴보는 기능입니다. |
| 레지스터 | CPU가 명령 실행에 직접 쓰는 작은 저장 공간. 현재 위치와 계산 중간값 등을 담습니다. |
| 호출 스택 / 언와인딩 | 누가 누구를 호출했는지 이어진 기록과, 그 기록을 역으로 복원하는 작업입니다. |
| 심벌 / 디버그 정보 | 주소를 함수·변수·형식·소스 줄 등 사람이 아는 정보와 연결하는 자료입니다. |
| PDB / CodeView | Windows 도구 생태계의 디버그 정보와 관련된 형식들. 컨테이너와 내부 레코드를 같은 개념으로 뭉뚱그리지 마세요. |
| DWARF | 소스 수준 디버깅을 위한 정보 형식 표준. Linux 등에서 널리 사용됩니다. |
| RDI | 이 프로젝트가 공통 조회를 위해 사용하는 자체 디버그 정보 형식입니다. |
| PE/COFF / ELF | 실행 파일이나 오브젝트 파일의 구조를 정의하는 형식 계열입니다. |
| 링커 / 재배치 | 여러 오브젝트를 실행 파일로 엮는 도구와, 최종 위치에 맞춰 주소 참조 등을 조정하는 작업입니다. |
| 모듈 기준 오프셋 | 모듈 시작점을 기준으로 한 상대 위치. 실제 실행 주소나 디스크 파일 위치와 구분합니다. |
| ASLR | 보안 등을 위해 프로그램 적재 주소를 바꾸는 방식. 같은 심벌의 실행 주소가 매번 같다고 가정하면 안 됩니다. |
| IPC | 서로 다른 프로세스 사이의 통신. 이 프로젝트의 인스턴스 명령 전달에는 소켓 경로가 있습니다. |
| 링 버퍼 | 배열 끝 다음을 다시 처음으로 이어 사용하는 버퍼. 읽기·쓰기 위치와 남은 공간을 관리합니다. |
| 역압력 | 받는 쪽이 밀릴 때 보내는 쪽의 속도나 요청을 제한해 넘침을 다루는 방식입니다. |
| 직렬화 | 구조화된 값을 통로에 보낼 수 있는 바이트 순서로 바꾸는 작업입니다. |
| 아레나 / 임시 아레나 | 메모리 수명을 묶어서 관리하는 영역과, 작업 후 위치를 되감아 재사용하는 임시 영역입니다. |
| 즉시 모드 UI | 화면을 구성하는 코드를 반복 호출해 현재 UI를 기술하는 스타일. 내부 캐시나 지속 상태와 함께 쓸 수 있습니다. |
| 글리프 아틀라스 | 래스터화한 글자 모양을 큰 텍스처에 모아 두어 화면에 효율적으로 그리는 저장 방식입니다. |
| Metadesk / metagen | 이 프로젝트의 선언 파일 형식과, 그 선언을 읽어 코드·데이터 등을 생성하는 프로그램입니다. |
| AST / IR | 문법 구조를 담은 트리와, 다음 처리 단계가 다루기 좋게 변환한 중간 표현입니다. |
| unity build | 여러 구현 파일을 한 번역 단위에 포함해 컴파일하는 구성 방식입니다. |
| 알파 | 기능과 안정성이 계속 바뀌는 초기 배포 단계. 버전 이름 자체가 완성도를 보장하지는 않습니다. |
용어를 외운 다음에는 자신이 읽은 함수와 하나씩 연결해 보세요. 예를 들어 직렬화는 d_serialized_string_from_msg_list(), 임시 수명은 temp_end(), UI 입력은 ui_signal_from_box()에 대응시킬 수 있습니다.
루트 README는 기술 개요이며 디버거 사용 설명서를 대신하지 않는다고 스스로 밝힙니다. 사용 안내는 릴리스 패키지나 빌드 결과의 README도 함께 확인해야 합니다. 특정 메뉴 위치와 키 바인딩을 소스의 오래된 설명만으로 외우지 마세요.
| 주제 | 고정된 소스 링크 | 먼저 볼 식별자 |
|---|---|---|
| 앱 입구 | src/raddbg/raddbg_main.c | entry_point(), ExecMode |
| 화면과 엔진 | src/raddbg/raddbg_core.c | rd_frame(), d_tick() 호출 |
| 엔진 초기화 | src/dbg_engine/dbg_engine_core.c | d_init(), 두 링 버퍼 |
| 스레드 통신 | src/dbg_engine/dbg_engine_ctrl.c | d_u2c_push_msgs(), 제어 스레드 입구 |
| 사용자 측 명령 | src/dbg_engine/dbg_engine_user.c | d_tick(), 명령 깊은 복사·송신 |
| 플랫폼별 제어 | Windows 구현 / Linux 구현 | dmn_ctrl_attach(), dmn_ctrl_run() |
| RDI 변환·로딩 | src/dbg_info/dbg_info.c | di_open(), di_rdi_from_key() |
| RDI 형식·파서 | rdi.h / rdi_parse.c | RDI_ENCODING_VERSION, rdi_parse() |
| 메모리 수명 | src/base/base_arena.c / base_thread_context.c | arena_push(), temp_end(), 임시 아레나 선택 |
| UI 구성 | ui_core.c / ui_basic_widgets.c | ui_begin_build(), ui_button() |
| 표현식 | eval_parse.c / eval_interpret.c | 토큰·구문 분석, e_interpret() |
| 선언과 생성 | ui.mdesk / metagen_main.c | UI_StackTable, 파일 검색·출력 |
| 빌드·CI | build.sh / build.bat / builds.yml | 컴파일 옵션·대상 행렬·비활성 실행 작업 |
| 0 핸들 처리 | linux_window_manager.c | wm_rect_from_window(), wm_client_rect_from_window() |
| 항목 | 상태 |
|---|---|
| 선정 기준 | GitHub Trending C Monthly #4 · 2026-10-23 분석 |
| 스타 / 포크 | ⭐8,638 / 428 · 2026-10-23 GitHub 기준 |
| 생성일 | 2024-01-10 |
| 최신 릴리스 | v0.9.29-alpha · 2026-09-30 |
| 최근 push | 2026-10-22 · GitHub 기준 |
| 소스 기준 커밋 | 771f63d · 2026-10-20 · Linux 창 사각형 함수의 0 핸들 방어 |
| 주 언어 / 코드 규모 | C · C 278,824줄 + 헤더 103,584줄, 합계 382,408줄 |
| 계수 범위 | 개행 기준, 외부·생성·테스트·과거 코드 포함 |
| 대표 의존성 | 동봉 BLAKE3 1.8.7 · xxHash 0.8.2 · Zydis 4.1.0.0 · stb_sprintf 1.10 기반 수정본 |
| 지원 범위 | 로컬 Windows·Linux x64 사용자 모드 디버깅; Linux는 초기 단계 |
| 라이선스 | 루트 MIT · 저작권자 Epic Games Tools |
최근 push는 저장소 갱신 시각이고 커밋 시각과 같은 개념이 아닙니다. 소스 기준은 커밋 771f63d(2026-10-20)이며, 릴리스·인기도 등 저장소 상태는 2026-10-23 GitHub 기준입니다.
① Linux는 초기 단계입니다. v0.9.29 변경 기록은 Linux 바이너리를 배포하지 않았다고 밝히며, fork·vfork 디버깅, TLS 계산에서의 libc 가정, .eh_frame_hdr 부재 시 언와인딩, 비트필드 형식 정보 등의 제한을 열거합니다. 이는 해당 릴리스의 안내이므로 이후 수정 여부는 이슈와 새 릴리스에서 다시 확인해야 합니다.
② 지원 계획과 코드 존재를 구분해야 합니다. README는 원격·다중 머신 디버깅을 확장 계획으로 설명하지만, 이 커밋에는 원격 서버 관련 메시지와 함수도 있습니다. 소켓 IPC나 함수 이름만으로 완성된 원격 디버깅을 지원한다고 판정할 수 없습니다. macOS·ARM64·커널 디버깅도 현재 지원 범위에 포함하지 않습니다.
③ 성능 수치는 조건과 출처를 함께 봐야 합니다. 링커 성능 수치는 README의 저자 측 테스트 사례에 한정됩니다. CI에는 빌드 작업이 정의되어 있지만 실행 테스트 두 작업은 비활성 상태입니다. 코드의 존재만으로 특정 장비에서의 성능·안정성을 보장할 수는 없습니다.
④ 문서와 소스가 함께 이동하는 것은 아닙니다. README의 일부 레이어 이름은 현재 디렉터리와 다릅니다. 생성 코드와 동봉 외부 코드도 상당한 비중을 차지하므로, 줄 수를 전부 직접 작성한 구현량으로 읽지 마세요. 재사용할 때는 루트 라이선스와 외부 코드의 고지 파일을 각각 확인할 수 있습니다.