WebAssembly 모듈 인스펙터
WebAssembly 모듈은 매직 \0asm과 버전으로 이루어진 8바이트 프리앰블 뒤에, 아이디 한 바이트와 LEB128 길이, 그리고 그 길이만큼의 바이트로 이루어진 섹션이 이어지는 구조입니다. 이 도구는 그 순서를 브라우저 안에서 그대로 따라가며 모듈이 스스로 선언한 내용을 보여줍니다. 버전, 파일 순서대로 나열한 모든 섹션과 각 섹션의 바이트 크기·오프셋·항목 수, 전체 타입이 붙은 임포트와 익스포트, 페이지와 바이트로 환산한 메모리 한계, start 함수, 그리고 끝에 붙는 커스텀 섹션까지 확인할 수 있습니다. 바이너리를 열어 보는 가장 흔한 이유는 크기입니다. 대부분의 빌드에서는 code 섹션이 파일의 대부분을 차지하지만 항상 그런 것은 아닙니다. Go 모듈은 data 섹션만 1MB를 넘기기도 하고, 에셋을 포함한 emscripten 빌드는 데이터가 더 클 수 있으며, 디버그 빌드는 실행에 아무 영향도 주지 않는 커스텀 섹션에 무게를 숨겨 둡니다. 임포트는 호스트가 지켜야 하는 계약입니다. emscripten은 env와 wasi_snapshot_preview1을, wasm-bindgen은 생성된 자바스크립트 글루 파일 경로를, Go는 gojs를 요구하며, 하나라도 채워지지 않으면 인스턴스화 시점에 LinkError가 납니다. 이름이 지워진 바이너리에서는 두 커스텀 섹션이 가장 많은 정보를 담고 있습니다. name 섹션은 모듈과 함수 이름을 되살려 주고, producers 섹션은 어떤 컴파일러와 도구가 이 파일을 만들었는지 기록합니다. 둘 다 있으면 여기서 해독해 보여 줍니다. 다만 이 도구는 모듈이 무엇을 하는지는 알려 주지 못합니다. code 섹션은 크기만 재고 디스어셈블하지 않으며 아무것도 실행하지 않습니다. 따라서 함수 본문 안에서만 쓰이는 기능, 예를 들어 SIMD 연산이나 원자적 명령은 시그니처나 리미트 플래그에 드러나지 않는 한 표시되지 않습니다. 표시되는 기능 목록은 인코딩이 증명하는 것만 담습니다.
사용법
- .wasm 파일을 상자에 끌어다 놓거나 상자를 눌러 선택하세요. 파일은 페이지 안에서만 읽히고 업로드되지 않습니다.
- 섹션 표를 먼저 보세요. 각 섹션의 바이트 크기와 파일에서 차지하는 비율이 나오므로 "이 모듈이 왜 3MB인가"에 대한 답이 됩니다.
- 임포트 목록은 호스트가 채워야 하는 계약입니다. 모듈 이름, 필드 이름, 종류, 그리고 정확한 시그니처를 확인하세요.
- 익스포트 목록은 자바스크립트에서 호출할 수 있는 API입니다. 매개변수와 반환 타입은 모듈의 type 섹션에서 그대로 읽어 옵니다.
- 메모리 패널에서 초기 페이지 수와 최대 페이지 수를 확인하고(한 페이지는 65,536바이트), 커스텀 섹션에서 name이나 producers 섹션이 남아 있는지 살펴보세요.
자주 묻는 질문
- .wasm 파일이 왜 이렇게 큰가요?
- 먼저 섹션 표를 보세요. 일반적인 러스트나 C 빌드에서는 code 섹션이 파일의 70~90%를 차지하며, 이 경우 해답은 코드 자체를 줄이는 것입니다. 제네릭 인스턴스화를 줄이고, 패닉 메시지 포매팅을 빼고, wasm-opt -Oz를 돌리는 식입니다. 하지만 구성이 예상과 다른 경우도 많습니다. Go 빌드는 런타임 정적 데이터가 함께 들어가 data 섹션이 크고, 파일을 내장한 emscripten 빌드는 데이터가 대부분이며, DWARF 디버그 정보를 남긴 빌드는 .debug_info 같은 커스텀 섹션에 그 무게를 담고 있습니다. 마지막 경우는 실행에 전혀 쓰이지 않으므로 wasm-strip으로 지우면 됩니다.
- 메모리가 "17..∞ 페이지"라고 나오면 무슨 뜻인가요?
- WebAssembly 메모리는 65,536바이트짜리 페이지 단위로 셉니다. 초기값 17페이지는 인스턴스화 순간에 약 1.1MB를 잡는다는 뜻입니다. 두 번째 숫자는 선언된 최대값이며, 없으면 호스트가 허용하는 한 계속 늘릴 수 있습니다. 32비트 WebAssembly에서 그 한계는 65,536페이지, 즉 4GiB입니다. 최대값이 적혀 있다고 해서 메모리를 아낀다는 뜻은 아니고, 호스트가 주소 공간을 미리 잡아 둘 수 있는 상한일 뿐입니다. 스레드와 함께 쓰는 공유 메모리는 최대값이 반드시 필요합니다.
- 임포트와 익스포트 이름이 전부 a, b, c인 이유는 무엇인가요?
- 최적화와 Closure를 켠 emscripten 빌드의 특징입니다. 임포트와 익스포트 이름은 바이너리 안에 문자열로 들어가므로 짧게 줄이면 파일이 작아지고, 함께 배포되는 자바스크립트 글루도 같은 이름으로 축약됩니다. 런타임에는 아무 문제가 없지만 바이너리만 보면 읽을 수 없습니다. 함수 인덱스를 원래 이름으로 되돌려 주는 name 커스텀 섹션도 같은 단계에서 지워집니다. 남아 있으면 이 도구가 풀어 주고, 없으면 producers 섹션이 누가 만들었는지 알려 주는 마지막 단서인 경우가 많습니다.
- env와 wasi_snapshot_preview1을 임포트하던데 무엇을 넣어 줘야 하나요?
- 임포트 항목은 호스트 객체가 모듈 이름과 필드 이름으로 채워야 하는 자리이며, 엔진이 인스턴스화 시점에 타입까지 검사합니다. env는 emscripten이 자바스크립트에서 받기를 기대하는 C 런타임 콜백을 모아 두는 이름 공간이고, wasi_snapshot_preview1은 WASI를 대상으로 빌드했다는 뜻이라 브라우저에서는 WASI 심이 필요합니다. gojs는 Go 런타임의 다리이며, ./something_bg.js 같은 경로는 wasm-bindgen이 생성한 글루 모듈을 기다린다는 뜻입니다. 하나라도 빠지거나 인자 수가 다르면 WebAssembly.instantiate가 그 이름을 짚어 LinkError를 던집니다.
- 이 도구가 모듈을 실행하거나 디스어셈블하나요?
- 둘 다 하지 않습니다. 섹션, 타입, 임포트, 익스포트, 리미트, start 함수 인덱스, 커스텀 섹션 같은 선언만 해석합니다. 함수 본문은 개수와 전체 크기만 재고 명령어는 해독하지 않으며, 엔진을 만들지 않으므로 악성 모듈이라도 여기서는 아무 일도 하지 못합니다. 대신 동작은 보이지 않습니다. 어떤 함수가 무엇을 계산하는지, 어떤 임포트가 실제로 호출되는지, 실행 중 메모리를 얼마나 쓰는지, 타입에 드러나지 않는 SIMD나 원자적 명령이 본문에 있는지는 알 수 없습니다.
- 코드를 읽지 않는데 참조 타입이나 다중 반환값을 쓴다고 어떻게 말할 수 있나요?
- 일부 MVP 이후 제안은 명령어뿐 아니라 인코딩 자체의 모양을 바꾸기 때문입니다. 반환값이 둘 이상인 함수 타입은 다중 반환값이고, 시그니처의 externref나 두 번째 테이블은 참조 타입이며, 메모리 리미트 플래그의 세 번째 비트는 스레드에 필요한 공유 비트입니다. data count 섹션이나 액티브가 아닌 패시브 세그먼트는 벌크 메모리에서만 나타나고, 시그니처의 v128은 SIMD입니다. 바이트가 증명하는 것만 표시하며, 함수 본문 안 옵코드로만 등장하는 기능은 아예 주장하지 않습니다. 그래서 이 목록은 실제로 쓰인 기능보다 짧을 수 있습니다.
관련 도구
GIF 구조 분석기
GIF 파일의 블록 구조를 그대로 읽어 프레임 지연과 폐기 방식, 반복 설정, 팔레트, 투명도, 프레임별 실제 바이트 비용까지 보여줍니다.
EditorConfig 테스터
.editorconfig와 파일 경로를 붙여넣으면 각 파일에 최종 적용되는 속성과, 그 값을 정한 섹션과 줄 번호를 보여줍니다.
.gitignore 테스터
.gitignore와 경로 목록을 붙여넣으면 어떤 경로가 무시되는지, 그리고 몇 번째 줄이 그렇게 결정했는지 git check-ignore -v처럼 보여줍니다.
ELF 바이너리 검사기
리눅스 실행 파일·.so·.o를 브라우저에서 엽니다. 아키텍처, 필요한 공유 라이브러리, 빌드 ID, PIE·NX·RELRO 적용 여부까지.
폰트 파일 검사기
.ttf, .otf, .woff 파일을 열어 실제 패밀리 이름, 글리프·문자 수, 버전, 라이선스 문구와 임베딩 허용 범위를 확인합니다.
JSON 스키마 검사기
JSON 문서를 JSON 스키마로 브라우저에서 검사합니다. 오류마다 JSON 포인터, 문서에서의 줄 번호, 실패한 키워드를 보여줍니다.