최신 도구로 WebAssembly 디버깅

Ingvar Stepanyan
Ingvar Stepanyan

지금까지의 도로

1년 전 Chrome은 Chrome DevTools에서 네이티브 WebAssembly 디버깅을 초기 지원한다고 발표했습니다.

기본적인 걷기에 대한 지원을 시연하고 기회에 대해 이야기했습니다. DWARF 정보 대신 소스 맵은 향후 공개될 예정입니다.

  • 변수 이름 확인
  • pretty print 적용 유형
  • 출발어로 표현식 평가
  • 그 외에도 다양한 기능 제공

오늘은 약속드린 기능이 실현되고 Emscripten 및 Chrome DevTools팀이 올해 특히 C 및 C++ 앱을 위해 이루어낸 진전을 소개하게 되어 기쁩니다.

아직 베타 버전이라는 점에 유의해 주시기 바랍니다. 사용하려면 모든 도구의 최신 버전을 사용해야 함 문제가 발생하면 https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

지난번과 동일한 간단한 C 예시로 시작해 보겠습니다.

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

컴파일하려면 최신 Emscripten을 사용합니다. 원본 게시물에서와 마찬가지로 -g 플래그를 전달하여 있습니다.

emcc -g temp.c -o temp.html

이제 localhost HTTP 서버( (예: serve) 최신 Chrome Canary에서 여세요.

이번에는 Chrome DevTools와 통합되고 WebAssembly 파일에 인코딩된 모든 디버깅 정보를 이해하는 데 도움이 되는 도우미 확장 프로그램도 필요합니다. 이 링크: goo.gle/wasm-debugging-extension

DevTools Experiments에서 WebAssembly 디버깅도 사용 설정해야 합니다. Chrome DevTools를 열고 톱니바퀴 () 아이콘을 클릭합니다. 도구 창의 오른쪽 상단에 있는 실험 패널로 이동합니다. WebAssembly Debugging: Enable DWARF support를 선택합니다.

DevTools 설정의 실험 창

설정을 닫으면 DevTools에서 설정을 적용하기 위해 새로고침하라는 메시지가 표시됩니다. 일회성 설정은 여기까지입니다.

이제 소스 패널로 돌아가 예외 발생 시 일시중지(⏸ 아이콘)를 사용 설정한 다음 포착된 예외 발생 시 일시중지를 선택하고 페이지를 새로고침합니다. 예외로 인해 DevTools가 일시중지된 것을 확인할 수 있습니다.

&#39;포착된 예외에서 일시중지&#39;를 사용 설정하는 방법을 보여주는 소스 패널의 스크린샷

기본적으로 Emscripten에서 생성한 글루 코드에서 중지되지만 오른쪽에는 호출의 스택 트레이스를 나타내는 Call Stack 뷰가 호출한 원래 C 라인으로 이동할 수 있으며 abort:

DevTools가 `assert_less` 함수에서 일시중지되고 범위 뷰에 `x` 및 `y` 값을 표시하는 경우

이제 범위 보기를 보면 원래 이름을 확인할 수 있습니다. 변수 값을 포함하므로 더 이상 일일이 $localN와 같이 손상된 이름의 의미와 소스 코드를 가져옵니다.

이는 정수와 같은 원시 값뿐만 아니라 복합 값에도 적용됩니다. 구조, 클래스, 배열 등과 같은 유형도 포함됩니다.

리치 유형 지원

이를 보여주기 위해 좀 더 복잡한 예를 살펴보겠습니다. 이 1을 시작해서 만델브로 프랙탈을 다음 C++ 코드를 실행합니다.

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

이 애플리케이션은 여전히 상당히 작습니다. 코드 50줄이 포함된 단일 파일입니다. 하지만 이번에는 그래픽용 SDL 라이브러리와 C++ 표준 라이브러리의 복소수와 같은 일부 외부 API도 사용하고 있습니다.

디버그 정보를 포함하도록 위와 동일한 -g 플래그로 컴파일하고 Emscripten에 SDL2 라이브러리를 제공하고 임의 크기의 메모리를 허용하도록 요청합니다.

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

브라우저에서 생성된 페이지를 방문하면 아름다운 프랙털 도형:

데모 페이지

DevTools를 열면 원래 C++ 파일이 다시 표시됩니다. 이 하지만 코드에 오류가 없으므로 코드 시작 부분에 있는 일부 중단점을 대신 표시합니다.

페이지를 다시 새로고침하면 디버거가 C++ 소스:

DevTools가 `SDL_Init` 호출에서 일시중지됨

이미 오른쪽에서 모든 변수를 볼 수 있지만 width만 표시됩니다. height는 현재 초기화되어 있으므로 검사하겠습니다

기본 망델브로 루프 내에 다른 중단점을 설정하고 조금 앞으로 건너뛰도록 할 수 있습니다.

중첩된 루프 내에서 DevTools가 일시중지됨

이 시점에서 palette가 임의의 색상으로 채워졌습니다. 그리고 배열 자체뿐만 아니라 개별 배열도 확장할 수 있습니다. SDL_Color 구조를 만들고 구성요소를 검사하여 아무 문제가 없어 보입니다. 예를 들어 '알파' 채널은 항상 전체 불투명도) 마찬가지로 center 변수에 저장된 복소수의 실수와 허수 부분을 펼치고 확인할 수 있습니다.

