diff --git a/src/transport_layer/call_ring.c b/src/transport_layer/call_ring.c index 8be1077f..9ce35072 100644 --- a/src/transport_layer/call_ring.c +++ b/src/transport_layer/call_ring.c @@ -1,16 +1,35 @@ // call_ring.c — статический кольцевой трейс вызовов (диагностика UAF в teardown) #include "call_ring.h" +#include "../lib/platform_compat.h" +#include #include +#include #if defined(_WIN32) #include #else -#include #include #endif -callring_entry_t g_callring[CALLRING_SIZE]; -uint32_t g_callring_idx = 0; +__thread callring_entry_t g_callring[CALLRING_SIZE]; +__thread uint32_t g_callring_idx = 0; +__thread uint32_t g_callring_count = 0; + +/* Никакого logger/malloc: путь вызывается из аварийного обработчика. */ +static void cr_write(int* fd, const char* data, size_t len) { + while (len) { + ssize_t n = write(*fd, data, len); + if (n < 0 && errno == EINTR) continue; + if (n <= 0) { + if (*fd == 2) return; + close(*fd); *fd = 2; + static const char error[] = "callring: dump write failed, continuing on stderr\n"; + ssize_t ignored = write(2, error, sizeof(error) - 1); (void)ignored; + continue; + } + data += n; len -= (size_t)n; + } +} static const char *cr_name(uint32_t code) { switch (code) { @@ -50,19 +69,22 @@ void callring_dump(void) { snprintf(path, sizeof(path), "/tmp/utun_callring.%d", (int)getpid()); fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); } - if (fd < 0) fd = 2; + if (fd < 0) { + fd = 2; + static const char error[] = "callring: cannot open dump file, using stderr\n"; + ssize_t ignored = write(2, error, sizeof(error) - 1); (void)ignored; + } #endif int n = snprintf(buf, sizeof(buf), "callring dump idx=%u\n", g_callring_idx); - if (n > 0) { ssize_t w = write(fd, buf, (size_t)n); (void)w; } + if (n > 0 && (size_t)n < sizeof(buf)) cr_write(&fd, buf, (size_t)n); - uint32_t total = g_callring_idx < CALLRING_SIZE ? g_callring_idx : CALLRING_SIZE; - uint32_t start = g_callring_idx >= CALLRING_SIZE ? g_callring_idx - CALLRING_SIZE : 0; + uint32_t total = g_callring_count; + uint32_t start = g_callring_idx - total; for (uint32_t k = 0; k < total; k++) { const callring_entry_t *e = &g_callring[(start + k) % CALLRING_SIZE]; - n = snprintf(buf, sizeof(buf), "%s a=0x%lx b=0x%lx\n", cr_name(e->code), - (unsigned long)e->a, (unsigned long)e->b); - if (n > 0) { ssize_t w = write(fd, buf, (size_t)n); (void)w; } + n = snprintf(buf, sizeof(buf), "%s a=0x%" PRIxPTR " b=0x%" PRIxPTR "\n", cr_name(e->code), e->a, e->b); + if (n > 0 && (size_t)n < sizeof(buf)) cr_write(&fd, buf, (size_t)n); } #if !defined(_WIN32) diff --git a/src/transport_layer/call_ring.h b/src/transport_layer/call_ring.h index 52785deb..97c021ec 100644 --- a/src/transport_layer/call_ring.h +++ b/src/transport_layer/call_ring.h @@ -1,6 +1,6 @@ // call_ring.h — статический кольцевой трейс вызовов (диагностика UAF в teardown) // -// Каждая контрольная точка пишет в общий кольцевой буфер номер (code) и два +// Каждая контрольная точка пишет в кольцевой буфер текущего потока номер (code) и два // указателя-объекта (a, b). По последовательности записей + адресам из core-дампа // можно размотать, кто кого вызвал и кто освободил линк без обнуления обратных ссылок. #ifndef CALL_RING_H @@ -17,15 +17,25 @@ typedef struct { uintptr_t b; // объект 2 (close_arg/tcp_link/etcp_link) } callring_entry_t; -extern callring_entry_t g_callring[CALLRING_SIZE]; -extern uint32_t g_callring_idx; +/* TLS отделяет одновременно работающие uasync-потоки; без аллокаций и блокировок. + * Индекс циклический uint32_t, count насыщается на CALLRING_SIZE; переменные доступны для core dump. */ +extern __thread callring_entry_t g_callring[CALLRING_SIZE]; +extern __thread uint32_t g_callring_idx; +extern __thread uint32_t g_callring_count; +/* Добавляет code/a/b без обращения по указателям a/b. Перезаписывает старейшую запись. + * Асинхронный сигнал может прервать запись: аварийный dump остаётся best effort. */ static inline void callring_put(uint32_t code, uintptr_t a, uintptr_t b) { callring_entry_t *e = &g_callring[g_callring_idx % CALLRING_SIZE]; e->code = code; e->a = a; e->b = b; g_callring_idx++; + if (g_callring_count < CALLRING_SIZE) g_callring_count++; } +/* Последние count записей текущего потока в хронологическом порядке. + * POSIX: /tmp/utun_callring. с перезаписью, при отказе — stderr; Windows: stderr. + * Ошибки I/O отражаются напрямую в stderr (обычный logger в crash-handler не используется). + * Собственных аллокаций нет; snprintf не даёт общей гарантии async-signal-safety libc. */ void callring_dump(void); /* коды контрольных точек */ @@ -46,7 +56,7 @@ enum { CR_CONN_FREE = 23, /* stcp_conn_free → a=c */ CR_CCLOSE_IN = 30, /* etcp_connection_close вход → a=etcp */ CR_CFREE_RES = 31, /* etcp_connection_free_resources → a=etcp */ - CR_NCD_DEFER = 40, /* ncd_deferred_close_conn → a=conn */ + CR_NCD_DEFER = 40, /* зарезервирован, текущий NCD не пишет эту точку */ }; #endif /* CALL_RING_H */