# 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, если и он не работает, отключаем бэктрейсы в данной реализации.