Отладка WebAssembly современными инструментами

Ингвар Степанян
Ingvar Stepanyan

Дорога до сих пор

Год назад Chrome объявил о начальной поддержке встроенной отладки WebAssembly в Chrome DevTools.

Мы продемонстрировали базовую поддержку степпинга и рассказали о возможностях использования информации DWARF вместо исходных карт, которые откроются для нас в будущем:

  • Разрешение имен переменных
  • Симпатичные типы печати
  • Оценка выражений на исходных языках
  • …и многое другое!

Сегодня мы рады продемонстрировать реализацию обещанных функций и прогресс, достигнутый командами Emscripten и Chrome DevTools за этот год, в частности, в приложениях C и C++.

Прежде чем мы начнем, имейте в виду, что это все еще бета-версия нового интерфейса, вам необходимо использовать последнюю версию всех инструментов на свой страх и риск, и если у вас возникнут какие-либо проблемы, сообщите о них по адресу https:/ /issues.chromium.org/issues/new?noWizard=true&template=0&comComponent=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

Теперь мы можем обслуживать сгенерированную страницу с локального HTTP-сервера (например, с помощью submit ) и открывать ее в последней версии Chrome Canary .

На этот раз нам также понадобится вспомогательное расширение, которое интегрируется с Chrome DevTools и помогает ему разобраться во всей отладочной информации, закодированной в файле WebAssembly. Установите его, перейдя по этой ссылке: goo.gle/wasm-debugging-extension .

Вам также понадобится включить отладку WebAssembly в DevTools Experiments . Откройте Chrome DevTools, щелкните значок шестеренки ( ) в правом верхнем углу панели DevTools, перейдите на панель «Эксперименты» и установите флажок «Отладка WebAssembly: Включить поддержку DWARF» .

Панель «Эксперименты» в настройках DevTools

Когда вы закроете «Настройки» , DevTools предложит перезагрузиться, чтобы применить настройки, так что давайте сделаем именно это. Вот и все, что касается одноразовой установки.

Теперь мы можем вернуться на панель «Источники» , включить «Паузу при исключениях» (значок ⏸), затем установить флажок «Приостановить при обнаружении исключений» и перезагрузить страницу. Вы должны увидеть, что DevTools приостановлены из-за исключения:

Снимок экрана панели «Источники», показывающий, как включить «Паузу при обнаружении исключений».

По умолчанию он останавливается на связующем коде, сгенерированном Emscripten, но справа вы можете увидеть представление стека вызовов , представляющее трассировку стека ошибки, и можете перейти к исходной строке C, которая вызвала abort :

DevTools приостановил работу функции Assert_less и отобразил значения x и y в представлении области действия.

Теперь, если вы посмотрите в представлении «Область» , вы сможете увидеть исходные имена и значения переменных в коде C/C++, и вам больше не придется выяснять, что означают искаженные имена, такие как $localN и как они связаны с исходным кодом, который вы используете. написал.

Это относится не только к примитивным значениям, таким как целые числа, но и к составным типам, таким как структуры, классы, массивы и т. д.!

Богатая поддержка типов

Давайте рассмотрим более сложный пример, чтобы продемонстрировать это. На этот раз мы нарисуем фрактал Мандельброта с помощью следующего кода 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 строк кода, — но на этот раз я также использую некоторые внешние API, такие как библиотека SDL для графики, а также комплексные числа из стандартной библиотеки C++.

Я собираюсь скомпилировать его с тем же флагом -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 : либо снова просмотрев представление «Область» , добавив имя переменной в список наблюдения, вычислив его в консоли, либо наведя указатель мыши на переменную в исходный код:

Подсказка над переменной `x` в исходном коде, показывающая ее значение `3`

Отсюда мы можем вводить или обходить операторы C++ и наблюдать, как изменяются и другие переменные:

Всплывающие подсказки и вид «Область», показывающие значения «цвета», «точки» и других переменных.

Хорошо, все это прекрасно работает, когда доступна отладочная информация, но что, если мы хотим отладить код, который был создан без параметров отладки?

Отладка необработанной WebAssembly

Например, мы попросили Emscripten предоставить нам предварительно созданную библиотеку SDL вместо того, чтобы компилировать ее самостоятельно из исходного кода, поэтому — по крайней мере, на данный момент — у отладчика нет возможности найти связанные исходные коды. Давайте снова вмешаемся, чтобы перейти к SDL_RenderDrawColor :

DevTools показывает вид разборки `mandelbrot.wasm`

Мы вернулись к чистому опыту отладки WebAssembly.

Это выглядит немного пугающе, и большинству веб-разработчиков это не то, с чем когда-либо придется иметь дело, но иногда вам может понадобиться отладить библиотеку, созданную без отладочной информации, потому что это сторонняя библиотека, которую вы не можете контролировать. , или потому что вы столкнулись с одной из тех ошибок, которые возникают только в рабочей среде.

Чтобы помочь в таких случаях, мы также внесли некоторые улучшения в базовый процесс отладки.

Прежде всего, если вы раньше использовали необработанную отладку WebAssembly, вы могли заметить, что вся дизассемблирование теперь отображается в одном файле — больше не нужно гадать, какой функции может соответствовать запись в исходном коде wasm-53834e3e/ wasm-53834e3e-7 .

