Отладка Wasm

Учимся находить и чинить ошибки в Wasm-коде.

Отладка Wasm опирается на отладочную информацию (DWARF/source maps), которая связывает байткод с исходным кодом на C/Rust.

Проблема: байткод нечитаем

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

Отладка в браузере

Современные Chrome DevTools поддерживают формат DWARF — тот же, что используют нативные отладчики. Скомпилировав с флагом отладки, вы получаете возможность ставить брейкпоинты в Rust/C-коде, который реально исполняется как Wasm:

# Emscripten: вложить DWARF-отладку
emcc app.c -g -o app.js

# Rust: собрать в debug-профиле (отладка по умолчанию)
wasm-pack build --dev

Логирование через импорт

Самый простой и универсальный способ — старый добрый «вывод в лог». Поскольку у Wasm нет своего console.log, его импортируют из JS. Тогда из кода на C/Rust можно «печатать» значения для отладки. Смоделируем такое логирование на JS, где функция log играет роль импорта:

// log — импортированная из JS функция логирования для Wasm
function log(value) { console.log("[wasm]", value); }

function process(n) {
  log(n);            // отладочный вывод "входа"
  const result = n * n;
  log(result);       // отладочный вывод "результата"
  return result;
}
process(7);

Вывод:

[wasm] 7
[wasm] 49

Ловушки (traps)

Wasm не «падает молча». При ошибке — выход за границу памяти, деление на ноль, переполнение таблицы, несовпадение сигнатуры в call_indirect — исполнение прерывается ловушкой (trap). В JS это превращается в исключение RuntimeError с сообщением вроде «unreachable» или «out of bounds memory access». Ловушка — ваш друг: она точно говорит, что нарушен инвариант, вместо тихой порчи данных.

ЛовушкаПричина
out of boundsчтение/запись за пределы линейной памяти
integer divide by zeroделение целого на ноль
indirect call type mismatchсигнатура в call_indirect не совпала
unreachableдостигнута инструкция unreachable (часто паника/assert)

Как работает под капотом

Отладочная карта (DWARF) не входит в исполнение — это отдельная секция модуля, которую читает отладчик. Поэтому для продакшена её вырезают (wasm-strip), чтобы уменьшить размер, а для отладки оставляют. Ловушки же встроены в саму проверку каждой опасной операции: например, перед каждым обращением к памяти движок проверяет границу, и если нарушена — генерирует trap. Это часть гарантий безопасности Wasm, не опциональная добавка.

Частые ошибки

  • Отлаживать release-сборку — без флага отладки нет карты, в стеке только номера функций.
  • Игнорировать текст trap — «out of bounds» прямо указывает на выход за память.
  • Тащить DWARF в прод — это раздувает модуль; для релиза отладку вырезают.

Итоги

  • Отладка опирается на DWARF/source maps — карту байткод ↔ исходник.
  • Chrome DevTools умеют ставить брейкпоинты в C/Rust, исполняемом как Wasm.
  • Универсальный приём — логирование через импортированную из JS функцию.
  • Ошибки превращаются в ловушки (trap) → RuntimeError с понятной причиной.
Проверьте себя
1. Что связывает Wasm-байткод с исходным кодом при отладке?
AИмена экспортов
BОтладочная информация (DWARF / source maps)
CРазмер модуля
DЛинейная память
2. Что происходит при выходе за границу линейной памяти?
AПамять расширяется
BВозникает ловушка (trap), в JS — RuntimeError
CЧтение возвращает 0 молча
DМодуль перезапускается
3. Почему отладочную информацию вырезают для продакшена?
AОна замедляет код
BОна заметно увеличивает размер модуля, а в исполнении не участвует
CОна небезопасна
DБраузер её не понимает