# Tarantool backtraces design ## Проблема В ходе разборок с [6060](https://github.com/tarantool/tarantool/issues/6060) обнаружилось, что текущий API бэктрейсов некорректно работает на M1. В то же время, для реализации [4002](https://github.com/tarantool/tarantool/issues/4002) планируется добавить новый API, который в том числе должен работать одинаково на всех поддерживаемых платформах. Предлагается перенести все бэктрейсы на этот API. ## Аппрув | [@alyapunov](https://github.com/alyapunov) | [@unera](https://github.com/unera) | | -------- | -------- | | -- | -- | ## Как сделано сейчас На данный момент есть одна функция, отвечающая за сбор бэктрейсов произвольной корутины: ```c void backtrace_foreach(backtrace_cb cb, coro_context *coro_ctx, void *cb_ctx); ``` `backtrace_foreach()` для стека чужой корутины реализуется через сбор ее libunwind-контекста с последующим проходом по этому контексту в своем стеке для получения информации о фреймах. `callback_cb` -- тип callback'а для обработки информации о фрейме (одинаковый для любого API бэктрейсов): ```c typedef int (backtrace_cb)(int frameno, void *frameret, const char *func, size_t offset, void *cb_ctx); ``` ## Почему нужно переделать - На Arm (в том числе M1) реализация libunwind-контекста не позволяет собирать бэктрейсы стеков других корутин из libcoro. В таком случае `backtrace_foreach()` работает корректно только если`coro_context == NULL`. - Текущий API не разделяет получение контекста для трассировки и резолвинг информации о фреймах с помощью данного контекста. Без этого сложно реализовать [4002](https://github.com/tarantool/tarantool/issues/4002). - На данный момент Lua-бэктрейсы логически не связаны с С-бэктрейсами, хотя в коде всегда используются вместе с ними. ## Новый API Основная идея API - отделение сбора общего (для Lua и С) контекста бэктрейса (`backtrace_collect()`) от резолвинга информации о фреймах по данному контексту (`backtrace_foreach()`) и объединения контекстов (`backtrace_append()`). API реализуется через такие же процедуры для Lua-контекста и C-контекста. ```c /** * Backtrace context */ struct backtrace { /** Number of frames. */ int frames_count; /** Buffer of Lua frame data. */ struct backtrace_frame frames[BACKTRACE_MAX_FRAMES]; }; /** * Fills @a ctx with RIP addresses of frames from * @a coro or current stack if @a coro == NULL. * @returns number of frames traversed. */ int backtrace_collect(struct backtrace *ctx, coro_context *coro); /** * Resolves frame information from @a ctx * and passes it along with @a cb to @a cb callback. * @returns number of frames traversed. */ int backtrace_foreach(const struct backtrace *bt, backtrace_cb cb, void *cb_ctx); /** * Appends @a add to @a to * @returns number of frames appended. */ int backtrace_append(struct backtrace *to, const struct backtrace *add) ``` #### Данные фреймов Для Lua-бэктрейсов фрейм хранит всю необходимую информацию о функции, для C-бэктрейсов сохраняется адрес исполняемого кода (значений регистра RIP) по аналогии с подходом `backtrace()` и `backtrace_symbols()` из `<execinfo.h>` в Linux. ```c struct backtrace_frame { int is_lua; union { /* C frame data */ void *rip; /* Lua frame data */ struct { char name[BACKTRACE_LUA_MAX_NAME]; char source[BACKTRACE_LUA_MAX_NAME]; int line; } } }; ``` #### Количество сохраняемых фреймов Все размеры буферов задаются статически, чтобы избавиться от необходимости аллокаций памяти. Количество сохраняемых фреймов предлагается сделать достаточно большим чтобы иметь возможность сохранять бэктрейсы предков файберов: ```c enum { BACKTRACE_MAX_FRAMES = 32, BACKTRACE_LUA_MAX_NAME = 64, }; ``` #### Бэктрейс предков файбера Файбер хранит в себе экземпляр `bt_ctx`, содержащий информацию о фиксированном числе фреймов его предков. При выводе в `fiber.info` собирается контекст текущего файбера, сливается с контекстом его предков и с помощью `backtrace_foreach_ctx()` последовательно добавляется в нужную Lua - таблицу. #### Возможные проблемы - Получение информации о фреймах по адресу исполняемого кода все еще не гарантирует полную работоспособность нового API на всех системах. - На текущий момент начало участка Lua-фреймов определяется по имени процедуры `lj_BC_FUNCC`. В новой реализации необходимо изменить этот подход. ## Реализация Реализация разбивается на 3 основных этапа, первые 2 уже частично реализованы в патче для [4002](https://github.com/tarantool/tarantool/issues/4002) (см. [PR](https://github.com/tarantool/tarantool/pull/6124)) Основная часть реализации будет привязана к патчсету для [6060](https://github.com/tarantool/tarantool/issues/6060), так как он вызвал необходимость поменять API. #### Подготовительный патч - Реализуем тесты на совместимость резолвинга фреймов через контекст libunwind и через буфер значений RIP с корутинами из libcoro. - Рефакторим текущую реализации бэктрейсов для удобства добавления нового API. #### Реализация API - Используем уже существующий кэш фреймов и деманглинг `С++`-имен для сохранения совместимости со старым API. - Для сбора RIP регистров используем `unw_backtrace()`, для резолвинга фреймов -- `unw_get_accessors()->get_proc_name()` из внутреннего функционала libunwind. В случае отсутствия такого функционала используем `unw_get_reg(UNW_REG_IP)` и `dladdr()` соответственно. #### Переход на новый API - Реализуем `backtrace_foreach()` через новый API. - Реализуем патч для [4002](https://github.com/tarantool/tarantool/issues/4002) через новый API. - При наличии конфигураций, в которых возникают проблемы с новой реализацией, оставляем старый API, если и он не работает, отключаем бэктрейсы в данной реализации.