다른 방법으로는 확인하기 어려운 복잡하게 중첩된 속성에 액세스하려는 경우 범위 뷰를 통해 이동하려는 경우 콘솔 살펴보겠습니다 그러나 더 복잡한 C++ 표현식은 아직 지원되지 않습니다.

`palette[10].r`의 결과를 보여주는 콘솔 패널

실행을 몇 번 재개해 보겠습니다. 그러면 내부 x가 어떻게 작동하는지 확인할 수 있습니다. 범위 뷰를 다시 보고 감시 목록에 변수 이름을 추가하거나 콘솔에서 변수를 평가하거나 소스 코드에서 변수 위로 마우스를 가져갑니다.

값 `3` 을 표시하는 소스의 `x` 변수에 대한 도움말

여기에서 C++ 문을 스테프인 또는 스테프오버하고 다른 변수도 어떻게 변경되는지 관찰할 수 있습니다.

`color`, `point` 및 기타 변수의 값을 보여주는 도움말 및 범위 뷰

디버그 정보를 사용할 수 있을 때 이 모든 것이 잘 작동하지만 디버깅을 통해 빌드되지 않은 코드를 디버깅하려는 경우에는 어떻게 해야 할까요? 옵션은 무엇인가요?

원시 WebAssembly 디버깅

예를 들어 Emscripten에 소스에서 직접 컴파일하는 대신 미리 빌드된 SDL 라이브러리를 제공해 달라고 요청했으므로 적어도 현재로서는 디버거가 연결된 소스를 찾을 방법이 없습니다. 다시 단계를 거쳐 SDL_RenderDrawColor로 이동해 보겠습니다.

`mandelbrot.wasm`의 디스어셈블리 뷰를 보여주는 DevTools

원시 WebAssembly 디버깅 환경으로 돌아갑니다.

다소 무서워 보이지만 대부분의 웹 개발자는 처리해야 하는 일이지만 가끔 디버그 정보 없이 빌드된 라이브러리입니다. 제어할 수 없는 서드 파티 라이브러리이거나 버그 중 하나에 직면하게 됩니다.

이러한 경우에 도움이 되도록 Google은 살펴봤습니다

먼저 이전에 원시 WebAssembly 디버깅을 사용했다면 이제 전체 디스어셈블리가 단일 파일에 표시되는 것을 볼 수 있습니다. 더 이상 소스 항목 wasm-53834e3e/ wasm-53834e3e-7가 어떤 함수에 해당할지 추측할 필요가 없습니다.

새 이름 생성 스키마

디스어셈블리 뷰의 이름도 개선되었습니다. 이전에는 숫자 색인만 표시되거나 함수의 경우 이름이 전혀 표시되지 않았습니다.

