-
-
Save Autoplay1999/f454d414d6b36ff110b94bfe242f5c52 to your computer and use it in GitHub Desktop.
Gemini + Grok
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include <windows.h> | |
| #include <cstdint> | |
| #include <cstddef> | |
| #include <array> | |
| #include <vector> | |
| #include <string> | |
| #include <string_view> | |
| #include <memory> | |
| #include <atomic> | |
| #include <mutex> | |
| #include <chrono> | |
| #include <unordered_map> | |
| #include <thread> | |
| #include <fstream> | |
| #include <algorithm> | |
| #include <cstdio> | |
| #include <cstdarg> | |
| #include <cstring> | |
| #include <stdexcept> | |
| namespace tl { | |
| constexpr uint64_t BLOCK_SIGNATURE = 0x544C4F47524F4B58ULL; | |
| constexpr size_t MAX_MESSAGE_LEN = 8192; | |
| constexpr size_t MAX_CALLSTACK_DEPTH = 256; | |
| constexpr size_t RING_BUFFER_SIZE = 8192; | |
| constexpr double ALPHA_WEIGHT = 0.1; | |
| #pragma pack(push, 1) | |
| struct LogEntryHeader { | |
| uint64_t signature{ BLOCK_SIGNATURE }; | |
| uint8_t version{ 1 }; | |
| uint8_t level{}; | |
| uint64_t status{}; | |
| uint32_t thread_id{}; | |
| int64_t timestamp_us{}; | |
| uint16_t callstack_count{}; | |
| uint16_t message_len{}; | |
| }; | |
| #pragma pack(pop) | |
| constexpr size_t MAX_BLOCK_SIZE = sizeof(uint64_t) + sizeof(uint64_t) + | |
| sizeof(LogEntryHeader) + | |
| (sizeof(uint16_t) + MAX_CALLSTACK_DEPTH * (sizeof(uint32_t) + sizeof(uint16_t))) + | |
| (sizeof(uint16_t) + MAX_MESSAGE_LEN); | |
| class LogEntryConverter { | |
| public: | |
| virtual bool convert(uint8_t* payload_start, size_t payload_size) noexcept = 0; | |
| }; | |
| class PlainConverter : public LogEntryConverter { | |
| public: | |
| bool convert(uint8_t* payload_start, size_t payload_size) noexcept override { return true; } | |
| }; | |
| class EncryptConverter : public LogEntryConverter { | |
| static constexpr std::array<uint8_t, 32> key = { 0xac, 0x0a, 0xb0, 0xeb, 0x54, 0xee, 0x88, 0xe4, 0xea, 0x13, 0x89, 0x3a, 0xc1, 0x29, 0x0f, 0x24, 0x02, 0x15, 0xcf, 0x71, 0xab, 0x51, 0x3b, 0xda, 0x11, 0xde, 0x0f, 0xb1, 0xcd, 0xa9, 0xe4, 0x70 }; | |
| static constexpr std::array<uint8_t, 12> nonce = { 0x75, 0xa3, 0xed, 0x51, 0x79, 0x84, 0xc7, 0xfa, 0x4b, 0x9e, 0xb7, 0xc2 }; | |
| bool convert(uint8_t* payload_start, size_t payload_size) noexcept override { | |
| ChaCha20 chacha20; | |
| chacha20.crypt(key.data(), key.size(), nonce.data(), nonce.size(), payload_start, payload_size); | |
| return true; | |
| } | |
| class ChaCha20 { | |
| private: | |
| static constexpr std::array<uint32_t, 4> sigma = { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 }; | |
| static uint32_t rotl(uint32_t x, int b) noexcept { | |
| return (x << b) | (x >> (32 - b)); | |
| } | |
| static void quarter_round(uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) noexcept { | |
| a += b; d ^= a; d = rotl(d, 16); | |
| c += d; b ^= c; b = rotl(b, 12); | |
| a += b; d ^= a; d = rotl(d, 8); | |
| c += d; b ^= c; b = rotl(b, 7); | |
| } | |
| static void chacha_block(std::array<uint32_t, 16>& state) noexcept { | |
| auto x = state; | |
| for (int i = 0; i < 10; ++i) { // 20 rounds (10 double rounds) | |
| quarter_round(x[0], x[4], x[8], x[12]); | |
| quarter_round(x[1], x[5], x[9], x[13]); | |
| quarter_round(x[2], x[6], x[10], x[14]); | |
| quarter_round(x[3], x[7], x[11], x[15]); | |
| quarter_round(x[0], x[5], x[10], x[15]); | |
| quarter_round(x[1], x[6], x[11], x[12]); | |
| quarter_round(x[2], x[7], x[8], x[13]); | |
| quarter_round(x[3], x[4], x[9], x[14]); | |
| } | |
| for (int i = 0; i < 16; ++i) { | |
| x[i] += state[i]; | |
| state[i] = x[i]; | |
| } | |
| } | |
| static void serialize(uint32_t v, uint8_t* out) noexcept { | |
| out[0] = static_cast<uint8_t>(v); | |
| out[1] = static_cast<uint8_t>(v >> 8); | |
| out[2] = static_cast<uint8_t>(v >> 16); | |
| out[3] = static_cast<uint8_t>(v >> 24); | |
| } | |
| static uint32_t load_le(const uint8_t* in) noexcept { | |
| return static_cast<uint32_t>(in[0]) | |
| | (static_cast<uint32_t>(in[1]) << 8) | |
| | (static_cast<uint32_t>(in[2]) << 16) | |
| | (static_cast<uint32_t>(in[3]) << 24); | |
| } | |
| public: | |
| static uint8_t* crypt(const uint8_t* key, size_t key_len, | |
| const uint8_t* nonce, size_t nonce_len, | |
| uint8_t* in, size_t in_len) { | |
| if (key_len != 32 || nonce_len != 12) { | |
| throw std::invalid_argument("ChaCha20 requires 32-byte key and 12-byte nonce"); | |
| } | |
| std::array<uint32_t, 16> state{}; | |
| state[0] = sigma[0]; | |
| state[1] = sigma[1]; | |
| state[2] = sigma[2]; | |
| state[3] = sigma[3]; | |
| for (int i = 0; i < 8; ++i) { | |
| state[4 + i] = load_le(key + 4 * i); | |
| } | |
| state[12] = 0; | |
| state[13] = load_le(nonce + 0); | |
| state[14] = load_le(nonce + 4); | |
| state[15] = load_le(nonce + 8); | |
| size_t pos = 0; | |
| uint64_t counter = 0; | |
| while (pos < in_len) { | |
| std::array<uint32_t, 16> block_state = state; | |
| block_state[12] = static_cast<uint32_t>(counter); | |
| chacha_block(block_state); | |
| std::array<uint8_t, 64> keystream{}; | |
| for (int i = 0; i < 16; ++i) { | |
| serialize(block_state[i], keystream.data() + 4 * i); | |
| } | |
| size_t block_size = std::min<size_t>(64, in_len - pos); | |
| for (size_t i = 0; i < block_size; ++i) { | |
| in[pos + i] = in[pos + i] ^ keystream[i]; | |
| } | |
| pos += block_size; | |
| ++counter; | |
| } | |
| return in; | |
| } | |
| }; | |
| }; | |
| inline std::atomic<LogEntryConverter*>& global_encryptor() noexcept { | |
| static EncryptConverter default_instance; | |
| static std::atomic<LogEntryConverter*> instance{ &default_instance }; | |
| return instance; | |
| } | |
| struct TimingMeasure { | |
| std::chrono::high_resolution_clock::time_point start{}; | |
| double avg_us{ 0.0 }; | |
| TimingMeasure() = default; | |
| void begin() noexcept { start = std::chrono::high_resolution_clock::now(); } | |
| void end() noexcept { | |
| auto now = std::chrono::high_resolution_clock::now(); | |
| double diff_us = std::chrono::duration<double, std::micro>(now - start).count(); | |
| if (avg_us == 0.0) avg_us = diff_us; | |
| else avg_us += ALPHA_WEIGHT * (diff_us - avg_us); | |
| } | |
| double get_average_us() const noexcept { return avg_us; } | |
| }; | |
| struct CallEntry { | |
| uint32_t func_id{ 0 }; | |
| uint16_t step{ 0 }; | |
| bool measure{ false }; | |
| TimingMeasure timing{}; | |
| CallEntry() = default; | |
| CallEntry(uint32_t fid, bool meas) noexcept : func_id(fid), measure(meas) { | |
| if (meas) timing.begin(); | |
| } | |
| }; | |
| struct ThreadContext { | |
| std::vector<CallEntry> call_stack; | |
| void push(uint32_t func_id, bool measure = false) noexcept { | |
| call_stack.emplace_back(func_id, measure); | |
| } | |
| void pop() noexcept { | |
| if (!call_stack.empty()) { | |
| auto& callstack = call_stack.back(); | |
| if (callstack.measure) { | |
| callstack.timing.end(); | |
| } | |
| call_stack.pop_back(); | |
| } | |
| } | |
| void set_step(uint16_t s) noexcept { | |
| if (!call_stack.empty()) call_stack.back().step = s; | |
| } | |
| const std::vector<CallEntry>& get_stack() const noexcept { return call_stack; } | |
| }; | |
| class ThreadRegistry { | |
| public: | |
| static ThreadRegistry& instance() noexcept { | |
| static ThreadRegistry inst; | |
| return inst; | |
| } | |
| void register_context(DWORD tid, std::shared_ptr<ThreadContext> ctx) noexcept { | |
| std::lock_guard<std::mutex> lk(mutex_); | |
| map_[tid] = std::weak_ptr<ThreadContext>(ctx); | |
| } | |
| void unregister_context(DWORD tid) noexcept { | |
| std::lock_guard<std::mutex> lk(mutex_); | |
| map_.erase(tid); | |
| } | |
| std::vector<std::pair<DWORD, std::shared_ptr<ThreadContext>>> get_active() const noexcept { | |
| std::vector<std::pair<DWORD, std::shared_ptr<ThreadContext>>> result; | |
| std::lock_guard<std::mutex> lk(mutex_); | |
| result.reserve(map_.size()); | |
| for (const auto& [tid, weak] : map_) { | |
| if (auto sp = weak.lock()) { | |
| result.emplace_back(tid, std::move(sp)); | |
| } | |
| } | |
| return result; | |
| } | |
| uint64_t& main_tid() noexcept { return main_tid_; } | |
| private: | |
| mutable std::mutex mutex_; | |
| uint64_t main_tid_ = 0; | |
| std::unordered_map<DWORD, std::weak_ptr<ThreadContext>> map_; | |
| }; | |
| struct ThreadContextManager { | |
| static ThreadContext* get() noexcept { | |
| thread_local std::shared_ptr<ThreadContext> ctx; | |
| thread_local bool initialized = false; | |
| thread_local struct Cleaner { | |
| ~Cleaner() { | |
| if (initialized) { | |
| ThreadRegistry::instance().unregister_context(::GetCurrentThreadId()); | |
| } | |
| } | |
| } cleaner{}; | |
| if (!initialized) { | |
| ctx = std::make_shared<ThreadContext>(); | |
| ThreadRegistry::instance().register_context(::GetCurrentThreadId(), ctx); | |
| initialized = true; | |
| } | |
| return ctx.get(); | |
| } | |
| }; | |
| class LogBlockPool { | |
| public: | |
| enum State : int { Empty = 0, Writing = 1, Ready = 2 }; | |
| struct Block { | |
| alignas(64) std::array<std::byte, MAX_BLOCK_SIZE> data; | |
| std::atomic<int> state{ Empty }; | |
| }; | |
| LogBlockPool() : blocks(std::make_unique<Block[]>(RING_BUFFER_SIZE)) {} | |
| Block* try_claim() noexcept { | |
| size_t wp = write_pos.load(std::memory_order_relaxed); | |
| while (true) { | |
| size_t rp = read_pos.load(std::memory_order_acquire); | |
| if ((wp - rp) >= RING_BUFFER_SIZE) { | |
| dropped_count.fetch_add(1, std::memory_order_relaxed); | |
| return nullptr; | |
| } | |
| if (write_pos.compare_exchange_weak(wp, wp + 1, std::memory_order_acq_rel)) { | |
| break; | |
| } | |
| } | |
| size_t idx = wp & (RING_BUFFER_SIZE - 1); | |
| Block* b = &blocks[idx]; | |
| int expected = Empty; | |
| if (b->state.compare_exchange_strong(expected, Writing, std::memory_order_acq_rel)) { | |
| return b; | |
| } | |
| dropped_count.fetch_add(1, std::memory_order_relaxed); | |
| return nullptr; | |
| } | |
| void commit(Block* block) noexcept { | |
| block->state.store(Ready, std::memory_order_release); | |
| committed.fetch_add(1, std::memory_order_release); | |
| } | |
| bool try_dequeue(std::byte*& ptr, size_t& sz) noexcept { | |
| if (committed.load(std::memory_order_acquire) == 0) | |
| return false; | |
| size_t rp = read_pos.load(std::memory_order_relaxed); | |
| size_t idx = rp & (RING_BUFFER_SIZE - 1); | |
| Block& b = blocks[idx]; | |
| int state = b.state.load(std::memory_order_acquire); | |
| if (state != Ready) | |
| return false; | |
| ptr = b.data.data(); | |
| sz = 0; | |
| b.state.store(Empty, std::memory_order_release); | |
| read_pos.store(rp + 1, std::memory_order_release); | |
| committed.fetch_sub(1, std::memory_order_release); | |
| return true; | |
| } | |
| uint64_t get_dropped_count() const noexcept { return dropped_count.load(); } | |
| size_t get_committed_count() const noexcept { return committed.load(std::memory_order_acquire); } | |
| private: | |
| std::unique_ptr<Block[]> blocks; | |
| alignas(64) std::atomic<size_t> write_pos{ 0 }; | |
| alignas(64) std::atomic<size_t> read_pos{ 0 }; | |
| alignas(64) std::atomic<size_t> committed{ 0 }; | |
| alignas(64) std::atomic<uint64_t> dropped_count{ 0 }; | |
| }; | |
| class AsyncLogWriter { | |
| public: | |
| enum class Mode { Async, Sync }; | |
| static AsyncLogWriter& instance() noexcept { | |
| static AsyncLogWriter writer; | |
| return writer; | |
| } | |
| void initialize(const std::string& filename = "app_trace.bin") noexcept { | |
| std::lock_guard<std::mutex> lk(init_mutex_); | |
| if (initialized_) return; | |
| filename_ = filename; | |
| mode_ = Mode::Async; | |
| handle_ = ::CreateFileA(filename_.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, | |
| OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr); | |
| if (handle_ == INVALID_HANDLE_VALUE) { | |
| use_fallback_ = true; | |
| fallback_.open(filename_, std::ios::binary | std::ios::app); | |
| } else { | |
| ::SetFilePointer(handle_, 0, nullptr, FILE_END); | |
| } | |
| initialized_ = true; | |
| } | |
| void shutdown() noexcept { | |
| flush(); | |
| std::lock_guard<std::mutex> lk(init_mutex_); | |
| if (handle_ != INVALID_HANDLE_VALUE) { | |
| ::FlushFileBuffers(handle_); | |
| ::CloseHandle(handle_); | |
| handle_ = INVALID_HANDLE_VALUE; | |
| } | |
| if (fallback_.is_open()) fallback_.close(); | |
| initialized_ = false; | |
| } | |
| void set_mode(Mode mode) noexcept { | |
| std::lock_guard<std::mutex> lk(init_mutex_); | |
| if (mode_ != mode) { | |
| flush(); | |
| mode_ = mode; | |
| } | |
| } | |
| Mode get_mode() noexcept { | |
| std::lock_guard<std::mutex> lk(init_mutex_); | |
| return mode_; | |
| } | |
| void flush() noexcept { | |
| std::vector<std::vector<std::byte>> batches; | |
| { | |
| std::vector<std::byte> batch; | |
| batch.reserve(1024 * 1024); | |
| std::byte* ptr; | |
| size_t dummy_sz; | |
| while (pool_.try_dequeue(ptr, dummy_sz)) { | |
| const uint64_t* sig = reinterpret_cast<const uint64_t*>(ptr); | |
| if (*sig != BLOCK_SIGNATURE) continue; | |
| const uint64_t* total_size_ptr = sig + 1; | |
| uint64_t block_size = *total_size_ptr; | |
| if (block_size <= sizeof(uint64_t) * 2 || block_size > MAX_BLOCK_SIZE) continue; | |
| batch.insert(batch.end(), ptr, ptr + block_size); | |
| if (batch.size() > 4 * 1024 * 1024) { | |
| batches.push_back(std::move(batch)); | |
| batch.clear(); | |
| batch.reserve(1024 * 1024); | |
| } | |
| } | |
| if (!batch.empty()) { | |
| batches.push_back(std::move(batch)); | |
| } | |
| } | |
| if (mode_ == Mode::Sync || batches.size() == 1) { | |
| for (const auto& b : batches) { | |
| write_batch_sync(b); | |
| } | |
| } else { | |
| std::vector<std::thread> workers; | |
| workers.reserve(batches.size()); | |
| for (auto& b : batches) { | |
| workers.emplace_back([this, data = std::move(b)]() mutable { | |
| write_batch_sync(data); | |
| }); | |
| } | |
| for (auto& t : workers) { | |
| if (t.joinable()) t.join(); | |
| } | |
| } | |
| } | |
| static uint64_t get_dropped_logs() noexcept { | |
| return instance().pool_.get_dropped_count(); | |
| } | |
| ~AsyncLogWriter() { if (initialized_) shutdown(); } | |
| LogBlockPool pool_; | |
| private: | |
| AsyncLogWriter() = default; | |
| void write_batch_sync(const std::vector<std::byte>& data) noexcept { | |
| if (data.empty()) return; | |
| if (use_fallback_) { | |
| std::lock_guard<std::mutex> lk(fallback_mutex_); | |
| fallback_.write(reinterpret_cast<const char*>(data.data()), data.size()); | |
| fallback_.flush(); | |
| } else { | |
| DWORD written = 0; | |
| ::WriteFile(handle_, data.data(), static_cast<DWORD>(data.size()), &written, nullptr); | |
| } | |
| } | |
| std::string filename_; | |
| HANDLE handle_{ INVALID_HANDLE_VALUE }; | |
| std::ofstream fallback_; | |
| std::mutex fallback_mutex_; | |
| bool use_fallback_{ false }; | |
| bool initialized_{ false }; | |
| std::mutex init_mutex_; | |
| Mode mode_{ Mode::Async }; | |
| }; | |
| inline size_t build_log_block_into(LogBlockPool::Block* block, | |
| uint8_t level, | |
| uint64_t status, | |
| const std::vector<CallEntry>& call_stack, | |
| std::string_view message, | |
| LogEntryConverter* encryptor = nullptr) noexcept { | |
| if (!encryptor) encryptor = global_encryptor().load(); | |
| uint8_t* dest = reinterpret_cast<uint8_t*>(block->data.data()); | |
| size_t callstack_section = sizeof(uint16_t) + call_stack.size() * (sizeof(uint32_t) + sizeof(uint16_t) + sizeof(float)); | |
| size_t message_section = sizeof(uint16_t) + message.size(); | |
| size_t payload_size = sizeof(LogEntryHeader) + callstack_section + message_section; | |
| size_t total_size = sizeof(uint64_t) + sizeof(uint64_t) + payload_size; | |
| *reinterpret_cast<uint64_t*>(dest) = BLOCK_SIGNATURE; | |
| *reinterpret_cast<uint64_t*>(dest + 8) = total_size; | |
| uint8_t* payload_start = dest + 16; | |
| size_t offset = 0; | |
| auto append = [&](const void* src, size_t sz) { | |
| memcpy(payload_start + offset, src, sz); | |
| offset += sz; | |
| }; | |
| LogEntryHeader header{}; | |
| header.signature = BLOCK_SIGNATURE; | |
| header.version = 1; | |
| header.level = level; | |
| header.status = status; | |
| header.thread_id = ::GetCurrentThreadId(); | |
| header.timestamp_us = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); | |
| header.callstack_count = static_cast<uint16_t>(call_stack.size()); | |
| header.message_len = static_cast<uint16_t>(message.size()); | |
| append(&header, sizeof(header)); | |
| uint16_t count = header.callstack_count; | |
| append(&count, sizeof(count)); | |
| for (const auto& e : call_stack) { | |
| append(&e.func_id, sizeof(e.func_id)); | |
| append(&e.step, sizeof(e.step)); | |
| float measureTime = e.measure ? static_cast<float>(e.timing.avg_us) : 0.0f; | |
| append(&measureTime, sizeof(measureTime)); | |
| } | |
| uint16_t msg_len = header.message_len; | |
| append(&msg_len, sizeof(msg_len)); | |
| if (msg_len > 0) { | |
| append(message.data(), message.size()); | |
| } | |
| if (encryptor) { | |
| encryptor->convert(payload_start, payload_size); | |
| } | |
| return total_size; | |
| } | |
| inline void initialize(const std::string& filename = "app_trace.bin") noexcept { | |
| AsyncLogWriter::instance().initialize(filename); | |
| } | |
| inline void shutdown() noexcept { | |
| AsyncLogWriter::instance().shutdown(); | |
| } | |
| inline void set_log_mode(bool synchronous) noexcept { | |
| AsyncLogWriter::instance().set_mode(synchronous ? AsyncLogWriter::Mode::Sync : AsyncLogWriter::Mode::Async); | |
| } | |
| inline void flush_logs() noexcept { | |
| AsyncLogWriter::instance().flush(); | |
| } | |
| inline uint64_t get_dropped_logs() noexcept { | |
| return AsyncLogWriter::get_dropped_logs(); | |
| } | |
| inline void push(uint32_t func_id, bool measure = false) noexcept { | |
| ThreadContextManager::get()->push(func_id, measure); | |
| } | |
| inline void pop() noexcept { | |
| ThreadContextManager::get()->pop(); | |
| } | |
| inline void step(uint16_t s) noexcept { | |
| ThreadContextManager::get()->set_step(s); | |
| } | |
| template<typename... Args> | |
| inline void log(uint8_t level, uint64_t status, const char* fmt, Args&&... args) noexcept { | |
| auto& writer = AsyncLogWriter::instance(); | |
| auto* block = writer.pool_.try_claim(); | |
| if (!block) return; | |
| thread_local std::string dynamic_buffer; | |
| dynamic_buffer.clear(); | |
| char small_buffer[4096]; | |
| int len = _snprintf_s(small_buffer, sizeof(small_buffer), _TRUNCATE, fmt, std::forward<Args>(args)...); | |
| std::string_view msg; | |
| if (len >= 0 && static_cast<size_t>(len) < sizeof(small_buffer)) { | |
| msg = { small_buffer, static_cast<size_t>(len) }; | |
| } else { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| int full_len = vsnprintf(nullptr, 0, fmt, ap); | |
| va_end(ap); | |
| if (full_len > 0 && full_len <= static_cast<int>(MAX_MESSAGE_LEN)) { | |
| dynamic_buffer.resize(full_len); | |
| va_start(ap, fmt); | |
| vsnprintf(dynamic_buffer.data(), full_len + 1, fmt, ap); | |
| va_end(ap); | |
| msg = dynamic_buffer; | |
| } else { | |
| msg = "<message too long or error>"; | |
| } | |
| } | |
| auto* ctx = ThreadContextManager::get(); | |
| build_log_block_into(block, level, status, ctx->get_stack(), msg); | |
| writer.pool_.commit(block); | |
| if (writer.get_mode() == AsyncLogWriter::Mode::Sync) { | |
| writer.flush(); | |
| } | |
| } | |
| inline void dump_all_thread_stacks() noexcept { | |
| auto& tr = ThreadRegistry::instance(); | |
| auto main_tid = tr.main_tid(); | |
| auto active = tr.get_active(); | |
| std::string msg = "[DUMP ALL THREADS]\n"; | |
| msg += " Main: " + std::to_string(main_tid) + "\n"; | |
| msg += " Active: " + std::to_string(active.size()) + "\n"; | |
| for (const auto& [tid, ctx] : active) { | |
| msg += " - [" + std::to_string(tid) + "] "; | |
| const auto& stack = ctx->get_stack(); | |
| for (size_t i = 0; i < stack.size(); ++i) { | |
| if (i > 0) msg += " -> "; | |
| msg += "Function(" + std::to_string(stack[i].func_id) + "), Step(" + std::to_string(stack[i].step) + "), Measure(" + std::to_string(stack[i].measure ? static_cast<float>(stack[i].timing.avg_us) : 0.0f) + ")"; | |
| } | |
| if (stack.empty()) msg += " <empty>"; | |
| msg += "\n"; | |
| } | |
| printf("%s\n", msg.c_str()); | |
| log(255, 0xDEADC0DEULL, "%s", msg.c_str()); | |
| } | |
| inline uint64_t& main_tid() noexcept { | |
| return ThreadRegistry::instance().main_tid(); | |
| } | |
| class [[nodiscard]] TraceScope { | |
| public: | |
| explicit TraceScope(uint32_t func_id, bool measure = false) noexcept | |
| : func_id_(func_id), measure_(measure) { | |
| push(func_id_, measure); | |
| } | |
| template<size_t N> | |
| explicit TraceScope(const char(&name)[N], bool measure = false) noexcept | |
| : TraceScope(static_hash(name), measure) {} | |
| ~TraceScope() noexcept { pop(); } | |
| TraceScope(const TraceScope&) = delete; | |
| TraceScope& operator=(const TraceScope&) = delete; | |
| private: | |
| uint32_t func_id_; | |
| bool measure_; | |
| static constexpr uint64_t fnv1a64(const char* s, uint64_t h = 14695981039346656037ULL) noexcept { | |
| return *s ? fnv1a64(s + 1, (h ^ static_cast<uint64_t>(*s)) * 1099511628211ULL) : h; | |
| } | |
| static constexpr uint32_t static_hash(const char* s) noexcept { | |
| return static_cast<uint32_t>(fnv1a64(s)); | |
| } | |
| }; | |
| } // namespace tl | |
| #define TRACE_LOG_ENABLED 1 | |
| #if TRACE_LOG_ENABLED | |
| #define TL_SCOPE() tl::TraceScope __scope(__FUNCTION__) | |
| #define TL_SCOPE_MEASURE() tl::TraceScope __scope(__FUNCTION__, true) | |
| #define TL_SCOPE_ID(id) tl::TraceScope __scope(id) | |
| #define TL_SCOPE_MEASURE_ID(id) tl::TraceScope __scope(id, true) | |
| #define TL_STEP(s) tl::step(s) | |
| #define TL_LOG(lvl, stat, ...) tl::log(lvl, stat, __VA_ARGS__) | |
| #define TL_INIT(file) tl::initialize(file) | |
| #define TL_SHUTDOWN() tl::shutdown() | |
| #define TL_FLUSH() tl::flush_logs() | |
| #define TL_SET_SYNC_MODE(on) tl::set_log_mode(on) // true = Sync, false = Async | |
| #define TL_GET_DROPPED_LOGS() tl::get_dropped_logs() | |
| #define TL_DUMP_TRACE_STACKS() tl::dump_all_thread_stacks() | |
| #define TL_MAIN_TID() tl::main_tid() | |
| #else | |
| #define TL_SCOPE() do {} while(0) | |
| #define TL_SCOPE_MEASURE() do {} while(0) | |
| #define TL_SCOPE_ID(id) do {} while(0) | |
| #define TL_SCOPE_MEASURE_ID(id) do {} while(0) | |
| #define TL_STEP(s) do {} while(0) | |
| #define TL_LOG(lvl, stat, ...) do {} while(0) | |
| #define TL_INIT(file) do {} while(0) | |
| #define TL_SHUTDOWN() do {} while(0) | |
| #define TL_FLUSH() do {} while(0) | |
| #define TL_SET_SYNC_MODE(on) do {} while(0) | |
| #define TL_GET_DROPPED_LOGS() 0ULL | |
| #define TL_DUMP_TRACE_STACKS() do {} while(0) | |
| #define TL_MAIN_TID() 0ULL | |
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import struct | |
| import sys | |
| from datetime import datetime, timedelta | |
| from typing import Optional, List, Tuple | |
| BLOCK_SIGNATURE = 0x544C4F47524F4B58 # "TLOGROKX" | |
| MAX_MESSAGE_LEN = 8192 | |
| MAX_CALLSTACK_DEPTH = 256 | |
| ENCRYPTION_KEY = bytes([ | |
| 0xac, 0x0a, 0xb0, 0xeb, 0x54, 0xee, 0x88, 0xe4, | |
| 0xea, 0x13, 0x89, 0x3a, 0xc1, 0x29, 0x0f, 0x24, | |
| 0x02, 0x15, 0xcf, 0x71, 0xab, 0x51, 0x3b, 0xda, | |
| 0x11, 0xde, 0x0f, 0xb1, 0xcd, 0xa9, 0xe4, 0x70 | |
| ]) | |
| ENCRYPTION_NONCE = bytes([ | |
| 0x75, 0xa3, 0xed, 0x51, 0x79, 0x84, 0xc7, 0xfa, | |
| 0x4b, 0x9e, 0xb7, 0xc2 | |
| ]) | |
| LOG_LEVELS = { | |
| 0: "TRACE", | |
| 1: "DEBUG", | |
| 2: "INFO", | |
| 3: "WARN", | |
| 4: "ERROR", | |
| 5: "FATAL", | |
| 255: "DUMP" | |
| } | |
| def rotl(x: int, b: int) -> int: | |
| return ((x << b) & 0xFFFFFFFF) | (x >> (32 - b)) | |
| def quarter_round(a, b, c, d): | |
| a = (a + b) & 0xFFFFFFFF; d = rotl(d ^ a, 16) | |
| c = (c + d) & 0xFFFFFFFF; b = rotl(b ^ c, 12) | |
| a = (a + b) & 0xFFFFFFFF; d = rotl(d ^ a, 8) | |
| c = (c + d) & 0xFFFFFFFF; b = rotl(b ^ c, 7) | |
| return a, b, c, d | |
| def chacha20_block(state: List[int]) -> bytes: | |
| x = state[:] | |
| for _ in range(10): # 20 rounds | |
| # Column rounds | |
| x[0], x[4], x[8], x[12] = quarter_round(x[0], x[4], x[8], x[12]) | |
| x[1], x[5], x[9], x[13] = quarter_round(x[1], x[5], x[9], x[13]) | |
| x[2], x[6], x[10], x[14] = quarter_round(x[2], x[6], x[10], x[14]) | |
| x[3], x[7], x[11], x[15] = quarter_round(x[3], x[7], x[11], x[15]) | |
| # Diagonal rounds | |
| x[0], x[5], x[10], x[15] = quarter_round(x[0], x[5], x[10], x[15]) | |
| x[1], x[6], x[11], x[12] = quarter_round(x[1], x[6], x[11], x[12]) | |
| x[2], x[7], x[8], x[13] = quarter_round(x[2], x[7], x[8], x[13]) | |
| x[3], x[4], x[9], x[14] = quarter_round(x[3], x[4], x[9], x[14]) | |
| for i in range(16): | |
| x[i] = (x[i] + state[i]) & 0xFFFFFFFF | |
| keystream = bytearray() | |
| for word in x: | |
| keystream.extend(struct.pack("<I", word)) | |
| return bytes(keystream) | |
| def chacha20_decrypt(data: bytes) -> bytes: | |
| if len(data) == 0: | |
| return data | |
| state = [0] * 16 | |
| sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] | |
| state[0:4] = sigma | |
| # Key | |
| for i in range(8): | |
| state[4 + i] = struct.unpack("<I", ENCRYPTION_KEY[i*4:(i+1)*4])[0] | |
| # Counter = 0 | |
| state[12] = 0 | |
| # Nonce | |
| state[13] = struct.unpack("<I", ENCRYPTION_NONCE[0:4])[0] | |
| state[14] = struct.unpack("<I", ENCRYPTION_NONCE[4:8])[0] | |
| state[15] = struct.unpack("<I", ENCRYPTION_NONCE[8:12])[0] | |
| decrypted = bytearray() | |
| pos = 0 | |
| counter = 0 | |
| while pos < len(data): | |
| block_state = state[:] | |
| block_state[12] = counter & 0xFFFFFFFF | |
| keystream = chacha20_block(block_state) | |
| block_size = min(64, len(data) - pos) | |
| for i in range(block_size): | |
| decrypted.append(data[pos + i] ^ keystream[i]) | |
| pos += block_size | |
| counter += 1 | |
| return bytes(decrypted) | |
| class LogEntryParser: | |
| def __init__(self, filename: str, decrypt: bool = True): | |
| self.filename = filename | |
| self.decrypt = decrypt | |
| def parse(self): | |
| with open(self.filename, "rb") as f: | |
| data = f.read() | |
| pos = 0 | |
| entry_count = 0 | |
| error_count = 0 | |
| print(f"Parsing log file: {self.filename}") | |
| print(f"File size: {len(data)} bytes\n") | |
| while pos + 16 <= len(data): | |
| signature = struct.unpack_from("<Q", data, pos)[0] | |
| if signature != BLOCK_SIGNATURE: | |
| next_sig = data.find(struct.pack("<Q", BLOCK_SIGNATURE), pos + 1) | |
| if next_sig == -1: | |
| print("No more valid blocks found.") | |
| break | |
| print(f"[!] Invalid block at 0x{pos:X}, skipping to next signature at 0x{next_sig:X}") | |
| pos = next_sig | |
| continue | |
| total_size = struct.unpack_from("<Q", data, pos + 8)[0] | |
| if pos + total_size > len(data): | |
| print(f"Incomplete block at 0x{pos:X} (declared size {total_size}), stopping.") | |
| break | |
| block_data = data[pos + 16 : pos + total_size] | |
| try: | |
| self.print_entry(block_data, entry_count, self.decrypt) | |
| entry_count += 1 | |
| except Exception as e: | |
| print(f"[ERROR] Failed to parse block at 0x{pos:X}: {e}") | |
| error_count += 1 | |
| pos += total_size | |
| print(f"\n=== Summary ===") | |
| print(f"Total entries parsed: {entry_count}") | |
| print(f"Errors: {error_count}") | |
| def print_entry(self, payload: bytes, index: int, decrypt: bool): | |
| if decrypt: | |
| payload = chacha20_decrypt(payload) | |
| offset = 0 | |
| # LogEntryHeader | |
| header_fmt = "<Q B B Q I q H H" | |
| header_size = struct.calcsize(header_fmt) | |
| signature, version, level, status, thread_id, timestamp_us, callstack_count, message_len = \ | |
| struct.unpack_from(header_fmt, payload, offset) | |
| offset += header_size | |
| # Callstack section | |
| callstack_count2 = struct.unpack_from("<H", payload, offset)[0] | |
| offset += 2 | |
| callstack = [] | |
| for _ in range(callstack_count): | |
| func_id = struct.unpack_from("<I", payload, offset)[0] | |
| offset += 4 | |
| step = struct.unpack_from("<H", payload, offset)[0] | |
| offset += 2 | |
| measure = struct.unpack_from("<f", payload, offset)[0] | |
| offset += 4 | |
| callstack.append((func_id, step, measure)) | |
| # Message section | |
| message_len2 = struct.unpack_from("<H", payload, offset)[0] | |
| offset += 2 | |
| message_bytes = payload[offset : offset + message_len] | |
| message = message_bytes.decode('utf-8', errors='replace') | |
| # Timestamp | |
| dt = datetime(1970, 1, 1) + timedelta(seconds=timestamp_us) | |
| level_name = LOG_LEVELS.get(level, f"LEVEL_{level}") | |
| print(f"[{index:04d}] {dt.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} " | |
| f"| T:{thread_id} | {level_name} | Status:0x{status:016X}") | |
| if callstack: | |
| stack_str = " -> ".join(f"Function({func_id}), Step({step}), Measure({measure})" for func_id, step, measure in callstack) | |
| print(f" Callstack: {stack_str}") | |
| print(f" Message: {message}") | |
| print("-" * 80) | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2: | |
| print("Usage: python log_viewer.py <log_file.bin> [no_decrypt]") | |
| sys.exit(1) | |
| filename = sys.argv[1] | |
| decrypt = "no_decrypt" not in sys.argv | |
| parser = LogEntryParser(filename, decrypt=decrypt) | |
| parser.parse() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment