데스크탑 분석도구
34483cb 기준입니다.ImHex는 16진수 편집기와 구조 해석기를 한 화면에 결합한 오픈소스 프로그램입니다. 바이트를 수정하는 기본 기능에 Pattern Language, 디스어셈블러, 해시 계산, 데이터 시각화, YARA 규칙 검색, 플러그인이 붙습니다. 핵심은 파일을 숫자로 보여주는 데서 나아가, 그 숫자가 어떤 필드인지 사람이 설명할 수 있게 만든다는 점입니다.
00~FF 두 자리입니다. 오프셋은 파일 시작에서 몇 바이트 떨어졌는지를 나타냅니다. 첫 바이트의 오프셋은 0입니다.아래는 학습용으로 정의한 18바이트 형식입니다. 앞의 12바이트는 헤더이고, 뒤의 6바이트는 내용입니다. 01 00만 보고서는 이것이 숫자 1인지, 숫자 256인지, 서로 독립적인 두 값인지 알 수 없습니다. 데이터와 해석 규칙을 함께 알아야 의미가 생깁니다.
# 학습용 형식: IMHX 헤더 + 6바이트 내용
오프셋 바이트 의미
0x00 49 4D 48 58 "IMHX"
0x04 01 00 버전 1
0x06 03 00 항목 수 3
0x08 06 00 00 00 내용 길이 6
0x0C 41 42 43 44 45 46 "ABCDEF"
ImHex의 Pattern Editor에 다음과 같은 규칙을 적으면, 각 구간을 필드 이름과 연결할 수 있습니다. u16은 2바이트 부호 없는 정수, u32는 4바이트 부호 없는 정수입니다. 이 예제는 Pattern Language 코드이며 C++ 코드와는 다릅니다.
#pragma endian little
struct DemoHeader {
char magic[4];
u16 version;
u16 count;
u32 payload_size;
};
DemoHeader header @ 0x00;
49 4D 48 58이라는 물건 네 개에 magic이라는 이름을 붙이고, 다음 두 개에는 version을 붙입니다. 분류표가 있으면 매번 눈으로 숫자를 세지 않아도 됩니다.
하지만 잘못된 분류표를 붙이면 그럴듯한 오답이 나옵니다. 길이·바이트 순서·필드 경계를 검증하는 일이 바이너리 분석의 출발점입니다.
우선 FileProvider::readRaw()에서 바이트가 어디서 오는지 보고, configureRuntime()에서 그 바이트를 해석기에 어떻게 넘기는지 따라가세요. 이 두 지점이 연결되면 나머지 기능의 위치를 찾기 쉬워집니다.
2026-10-23 분석 기준 선정 위치는 GitHub Trending C++ Weekly #9입니다. 스타 55,522개와 포크 2,565개는 누적 관심을 보여주지만, 그 수치만으로 이번 주 유입 원인을 설명할 수는 없습니다. 특정 홍보·영상·신규 릴리스가 순위 상승을 일으켰다는 인과관계는 확인되지 않았습니다.
코드와 기능에서 읽을 수 있는 주목 포인트는 분명합니다. 바이트 편집, 구조 해석, 시각화, 확장 API가 하나의 데이터 인터페이스를 공유하므로 기능 사이를 오가며 분석할 수 있습니다. 여기에 구현 자체가 공개되어 있어 도구의 동작을 소스로 검증할 수 있다는 장점이 있습니다.
비교의 기준은 “무엇을 주로 읽을 것인가”입니다. 아래는 전체 성능 순위가 아니라 대표적인 작업 흐름의 비교입니다. 같은 기능이 여러 제품에 있어도 설계의 중심은 다를 수 있습니다.
| 도구 | 분석의 중심 | ImHex에서 배울 장점 |
|---|---|---|
| 일반 헥스 편집기 | 바이트 탐색·검색·직접 수정 | 구조 정의와 시각화까지 연결해 반복적인 수작업을 줄이는 설계 |
| 010 Editor | 텍스트·헥스 편집과 Binary Templates 기반의 계층적 해석 | 유사한 구조 해석 문제를 공개된 Pattern Language 연동 코드와 C++ 플러그인 API로 공부할 수 있음 |
| Ghidra | 디스어셈블·디컴파일·그래프 등 실행 코드의 역공학 | 임의 파일 형식의 필드·바이트·통계에 집중하기 좋은 흐름. 디컴파일 기능의 대체재로 단정할 수 없음 |
010 Editor도 구조 해석을 지원하므로 “ImHex만 파일 구조를 읽는다”는 비교는 부정확합니다. 비교 근거는 010 Editor 공식 소개와 Ghidra 공식 소개입니다. ImHex의 학습상 강점은 그 기능이 연결되는 내부 코드를 끝까지 읽을 수 있다는 것입니다.
plugins/builtin/source/content/views.cpp의 registerViews()는 ViewHexEditor, ViewPatternEditor, ViewPatternData, ViewInformation 등을 같은 등록 API에 넣습니다. 각 화면을 별도 프로그램으로 만들지 않고, 공통 환경 안에 배치하는 방식입니다.
확장도 같은 흐름을 따릅니다. plugins/disassembler/CMakeLists.txt는 Capstone과 별도 libdisassembler를 연결하고, plugins/decompress/CMakeLists.txt는 찾은 압축 라이브러리만 활성화합니다. 큰 제품이 기능별 의존성을 분리하는 사례입니다.
분석 대상 커밋은 2026-10-08의 34483cb이며, 제목은 Pattern Editor의 데이터 경쟁과 충돌 수정에 관한 것입니다. view_pattern_editor.cpp에는 런타임 잠금, 로그 잠금, 비동기 실행, UI 상태가 함께 나옵니다. 이 복잡함은 “화면이 있는 언어 도구”를 만들 때 동시성이 왜 중요한지 보여줍니다.
편집자는 원고를 바꾸고, 분석가는 같은 원고를 읽고, 인쇄 담당자는 결과를 화면에 내보냅니다. 셋이 동시에 같은 종이를 고치면 충돌합니다. 작업 속도를 높이는 것만큼 누가 언제 어떤 상태를 소유하는지 정하는 일이 중요합니다.
이 프로젝트의 기본 실행 단위는 데스크톱 프로세스입니다. 웹 서비스에서 말하는 백엔드와 프론트엔드를 그대로 대입하기보다, 데이터·분석 계층 / 화면 계층 / 빌드·배포 계층으로 읽으면 명확합니다. 데이터베이스 서버나 Node.js 서버가 핵심 구성 요소인 구조는 아닙니다.
| 기술 | 역할 | 실제 근거·버전 |
|---|---|---|
| C++23 | 공통 API, 데이터 접근, 작업 관리, 플러그인 구현 | 루트 CMakeLists.txt: CMAKE_CXX_STANDARD 23 |
| Pattern Language | 파일 구조를 정의하고 바이트를 필드로 해석 | lib/external/pattern_language, 서브모듈 커밋 300d738으로 버전 고정 |
| nlohmann/json | 설정과 구조화된 메시지 처리 | 동봉 lib/third_party/nlohmann_json/CMakeLists.txt: 3.11.3 |
| mbedTLS·libmagic | 암호 관련 기능·파일 종류 식별 | cmake/build_helpers.cmake: 3.4.0 이상, 5.39 이상 요구 |
| Capstone·YARA | 기계어 해석·규칙 기반 바이트 검색 | plugins/disassembler, plugins/yara_rules. 동봉 버전은 서브모듈 커밋을 참조하며, 시스템 Capstone도 선택 가능 |
| zlib 등 압축 라이브러리 | 압축 데이터 해제 기능을 선택적으로 제공 | plugins/decompress/CMakeLists.txt: ZLIB, BZip2, LibLZMA, ZSTD, LZ4 탐색 |
3.4.0 이상은 설치된 라이브러리의 확정 버전이 아니라 CMake의 최소 요구값입니다. 시스템 라이브러리를 쓰는 옵션과 동봉 코드를 쓰는 옵션도 다릅니다. 예를 들어 시스템 nlohmann/json은 find_package(nlohmann_json 3.10.2 REQUIRED)를 사용합니다.
| 기술 | 화면에서 하는 일 | 확인 위치 |
|---|---|---|
| Dear ImGui | 도킹 창, 버튼, 메뉴, 입력과 프레임 구성 | lib/third_party/imgui/imgui/include/imgui.h: 1.92.7 WIP |
| ImPlot·ImPlot3D | 2차원·3차원 그래프 | 각 include/implot.h, include/implot3d.h: 0.17, 0.3 WIP |
| ImNodes | 노드를 선으로 연결하는 데이터 처리 화면의 기반 | lib/third_party/imgui/imnodes. 버전 번호는 별도로 확정하지 않음 |
| GLFW·OpenGL | 네이티브 창·입력과 GUI 렌더링 | 루트 기본값 IMHEX_RENDERING_BACKEND="GLFW"; 백엔드 CMake에서 OpenGL 탐색 |
main/gui/source/window/window.cpp에서 frameBegin()은 ImGui::NewFrame()을 호출하고, frame()은 등록된 뷰를 그리며, frameEnd()는 렌더링을 마무리합니다. HTML DOM을 갱신하는 웹 UI와는 다른 실행 흐름입니다.
cmake_minimum_required()가 최종 기준입니다. CMakePresets.json의 3.20 표기만 보고 최소 버전을 판단하면 안 됩니다..github/workflows/tests.yml에서 빌드 실행과 컴파일 캐시를 담당합니다.ubuntu-26.04, gcc-16·g++-16, actions/checkout@v7과 재귀 서브모듈 체크아웃을 명시합니다.dist/vcpkg.json의 builtin-baseline은 9e593bb18ea69cc5095e012465dcd675a822ed0d입니다. 파일의 version: 1.0.0은 ImHex 버전이 아닙니다.dist/web/Dockerfile의 빌드 이미지가 emscripten/emsdk:6.0.6입니다. C++를 WebAssembly로 빌드하며 별도 React 앱으로 다시 구현하는 방식이 아닙니다.plugins/script_loader는 선택적 스크립트 지원입니다. FindCoreClrEmbed.cmake는 CORECLR_VERSION "10.0"을 찾으며, 전체 앱의 필수 런타임으로 해석하면 안 됩니다.README는 MSVC와 AppleClang을 지원하지 않는다고 안내합니다. 그러나 verifyCompiler()는 GCC 12 이상·Clang 17 이상을 검사하고, MSVC 분기는 비어 있습니다. build.yml에도 Windows MSVC 작업이 있습니다. 따라서 “CMake가 MSVC를 무조건 거부한다”고 설명할 수 없습니다.
반대로 이 작업 정의만으로 모든 MSVC 환경에서 빌드가 성공한다고 보장할 수도 없습니다. AppleClang은 허용된 GNU·Clang 식별자에 포함되지 않습니다. 빌드 가능 여부는 컴파일러·표준 라이브러리·의존성을 함께 확인해야 합니다.
버전 숫자를 읽을 때는 앱 버전, 동봉 라이브러리 버전, 최소 요구 버전, 서브모듈 커밋을 구별해야 합니다. 이 구분만 익혀도 낯선 C++ 저장소의 빌드 오류를 훨씬 정확히 해석할 수 있습니다.
main/gui/source/main.cpp의 main()은 메인 스레드 ID, 충돌 처리, 네이티브 초기화, 인스턴스 간 메시징을 준비한 뒤 init::runImHex()로 들어갑니다. 데스크톱 구현은 GLFW 초기화, 시작 화면의 초기화 작업, 메인 Window::loop(), 정리 순서로 진행됩니다.
그림에서 중요한 연결은 런타임에서 Provider로 향하는 선입니다. 해석기는 파일 핸들을 직접 열지 않아도 됩니다. ImHex가 공급하는 읽기·쓰기 콜백을 통해 현재 데이터에 접근합니다. 전체 연결을 한 번 파악한 뒤 아래 네 가지 설계를 읽으면 세부 코드의 목적이 드러납니다.
lib/libimhex/include/hex/providers/provider.hpp의 prv::Provider는 readRaw(), writeRaw(), getActualSize() 같은 인터페이스를 정의합니다. 화면이 구체적인 파일 구현 대신 이 인터페이스에 의존하므로 다른 데이터 원천을 붙일 수 있습니다. 같은 작업을 다른 구현에 맡기는 전략 교체의 관점으로 읽을 수 있습니다.
// Provider의 실제 선언 중 핵심 부분 발췌
virtual void read(u64 offset, void *buffer,
size_t size, bool overlays = true);
virtual void write(u64 offset, const void *buffer, size_t size);
virtual void readRaw(u64 offset, void *buffer, size_t size) = 0;
virtual void writeRaw(u64 offset, const void *buffer, size_t size) = 0;
[[nodiscard]] virtual u64 getActualSize() const = 0;
= 0인 함수는 구현을 하위 클래스에 맡깁니다. 호출자는 구체적인 파일·메모리 구현을 몰라도 같은 함수로 작업합니다.lib/libimhex/source/providers/provider.cpp의 Provider::read()는 기준 주소를 뺀 오프셋으로 readRaw()를 호출하고, 필요하면 오버레이를 적용합니다. 따라서 화면 주소와 파일 오프셋은 항상 같지 않습니다. 기준 주소가 0x1000이면 화면의 0x1004는 파일 오프셋 4에 해당합니다.
plugins/builtin/source/content/providers/file_provider.cpp의 FileProvider::open()은 기본 128_MiB 설정을 기준으로 접근 방식을 나눕니다. 그 이상이면 직접 파일 접근 경로를 선택하고, 처음에는 쓰기를 꺼 둔 뒤 화면에서 허용할 수 있게 합니다. 작은 일반 파일의 메모리 적재 경로와 큰 파일의 직접 접근 경로를 구별해야 합니다.
Provider::write()는 곧바로 writeRaw()를 호출하지 않습니다. 먼저 변경 이벤트를 발행합니다. ViewPatches가 이를 받아 이전·이후 바이트를 보관하는 OperationWrite를 만들고, 되돌리기 스택이 작업을 적용합니다.
근거는 plugins/builtin/source/content/views/view_patches.cpp, plugins/builtin/include/content/providers/undo_operations/operation_write.hpp, lib/libimhex/source/providers/undo/stack.cpp입니다. Stack::add()는 redo 기록을 비우고 새 작업의 redo()를 실행합니다. 새 편집 뒤에 예전 redo가 사라지는 이유를 코드로 설명할 수 있습니다.
FileProvider::writeRaw()는 메모리 적재 상태에서는 버퍼를 바꾸고, 직접 접근 상태에서는 파일 쓰기 함수를 호출합니다. 따라서 모든 파일이 저장 버튼을 누를 때까지 원본 디스크에 반영되지 않는다고 가정하면 안 됩니다. 실습에는 복사본을 사용하세요.
lib/libimhex/source/api/plugin_manager.cpp의 Plugin::Plugin(path)는 Windows에서는 LoadLibraryW(), 다른 데스크톱 환경에서는 dlopen()으로 라이브러리를 읽고 initializePlugin 등의 진입점을 찾습니다. Plugin::initializePlugin()은 요청한 호환 버전을 검사한 뒤 초기화 함수를 호출합니다.
기본 기능도 예외가 아닙니다. plugins/builtin/source/plugin_builtin.cpp의 IMHEX_PLUGIN_SETUP_BUILTIN 안에서 registerProviders(), registerViews(), registerPatternLanguageFunctions() 등을 호출합니다. 실행 파일에 기능을 직접 늘어놓기보다 등록 API를 통해 채우는 구조입니다.
// plugins/builtin/source/content/providers.cpp 발췌
ContentRegistry::Provider::add<FileProvider>(true);
ContentRegistry::Provider::add<IntelHexProvider>();
ContentRegistry::Provider::add<MemoryFileProvider>(true);
// plugins/builtin/source/content/views.cpp 발췌
ContentRegistry::Views::add<ViewHexEditor>();
ContentRegistry::Views::add<ViewPatternEditor>();
ContentRegistry::Provider::add<T>()는 타입 이름과 생성 함수를 등록합니다. ContentRegistry::Views::add<T>()는 뷰 객체를 만들어 등록합니다. 둘 다 std::derived_from 제약을 써서 올바른 기반 클래스를 상속한 타입만 받습니다. 등록소는 “무엇이 있는가”를 관리하고, 팩토리는 “어떻게 만들 것인가”를 담당합니다.
Registry는 공구 카탈로그이고 Factory는 주문받은 공구를 만들어 주는 창구입니다. 분석 화면은 공구의 제작법을 몰라도 카탈로그의 공통 규격을 통해 사용할 수 있습니다.
웹 빌드는 같은 플러그인을 모두 동적으로 읽는다고 보면 안 됩니다. dist/web/Dockerfile은 IMHEX_STATIC_LINK_PLUGINS=ON을 지정하고 script_loader;remote를 제외합니다. 플랫폼에 따라 확장 기능의 묶음과 로딩 방식이 달라집니다.
lib/libimhex/include/hex/api/event_manager.hpp에는 subscribe(), unsubscribe(), post()가 있습니다. 한 기능이 이벤트를 보내면 구독한 기능들이 반응합니다. 예를 들어 편집 계층은 변경 이벤트를 보내고, 패치 화면은 그것을 받아 편집 이력을 관리합니다.
// 학습용 의사 코드: 이벤트의 흐름만 요약
EventProviderDataModified::subscribe(owner, onBytesChanged);
EventProviderDataModified::post(provider, offset, size, bytes);
EventProviderDataModified::unsubscribe(owner);
여기서 post()라는 이름에 주의하세요. 구현은 해당 이벤트의 구독 목록을 순회하며 현재 호출 스레드에서 콜백을 직접 실행합니다. 메시지를 큐에 넣고 나중에 처리하는 비동기 버스가 아닙니다. std::recursive_mutex를 사용한다는 사실도 모든 구독자 상태의 안전을 자동으로 보장하지는 않습니다.
배울 점은 “발행자는 구독자를 몰라도 된다”는 결합도 완화와 그 대가입니다. 호출 관계가 파일 사이로 흩어지므로, 이벤트 이름을 검색해 구독자까지 함께 읽어야 실제 실행 경로가 보입니다. 객체가 사라질 때 구독 해제도 살펴봐야 합니다.
lib/libimhex/source/api/task_manager.cpp의 TaskManager::init()은 hardware_concurrency() 값에 따라 std::jthread 작업자를 만들고, 조건 변수와 작업 큐를 사용합니다. 긴 해석 작업을 화면 루프와 분리하는 기반입니다.
ViewPatternEditor::evaluatePattern()의 중요한 단계는 다음과 같습니다. 근거 파일은 plugins/builtin/source/content/views/view_pattern_editor.cpp입니다.
getRuntimeLock()을 잡고 런타임과 화면의 이전 실행 상태를 정리합니다.TaskManager::createTask()에 평가 작업을 넘깁니다.configureRuntime(runtime, provider)로 데이터 원천을 연결합니다.runtime.executeString()을 호출합니다.TaskManager::doLater()를 통해 EventPatternExecuted 발행을 예약합니다.// configureRuntime()의 데이터 연결 부분 발췌
runtime.setDataSource(
provider->getBaseAddress(), provider->getActualSize(),
[provider](u64 offset, u8 *buffer, size_t size) {
provider->read(offset, buffer, size);
},
[provider](u64 offset, const u8 *buffer, size_t size) {
if (provider->isWritable())
provider->write(offset, buffer, size);
}
);
TaskManager::doLater()는 콜백을 별도 목록에 저장하고, 화면의 Window::frameBegin()에서 runDeferredCalls()가 이를 실행합니다. 작업을 큐에 넣는 API와 이벤트를 즉시 발행하는 API를 구분해야 UI 스레드를 잘못 건드리지 않습니다.
getRuntime()은 PerProvider<pl::PatternLanguage>를 사용합니다. 여러 파일의 상태를 나누는 장치입니다. 반면 잠금은 동시에 같은 상태를 만지지 못하게 하는 장치입니다. 데이터를 나누는 것과 접근 순서를 정하는 것은 별개의 설계입니다.새 기능을 읽을 때는 데이터가 어디서 오고, 누가 상태를 소유하며, 어느 스레드에서 결과가 반영되는지를 함께 추적하세요. 이 세 질문으로 GUI·플러그인·언어 런타임의 연결을 설명할 수 있습니다.
main/gui는 창과 실행 수명을 관리합니다. 헥스 편집 기능 전체가 이곳에 있는 것은 아닙니다.lib/libimhex는 여러 플러그인이 공유할 계약과 기반 기능을 제공합니다.plugins/builtin은 그 계약을 이용해 실제 제품의 주요 화면과 기능을 구성합니다.예를 들어 버튼을 눌렀을 때 동작을 찾고 싶다면 main.cpp부터 전부 읽기보다 plugins/builtin/source/content/views/에서 화면을 찾으세요. 그 화면이 호출하는 hex/api와 Provider를 거슬러 올라가면 범위를 좁힐 수 있습니다.
아래는 커밋 34483cb의 파일을 확장자별로 센 물리 줄 수입니다. 줄바꿈 문자 LF를 세므로 마지막에 줄바꿈이 없는 줄은 제외합니다. 빈 줄·주석을 포함하며, lib/third_party/와 lib/external/은 제외했습니다. 글꼴·아이콘 헤더 등 선언이 많은 파일은 포함되므로 순수 기능 구현량과 같지는 않습니다.
| 언어·파일 종류 | 파일 수 | 물리 줄 수 |
|---|---|---|
| C++·공통 헤더 | 512 | 117,457 |
| Objective-C | 2 | 909 |
| C# | 7 | 545 |
| Python | 6 | 745 |
| JavaScript | 2 | 296 |
| GLSL | 8 | 209 |
| CMake | 52 | 4,338 |
C++·공통 헤더의 집계 대상은 .cpp·.hpp·.h·.cppm입니다.
| C++·헤더의 주요 위치 | 파일 수 | 물리 줄 수 |
|---|---|---|
| plugins/builtin | 196 | 46,412 |
| lib/libimhex | 174 | 30,491 |
| plugins/ui | 32 | 15,416 |
| main | 26 | 5,414 |
| tests | 13 | 2,274 |
외부 디렉터리까지 포함해 존재하는 C++·헤더 파일을 세면 738개, 473,305줄입니다. 이 차이는 동봉 GUI·JSON 등 외부 코드의 비중을 보여줍니다. 미전개 서브모듈 본체는 어느 수치에도 포함되지 않으므로 전체 의존성까지 합친 완전한 제품 코드량이라고 부를 수 없습니다.
디렉터리별 줄 수를 먼저 나누면, 수십만 줄의 저장소도 지금 읽을 책임 범위로 좁힐 수 있습니다. 처음에는 main 전체보다 Provider → FileProvider → configureRuntime의 짧은 경로부터 읽는 편이 좋습니다.
ContentRegistry::Views::add()는 std::make_unique<T>()로 뷰를 만들고 등록소에 넘깁니다. Provider 생성 API에는 std::shared_ptr도 등장합니다. 같은 스마트 포인터라도 소유자가 하나인가, 여러 곳이 함께 수명을 유지해야 하는가라는 질문은 다릅니다.
실습 아이디어: lib/libimhex/include/hex/api/content_registry/views.hpp와 lib/libimhex/include/hex/api/content_registry/provider.hpp를 나란히 읽고, 생성 함수의 반환 타입과 보관 타입을 표로 정리하세요. 이어서 런타임 콜백의 [provider] 캡처가 데이터 자체의 복사인지 포인터 값의 복사인지 설명해 보세요. 포인터를 캡처한다고 대상의 수명이 자동 연장되는 것은 아닙니다.
앞의 예제에서 01 00은 little endian u16으로 1입니다. 순서를 반대로 해석하면 256이 됩니다. C++ 구조체의 sizeof는 패딩 때문에 파일 형식의 길이와 다를 수 있으므로, 파일의 레이아웃과 메모리 레이아웃을 혼동하지 마세요.
실습 아이디어: tests/algorithms/source/endian.cpp의 changeEndianness<u32>() 검사와 tests/helpers/source/encoding.cpp의 UTF-16 LE·BE 사례를 읽으세요. 같은 바이트가 정수·문자·문자열 중 무엇으로 해석되는지 별도로 적어 보면 바이트 순서와 문자 인코딩의 차이가 보입니다.
ImGui의 즉시 모드에서는 매 프레임 UI를 기술합니다. 그렇다고 사용자 상태가 사라지는 것은 아닙니다. 파일별 콘솔·편집기 상태는 PerProvider<T> 같은 저장 공간에 남습니다. lib/libimhex/include/hex/providers/provider_data.hpp에서 Provider 열기 이벤트와 상태 생성의 연결을 볼 수 있습니다.
실습 아이디어: 두 파일을 연 상황을 종이에 그려 보세요. 탭 A의 오류 메시지가 탭 B에 보이지 않으려면 어떤 상태가 전역이고 어떤 상태가 Provider별이어야 할지 분류합니다. 그 뒤 view_pattern_editor.cpp의 .get(provider) 호출을 찾아 예상과 비교하세요.
Task::update()는 진행률을 갱신하면서 중단 요청을 확인하고, 요청이 있으면 TaskInterruptor를 던집니다. 취소 버튼을 만드는 일은 버튼만 그리는 일이 아닙니다. 반복 작업이 적절한 간격으로 취소 상태를 확인해야 합니다.
실습 아이디어: Task::interrupt(), Task::update(), 작업자의 예외 처리 분기를 이어 읽으세요. “중단 요청 → 작업의 확인 → 정리 → 완료 상태 반영”을 네 칸 도식으로 정리합니다. 잠금을 오래 잡을 때 어떤 화면 동작이 기다릴지도 예상해 보세요.
InformationByteAnalysis::process()는 선택한 영역을 한 번 순회하면서 바이트 분포·종류 분포·구간 엔트로피를 함께 갱신합니다. 파일은 plugins/builtin/source/content/data_information_sections.cpp입니다. 엔트로피 구간 크기 설정이 0이면 256바이트를 사용합니다.
# diagrams.hpp의 calculateEntropy()를 수식으로 풀면
p[b] = 바이트 b의 등장 횟수 / 구간의 바이트 수
H = -sum(p[b] * log2(p[b]))
화면에 쓰는 정규화 값 = min(1, H / 8)
# 같은 바이트만 반복: 0에 가까움
# 256종류가 고르게 등장: 1에 가까움
수식 구현은 plugins/builtin/include/content/helpers/diagrams.hpp의 DiagramChunkBasedEntropyAnalysis::calculateEntropy()에 있습니다. 화면의 힌트는 평균값이 0.83보다 크고 최고 구간값이 0.9보다 클 때 나타납니다. 압축 데이터와 암호화 데이터는 모두 높은 값을 보일 수 있으므로 그 숫자만으로 암호화 여부를 확정할 수 없습니다.
실습 아이디어: 0만 반복하는 파일과 0~255가 반복하는 파일을 같은 길이로 준비한다고 가정하고 분포를 먼저 예상하세요. 구간 길이를 줄였을 때 값이 어떻게 달라질지도 설명해 보면 통계의 표본 크기를 배울 수 있습니다.
이 커밋에는 MCP 연동도 있습니다. plugins/builtin/source/content/mcp_tools.cpp는 파일 열기·열린 데이터 목록·데이터 선택·읽기 도구를 등록합니다. ViewPatternEditor::registerHandlers()에는 패턴 실행·콘솔 조회·결과 조회 도구 등록도 있습니다.
plugins/builtin/romfs/mcp/tools/read_data.json은 입력 스키마를 정의하며, 구현은 한 번의 읽기를 16_MiB로 제한하고 결과 바이트를 Base64로 돌려줍니다. 여기서 Base64는 암호화가 아니라 JSON 문자열로 옮기기 위한 인코딩입니다.
실습 아이디어: 읽기 도구의 스키마와 콜백을 비교해 보세요. “문법상 숫자”라는 조건과 “현재 파일 범위에 들어가는 유효한 주소”라는 조건을 구분해 입력 검증 표를 만듭니다. 서버 구현의 JSON-RPC 오류 처리만으로 모든 도구 인자가 검증된다고 가정하지 않는 것이 핵심입니다.
이 저장소의 학습 가치는 개별 라이브러리를 쓰는 법보다, 타입·상태·작업·입력 검증을 서로 연결하는 방법에 있습니다.
tests/helpers/source/common.cpp의 TestProvider_read는 8바이트 데이터에서 오프셋 7부터 2바이트를 읽는 경계 초과 요청을 검사합니다. 대상 버퍼가 바뀌지 않는지도 확인합니다. 성공한 값만 비교하는 테스트보다 API의 실패 계약을 더 잘 드러냅니다.
.github/workflows/tests.yml은 AddressSanitizer·LeakSanitizer·UndefinedBehaviorSanitizer를 켜고 ctest --output-on-failure를 실행하도록 정의합니다. IMHEX_OFFLINE_BUILD=ON일 때 일부 네트워크 테스트는 tests/helpers/CMakeLists.txt에서 제외됩니다. 테스트 구성의 존재와 실제 통과 여부는 별개의 정보입니다.
| 항목 | README 기준 안내 | 읽을 때 주의할 점 |
|---|---|---|
| Windows | Windows 7 이상, 10/11 권장 | 배포 파일별 의존성과 지원 범위를 별도로 확인해야 함 |
| macOS | 배포 바이너리 기준 macOS 15 이상 | 이전 버전은 직접 빌드 가능성을 안내하지만 호환성 보장은 아님 |
| Linux·FreeBSD | 현대적인 Linux 배포판, FreeBSD 14.3 테스트 안내 | 배포판별 패키지·AppImage·Flatpak·Snap 및 직접 빌드 경로가 다름 |
| CPU | x86·AMD64·ARM64 공식 지원 안내 | 다른 little endian CPU의 가능성은 README 설명이며 별도 검증이 필요 |
| GPU | 일반 빌드에서 OpenGL 3.0 이상 | 일부 Windows Intel HD 드라이버의 그래픽 문제를 README가 안내 |
| 메모리·저장 공간 | 약 50MiB·약 100MiB | 앱의 기본 사용 안내값. 큰 파일 분석·패턴 결과·편집 이력의 상한이 아님 |
-NoGPU 릴리스는 README 기준 소프트웨어 렌더링 대안입니다. GPU 가속보다 느릴 수 있습니다. 머신러닝 모델을 돌리는 도구가 아니므로 CUDA GPU나 대용량 VRAM을 기본 조건으로 삼을 이유는 없습니다.
FileProvider::open()의 128_MiB는 메모리 적재 여부를 가르는 기본 설정값입니다. 전체 앱의 메모리 한도나 최대 파일 크기가 아닙니다. 패턴이 생성하는 객체 수, 문자열·배열 크기, 통계 버퍼, 이전·이후 바이트를 보관하는 편집 이력이 추가 비용을 만듭니다.
책 한 권의 무게만 보고 공부방 전체의 무게를 알 수는 없습니다. 원본 파일은 책이고, 해석한 필드·그래프·되돌리기 기록은 노트와 복사본입니다. 분석 방식에 따라 부가 자료가 더 커질 수 있습니다.
루트 기준 CMake는 3.25 이상이며 C++23을 요구합니다. verifyCompiler()의 하한은 GCC 12·Clang 17이지만, Linux 테스트 CI와 Linux 빌드 안내는 GCC 16을 사용합니다. 최소 버전 검사를 통과하는 것과 모든 최신 코드가 성공적으로 컴파일되는 것은 같은 약속이 아닙니다.
서브모듈과 시스템 의존성도 필요합니다. detectBadClone()는 외부 의존성 디렉터리가 비어 있으면 오류를 냅니다. 빌드 학습에서는 먼저 .gitmodules, dist/compiling/, CI 설정을 읽고 툴체인 조합을 정리하는 것이 좋습니다. 구체적인 빌드 시간·필요 RAM·동시 작업 수는 CPU와 환경에 따라 달라 고정 수치로 보장할 수 없습니다.
dist/web/Dockerfile은 의존성에도 -pthread를 붙이며 wasm32-emscripten을 사용합니다. 웹에서는 registerProviders()의 OS_WEB 조건 때문에 데스크톱과 같은 Provider가 모두 등록되지 않습니다. 브라우저 권한과 샌드박스가 있으므로 디스크·프로세스 메모리·원격 기능을 그대로 기대하면 안 됩니다.
아래 다섯 과제는 읽기에서 시작해 작은 구현으로 확장합니다. 1~3번은 배포 앱과 별도 학습 파일을 사용할 수 있고, 4~5번은 소스를 수정할 수 있는 별도 실습 환경을 전제로 합니다. 과제의 완료 기준을 코드 작성량이 아니라, 입력을 바꿨을 때 결과가 왜 달라지는지 설명할 수 있는가로 잡으세요.
목표: 오프셋·정수 크기·little endian을 연결합니다.
할 일: 아래 명령은 새 학습 파일을 만드는 예입니다. 파일을 ImHex로 열고, 0x04에서 2바이트를 선택해 버전 값을 확인합니다. 복사본의 01 00을 02 00으로 바꿔 보세요.
python3 - <<'PY'
from pathlib import Path
raw = bytes.fromhex(
"49 4D 48 58 01 00 03 00 06 00 00 00 41 42 43 44 45 46"
)
Path("demo.bin").write_bytes(raw)
PY
완료 기준: 총 길이 18바이트, 헤더 길이 12바이트, 내용 시작 0x0C를 설명합니다. 0x04의 두 바이트를 big endian으로 해석했을 때 256이 되는 이유도 적습니다.
소스 연결: tests/algorithms/source/endian.cpp에서 순서 변환의 기대값을 읽습니다.
목표: 숫자 배열과 구조 정의를 연결합니다.
할 일: 1절의 DemoHeader를 Pattern Editor에 입력하고 실행합니다. 결과에서 version과 payload_size의 위치를 확인한 뒤, 다음 선언을 덧붙여 내용 영역도 표시합니다.
char payload[header.payload_size] @ 0x0C;
완료 기준: version=1, count=3, payload_size=6과 내용 6바이트의 범위가 일치합니다. 버전 바이트를 바꾸고 재평가했을 때 결과가 달라지는 이유를 설명합니다.
추가 질문: 내용 길이만 20으로 바꾸면 실제 남은 6바이트와 모순됩니다. 오류 표시와 원본 바이트를 비교하고, 유효한 파일이 되려면 어떤 검사가 필요한지 적습니다.
소스 연결: configureRuntime()의 setDataSource() 콜백에서 읽기 요청의 경계를 찾습니다.
목표: 이벤트와 되돌리기 기록을 연결합니다.
할 일: Provider::write() → EventProviderDataModified → ViewPatches → Stack::add() → OperationWrite::redo()를 읽고, 함수마다 입력·상태 변경·다음 호출을 한 줄씩 적습니다.
확인 방법: 학습 파일의 버전 바이트를 수정하고 Undo·Redo를 사용합니다. 메모리 버퍼 변경과 디스크 저장은 별도 항목으로 기록하세요.
완료 기준: “새 수정 뒤에 예전 redo를 왜 지우는가”와 “왜 writeRaw가 아니라 write를 호출하는가”를 소스 경로와 함께 설명합니다.
목표: 데이터 원천과 화면을 분리하는 계약을 익힙니다.
할 일: 먼저 lib/libimhex/include/hex/test/test_provider.hpp와 tests/helpers/source/common.cpp를 읽습니다. 별도 실습 코드에서 8바이트 배열을 읽기 전용 데이터 원천으로 제공하는 클래스를 설계하세요. 실제 플러그인 연결은 SDK 환경을 갖춘 다음 단계입니다.
필수 사례: 시작에서 읽기, 끝에 정확히 닿는 읽기, 범위를 넘는 읽기, 빈 읽기, 기준 주소가 0이 아닌 경우를 표로 만듭니다.
완료 기준: 데이터 크기를 넘는 요청이 버퍼를 어떻게 처리해야 하는지 정의하고 테스트합니다. read()의 주소와 readRaw()의 오프셋을 구별해 설명합니다.
목표: 작업 실행·취소·UI 반영을 분리합니다.
할 일: 플러그인 SDK 실습 환경에서 작은 파일의 바이트 빈도 256칸을 계산하는 기능을 설계합니다. 계산은 TaskManager 작업으로 보내고 반복 중 중단 요청을 확인합니다. 완료 결과는 소유권을 정한 뒤 UI에 반영합니다.
확인 방법: InformationByteAnalysis::process(), Task::update(), TaskManager::doLater()를 참고합니다. 계산 중 탭 전환·파일 닫기·취소를 했을 때 작업이 참조하는 데이터의 수명을 먼저 정하세요.
완료 기준: 0만 있는 입력에서는 빈도 0번 칸만 증가하고, 취소 뒤 불완전한 결과가 정상 완료처럼 표시되지 않습니다. 닫힌 Provider를 뒤늦게 접근하지 않도록 수명 전략도 문서화합니다.
화면 캡처만 남기기보다 바이트·필드·함수의 대응을 기록하세요. 이 세 가지가 있으면 결과가 틀렸을 때 파일 형식 해석, 주소 계산, UI 표시 중 어디부터 확인할지 정할 수 있습니다.
주당 3~5시간 정도를 가정한 학습 제안입니다. C++가 처음이라면 1~2주차를 반복해도 좋습니다. 매주 새로운 기능을 늘리기보다, 같은 작은 바이너리를 더 깊은 계층에서 설명하는 방식으로 공부하세요.
| 주차 | 배울 개념·읽을 코드 | 남길 결과물 |
|---|---|---|
| 1주차 | 16진수, 정수 크기, 엔디언, 인코딩. tests/algorithms/source/endian.cpp |
18바이트 파일을 오프셋·길이·값으로 나눈 표 |
| 2주차 | 상속, 가상 함수, 스마트 포인터. provider.hpp와 FileProvider::readRaw() |
Provider 공통 계약과 파일별 구현을 나눈 그림 |
| 3주차 | Registry, Factory, 플러그인 진입점. registerViews(), Plugin::initializePlugin() |
새 뷰가 생성되어 화면 루프에 도달하는 호출 경로 |
| 4주차 | Pattern Language, 콜백, 오류 결과. configureRuntime(), evaluatePattern() |
정상·길이 오류·엔디언 오류를 비교한 패턴 실습 기록 |
| 5주차 | 뮤텍스, 작업 큐, 중단 요청, UI 스레드. task_manager.cpp, doLater() |
상태별 소유 스레드 표와 취소 시나리오 |
| 6주차 | CMake 타깃, 의존성, CTest, sanitizer. 루트 빌드 설정과 tests.yml |
플러그인 또는 테스트 하나의 설계·검증 보고서 |
virtual이 어렵다면 파일·메모리를 동일한 함수로 읽는 짧은 예제부터 복습합니다.add<ViewHexEditor>()의 타입 인자가 무엇을 결정하는지 설명하는 데 집중합니다.언어 도구에 관심이 있다면 별도 PatternLanguage 저장소의 렉서·파서·평가기와 오류 위치 전달을 공부합니다. ImHex의 연결 코드와 언어 본체를 구분해 읽는 것이 출발점입니다.
시스템 도구에 관심이 있다면 Provider의 주소 변환·파일 접근·권한·원격 데이터 경계를 공부합니다. 같은 읽기 인터페이스라도 지연 시간과 실패 방식은 달라집니다.
데스크톱 제품에 관심이 있다면 ImGui 프레임, 탭별 상태, 단축키, 로컬라이제이션, 패키징을 따라갑니다. 기능 구현 이후 사용자에게 전달하기까지 필요한 층을 배울 수 있습니다.
| 용어 | 쉬운 설명 |
|---|---|
| 바이너리 | 바이트로 저장된 데이터. 실행 파일뿐 아니라 이미지·압축 파일·저장 데이터도 포함합니다. |
| 헥스 편집기 | 바이트를 16진수로 보고 바꾸는 편집기입니다. |
| 오프셋 | 시작점에서 떨어진 거리입니다. 파일의 첫 바이트는 오프셋 0입니다. |
| 기준 주소 | 데이터를 화면의 어느 주소부터 배치할지 정하는 값입니다. 파일 오프셋과 구별합니다. |
| 엔디언 | 여러 바이트짜리 숫자를 어떤 바이트 순서로 저장하고 읽을지 정한 규칙입니다. |
| 매직 바이트 | 파일 종류를 알아보는 단서가 되는 특정 바이트 배열입니다. 이름만으로 파일의 무결성을 보장하지는 않습니다. |
| Pattern Language | 파일의 필드·배열·구조를 정의해 바이트를 해석하는 ImHex의 별도 언어입니다. |
| Provider | 파일·메모리 등 데이터 원천을 공통 읽기·쓰기 계약으로 감싸는 객체입니다. |
| 콜백 | 어떤 시점에 호출하도록 다른 코드에 건네는 함수입니다. |
| Registry | 사용 가능한 화면·타입·함수·설정 등을 모아 두는 등록소입니다. |
| Factory | 구체적인 객체 생성 절차를 호출자 대신 맡는 코드입니다. |
| 플러그인 | 공통 API와 진입점을 통해 프로그램에 기능을 보태는 모듈입니다. |
| ABI | 컴파일된 코드끼리 함수·객체를 주고받는 이진 수준의 약속입니다. C++ 플러그인 호환성과 관련됩니다. |
| 발행·구독 | 이벤트를 보내는 쪽과 반응하는 쪽을 등록 관계로 연결하는 방식입니다. |
| 되돌리기 스택 | 편집의 이전·이후 상태나 작업을 저장해 Undo·Redo를 가능하게 하는 구조입니다. |
| 오버레이 | 원본 위에 별도의 바이트 내용을 겹쳐 읽을 수 있게 하는 층입니다. |
| 뮤텍스 | 동시에 접근하면 안 되는 공유 상태 앞에서 순서를 정해 주는 잠금입니다. |
| 데이터 경쟁 | 여러 스레드가 동기화 없이 같은 메모리에 접근하며 적어도 하나가 쓰기인 상황입니다. C++에서는 정의되지 않은 동작을 일으킬 수 있습니다. |
| 작업 큐 | 할 일을 줄에 넣고 작업자 스레드가 가져가 실행하도록 만든 구조입니다. |
| 즉시 모드 GUI | 매 프레임 코드를 통해 화면 구성을 기술하는 방식입니다. 앱의 상태 저장 자체가 없어지는 것은 아닙니다. |
| 엔트로피 | 바이트 분포가 얼마나 다양하고 고른지 나타내는 통계량입니다. 이 코드에서는 8로 나눠 정규화합니다. |
| 디스어셈블 | 기계어 바이트를 어셈블리 명령으로 해석하는 작업입니다. |
| YARA | 문자열·바이트 패턴과 조건을 규칙으로 적어 데이터를 찾는 도구입니다. |
| MCP·JSON-RPC | MCP는 AI 도구 연결에 쓰이는 프로토콜이며, JSON-RPC는 메서드·인자·결과를 JSON 메시지로 주고받는 규약입니다. |
| WebAssembly | C++ 등의 코드를 브라우저 같은 실행 환경에서 구동할 수 있게 하는 바이너리 명령 형식입니다. |
| 서브모듈 | 다른 Git 저장소를 특정 커밋으로 참조하는 방식입니다. 상위 저장소만 받으면 본체가 비어 있을 수 있습니다. |
| sanitizer | 실행 중 메모리 오류·누수·정의되지 않은 동작 등을 찾도록 계측하는 도구입니다. 모든 오류를 보장해 검출하지는 않습니다. |
아래 링크의 파일 경로는 저장소 루트 기준이며, 모두 분석 커밋에 고정되어 있습니다. 함수 이름으로 검색하면 본문에서 설명한 연결을 확인할 수 있습니다.
loadVersion(), detectBadClone(), verifyCompiler(), 의존성 구성.configureRuntime()와 해석기의 데이터 연결.| 항목 | 상태·해석 |
|---|---|
| 저장소 | WerWolv/ImHex · 주 언어 C++ |
| 선정 위치 | GitHub Trending C++ Weekly #9 · 2026-10-23 분석 기준 |
| 스타 / 포크 | 55,522 / 2,565 · 2026-10-23 GitHub 기준 |
| 생성일 | 2020-11-10 |
| 최신 릴리스 | v1.38.1 · 2025-12-21 |
| 최근 push | 2026-10-09 · GitHub 저장소 메타데이터 기준 |
| 분석한 최근 커밋 | 34483cb · 2026-10-08 · Pattern Editor의 데이터 경쟁·충돌 수정 (#2930) |
| 소스 버전 | VERSION은 1.39.0.WIP · 릴리스와 다른 개발 상태 |
| 코드 규모 | 외부 디렉터리 제외 C++·헤더 512개, 117,457줄. Objective-C 909줄, C# 545줄, Python 745줄 등 |
| 라이선스 | GitHub 표기 GPL-2.0. README는 대부분 GPLv2-only, lib/libimhex와 plugins/ui는 LGPLv2.1 예외로 설명하며 각 LICENSE 파일도 분리 |
최근 push 날짜는 저장소의 갱신 시각이고 커밋 날짜는 해당 커밋에 기록된 시각이므로 서로 다를 수 있습니다. 코드 규모는 빈 줄·주석을 포함한 물리 줄 수이며, 날짜·스타·포크와 달리 위에서 정의한 소스 범위의 집계입니다.
버전: 이 문서의 함수와 설정은 개발 커밋 기준입니다. v1.38.1 배포 파일에 MCP 도구나 같은 UI·기본값이 모두 있다고 가정하면 안 됩니다. 별도 PatternLanguage 서브모듈 본체의 내부 알고리즘과 성능은 이 설명의 범위 밖입니다.
정확도와 성능: 패턴은 작성한 구조 가정에 따라 해석합니다. 엔트로피 힌트는 암호화 판정의 증거가 아니며, README의 약 50MiB 메모리 안내는 복잡한 분석 작업의 보장치가 아닙니다. 하드웨어 표와 CI 정의는 실제 환경별 실행·호환성 측정을 대체하지 않습니다.
데이터와 연결: 직접 파일 접근 경로에서는 수정이 원본에 반영될 수 있습니다. 복사본으로 연습하세요. 로컬 분석이 가능해도 모든 기능이 무조건 오프라인이라는 뜻은 아닙니다. Store·업데이트·원격 기능과 함께 PRIVACY.md의 선택적 통계·동의 기반 충돌 로그 안내를 확인하세요.
라이선스: 루트 GPL과 일부 LGPL 예외, 외부 라이브러리의 조건이 나뉩니다. 재사용할 때는 루트 LICENSE, libimhex LICENSE, UI LICENSE와 해당 의존성의 원문을 함께 읽어야 합니다.