이제 WebAssembly 이름 섹션의 힌트, 가져오기/내보내기 경로를 사용하여 다른 디스어셈블리 도구와 마찬가지로 이름을 생성하고, 다른 모든 방법이 실패하면 $func123와 같은 항목의 유형과 색인을 기반으로 이름을 생성합니다. 위의 스크린샷에서 볼 수 있듯이 이렇게 하면 읽기 쉬운 스택 트레이스와 디스어셈블리를 얻는 데 도움이 됩니다.

사용 가능한 유형 정보가 없으면 검사가 어려울 수 있습니다. 프리미티브 외의 모든 값(예: 포인터는 그 뒤에 무엇이 저장되어 있는지 알 수 있는 방법이 없다는 것을 사용할 수 있습니다

메모리 검사

이전에는 범위 뷰에서 env.memory로 표시되는 WebAssembly 메모리 객체만 확장하여 개별 바이트를 조회할 수 있었습니다. 이는 일부 사소한 시나리오에서는 작동했지만 확장하기가 특히 편리하지 않았고 바이트 값 이외의 형식으로 데이터를 재해석할 수 없었습니다. 이 문제 해결에 도움이 되는 선형 메모리 검사기라는 새로운 기능도 추가되었습니다.

env.memory를 마우스 오른쪽 버튼으로 클릭하면 이제 새 메모리 검사라는 옵션을 사용합니다.

&#39;메모리 검사&#39;를 보여주는 범위 창의 `env.memory` 컨텍스트 메뉴 항목

클릭하면 Memory Inspector가 16진수 및 ASCII 보기로 WebAssembly 메모리를 검사할 수 있습니다. 특정 주소로 이동할 뿐만 아니라 다양한 형식:

메모리의 16진수 및 ASCII 뷰를 보여주는 DevTools의 메모리 검사기 창

고급 시나리오 및 주의사항

WebAssembly 코드 프로파일링

DevTools를 열면 디버깅을 사용 설정하기 위해 최적화되지 않은 버전으로 WebAssembly 코드가 '다운스케일링'됩니다. 이 버전은 훨씬 느리므로 DevTools가 열려 있는 동안 console.time, performance.now 및 기타 코드 속도 측정 방법을 사용할 수 없습니다. 표시되는 숫자가 실제 성능을 전혀 나타내지 않기 때문입니다.

대신 DevTools Performance 패널을 사용해야 합니다. 코드를 최대 속도로 실행하고 여러 기능에 소요된 시간에 대한 자세한 분석:

다양한 Wasm 함수를 보여주는 프로파일링 패널

또는 DevTools를 닫은 상태에서 애플리케이션을 실행할 수 있습니다. 완료되면 열어서 콘솔을 검사합니다.

향후 프로파일링 시나리오가 개선될 예정이지만 지금은 주의해야 할 사항입니다. WebAssembly 및 WebAssembly가 계층화 시나리오는 WebAssembly 컴파일 파이프라인에 대한 문서를 확인하세요.

Docker/호스트를 포함한 여러 머신에서 빌드 및 디버그

Docker, 가상 머신 또는 원격 빌드 서버에서 빌드할 때는 빌드 중에 사용되는 소스 파일의 경로가 Chrome DevTools가 실행 중인 자체 파일 시스템의 경로와 일치하지 않는 경우가 발생할 수 있습니다. 이 경우 파일이 Sources 패널이 표시되는데 로드되지 않습니다.

이 문제를 해결하기 위해 다음 경로에 경로 매핑 기능을 구현했습니다. C/C++ 확장 옵션입니다. 이를 사용하여 임의의 경로를 재매핑하고 DevTools가 소스를 찾을 수 있게 도와줍니다.

예를 들어 호스트 머신의 프로젝트가 C:\src\my_project이지만 Docker 컨테이너 내부에 빌드되었는데 이 경로는 /mnt/c/src/my_project로 표시되어 있으므로 다시 매핑하면 해당 경로를 접두사로 지정하여 디버깅 중에 다시 가져옵니다.

C/C++ 디버깅 확장 프로그램의 옵션 페이지

처음 일치하는 접두사가 'wins'입니다. 다른 C++ 프로그래밍 언어나 디버거의 경우 이 옵션은 set substitute-path 명령어와 비슷합니다. GDB에서 또는 LLDB의 target.source-map 설정.

최적화된 빌드 디버깅

다른 언어와 마찬가지로 최적화가 필요한 경우 디버깅이 가장 잘 작동합니다. 사용 중지됩니다. 최적화는 함수를 다른 함수에 인라인으로 삽입하고 재정렬할 수 있음 코드의 일부를 완전히 삭제할 수 있습니다. 디버거를 혼동하여 사용자인 여러분을 혼동시킬 수 있습니다.

좀 더 제한된 디버깅 환경에도 괜찮고 좀 더 디버그하는 경우 대부분의 최적화는 (함수 인라인 처리는 예외) 나머지 문제는 향후 해결할 계획이지만 지금은 -O 수준 최적화로 컴파일할 때 -fno-inline를 사용하여 사용 중지하세요. 예를 들면 다음과 같습니다.

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

디버그 정보 분리

디버그 정보는 정의된 유형, 변수, 함수, 범위, 위치 등 코드에 관한 많은 세부정보를 보존합니다. 디버거에 유용할 수 있는 모든 항목이 여기에 포함됩니다. 따라서 코드 자체보다 클 수 있습니다.

WebAssembly 모듈의 로드 및 컴파일 속도를 높이려면 이 디버그 정보를 별도의 WebAssembly 파일로 분할하는 것이 좋습니다. Emscripten에서 이렇게 하려면 다음과 같이 -gseparate-dwarf=… 플래그를 전달합니다. 원하는 파일 이름:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

이 경우 기본 애플리케이션은 파일 이름 temp.debug.wasm만 저장하고 DevTools를 열면 도우미 확장 프로그램이 이를 찾아 로드할 수 있습니다.

위에서 설명한 것과 같은 최적화와 함께 사용하면 이 기능을 사용하여 거의 최적화된 애플리케이션 프로덕션 빌드를 출시하고 나중에 로컬 측정 파일로 디버그할 수도 있습니다. 이 경우 확장 프로그램이 부속 파일을 찾을 수 있도록 저장된 URL을 재정의해야 합니다. 예를 들면 다음과 같습니다.

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

계속해서

새로운 기능이 정말 많네요.

이러한 새로운 통합을 통해 Chrome DevTools는 JavaScript뿐만 아니라 C 및 C++ 앱을 위한 실용적이고 강력한 디버거가 됩니다. 이제 다양한 기술로 빌드된 앱을 공유된 크로스 플랫폼 웹으로 가져가기가 그 어느 때보다 쉬워졌습니다.

하지만 여정은 아직 끝나지 않았습니다. 오늘 살펴볼 내용은 부터 작업:

  • 디버깅 환경에서 거친 가장자리를 정리했습니다.
  • 맞춤 유형 형식 지정자에 대한 지원을 추가했습니다.
  • WebAssembly 앱의 프로파일링 개선을 위해 노력하고 있습니다.
  • 사용되지 않는 코드를 더 쉽게 찾을 수 있도록 코드 범위 지원을 추가했습니다.
  • 콘솔 평가에서 표현식 지원을 개선했습니다.
  • 더 많은 언어가 지원됩니다.
  • 그 밖의 다양한 신호

그동안 귀하의 코드에서 현재 베타 버전을 사용해 보고 발견된 문제를 신고해 주시면 감사하겠습니다. 문제를 https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

미리보기 채널 다운로드

Chrome Canary, Dev 또는 베타를 기본 개발 브라우저로 사용하는 것이 좋습니다. 이러한 미리보기 채널을 사용하면 최신 DevTools 기능에 액세스하고, 최신 웹 플랫폼 API를 테스트하고, 사용자가 발견하기 전에 사이트에서 문제를 찾을 수 있습니다.

Chrome DevTools팀에 문의하기

다음 옵션을 사용하여 새로운 기능, 업데이트 또는 DevTools와 관련된 다른 내용을 논의하세요.