Новая схема генерации имени

Мы также улучшили имена в режиме дизассемблирования. Раньше вы видели только числовые индексы или, в случае функций, вообще не видели имени.

Теперь мы генерируем имена аналогично другим инструментам дизассемблирования, используя подсказки из раздела имени WebAssembly , пути импорта/экспорта и, наконец, если ничего не помогает, генерируем их на основе типа и индекса элемента, например $func123 . На скриншоте выше вы можете видеть, как это уже помогает получить немного более читаемые трассировки стека и дизассемблирование.

Когда информация о типе отсутствует, может быть сложно проверить какие-либо значения, кроме примитивов — например, указатели будут отображаться как обычные целые числа, без возможности узнать, что хранится за ними в памяти.

Проверка памяти

Раньше можно было только развернуть объект памяти WebAssembly, представленный env.memory в представлении «Область» , для поиска отдельных байтов. Это работало в некоторых тривиальных сценариях, но было не особенно удобно расширять и не позволяло интерпретировать данные в форматах, отличных от байтовых значений. Мы также добавили новую функцию, которая поможет в этом: инспектор линейной памяти.

Если вы щелкнете правой кнопкой мыши по env.memory , вы увидите новую опцию под названием «Проверить память» :

Контекстное меню в env.memory на панели «Область», отображающее элемент «Проверить память».

После щелчка откроется инспектор памяти , в котором вы сможете проверить память WebAssembly в шестнадцатеричном и ASCII-представлении, перейти к определенным адресам, а также интерпретировать данные в различных форматах:

Панель инспектора памяти в DevTools, показывающая шестнадцатеричное и ASCII-представление памяти.

Расширенные сценарии и предостережения

Профилирование кода WebAssembly

Когда вы открываете DevTools, код WebAssembly «понижается» до неоптимизированной версии, чтобы обеспечить возможность отладки. Эта версия намного медленнее, а это означает, что вы не можете полагаться на console.time , performance.now и другие методы измерения скорости вашего кода, пока DevTools открыты, поскольку полученные вами цифры не будут отражать реальный мир. производительность вообще.

Вместо этого вам следует использовать панель DevTools Performance , которая запустит код на полной скорости и предоставит вам подробную разбивку времени, затраченного на различные функции:

Панель профилирования, показывающая различные функции Wasm

Кроме того, вы можете запустить приложение с закрытыми DevTools и открыть их после завершения, чтобы проверить консоль .

В будущем мы будем улучшать сценарии профилирования, но на данный момент это предостережение, о котором следует знать. Если вы хотите узнать больше о сценариях многоуровневого управления WebAssembly, ознакомьтесь с нашей документацией по конвейеру компиляции WebAssembly .

Сборка и отладка на разных машинах (включая Docker/хост)

При сборке в Docker, виртуальной машине или на удаленном сервере сборки вы, скорее всего, столкнетесь с ситуациями, когда пути к исходным файлам, использованным во время сборки, не совпадают с путями в вашей собственной файловой системе, где запущены Chrome DevTools. В этом случае файлы появятся на панели «Источники» , но не загрузятся.

Чтобы решить эту проблему, мы реализовали функцию сопоставления путей в опциях расширения C/C++. Вы можете использовать его для переназначения произвольных путей и помощи DevTools в поиске источников.

Например, если проект на вашем хост-компьютере находится по пути C:\src\my_project , но был построен внутри контейнера Docker, где этот путь был представлен как /mnt/c/src/my_project , вы можете переназначить его обратно во время отладки. указав эти пути в качестве префиксов:

Страница параметров расширения отладки C/C++.

Первый совпавший префикс «выигрывает». Если вы знакомы с другими отладчиками C++, эта опция аналогична команде set substitute-path в GDB или настройке target.source-map в LLDB.

Отладка оптимизированных сборок

Как и в случае с любыми другими языками, отладка работает лучше всего, если оптимизация отключена. Оптимизации могут встраивать функции друг в друга, переупорядочивать код или вообще удалять части кода — и все это может запутать отладчик и, следовательно, вас как пользователя.

Если вы не возражаете против более ограниченного опыта отладки и все же хотите отлаживать оптимизированную сборку, то большинство оптимизаций будут работать как положено, за исключением встраивания функций. Мы планируем решить оставшиеся проблемы в будущем, но сейчас используйте -fno-inline , чтобы отключить его при компиляции с любыми оптимизациями уровня -O , например:

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&template=0&comComponent=1456350 .

Загрузите предварительный просмотр каналов

Рассмотрите возможность использования Chrome Canary , Dev или Beta в качестве браузера для разработки по умолчанию. Эти каналы предварительного просмотра предоставляют вам доступ к новейшим функциям DevTools, позволяют тестировать передовые API-интерфейсы веб-платформы и помогают находить проблемы на вашем сайте раньше, чем это сделают ваши пользователи!

Свяжитесь с командой Chrome DevTools

Используйте следующие параметры, чтобы обсудить новые функции, обновления или что-либо еще, связанное с DevTools.