-
Notifications
You must be signed in to change notification settings - Fork 3
C API
Crash capture exposes a public C interface so other binary modules can integrate with it:
add your own report sections, share key/value data, query liveness, and request dumps.
Everything lives in include/crashcapture_api.h, a single header with no dependencies.
The interface name is CRASHCAPTURE001, version CC_API_VER 1.
You never link against crash capture; the header ships a static CC_FIND_API() that scans the loaded modules for a crash capture binary (by known filenames and by any module whose name contains crashcapture) and calls its CreateInterface("CRASHCAPTURE001").
-
CC_FIND_API(): CCAPI*- locate the loaded crash capture and get itsCCAPItable, orNULLif it isn't loaded yet. -
CC_ENSURE_API(): CCAPI*- convenience wrapper: returns the api only ifCC_API_OKpasses, elseNULL. -
CC_API_OK(api, minSize): bool- true when the api is present, at least version 1, and big enough to contain the firstminSizebytes.- Pass
sizeof(CCAPI)for everything, or the size of a trailing subset you actually use, so older hosts stay compatible.
- Pass
Because crash capture may load after your module, call CC_ENSURE_API() on your game-thread tick until it succeeds.
This "discovery + retry" pattern is the expected usage.
#include "crashcapture_api.h"
static CCAPI* cc = nullptr;
static bool EnsureApi()
{
if (cc) return true;
cc = CC_ENSURE_API();
return cc != nullptr;
}All functions below are members of the CCAPI struct, reached as cc->method(...).
The header marks each one with the threads it is safe on; they are grouped that way here.
-
cc->version_str(): const char*- Crash capture version string (static).
-
cc->build_str(): const char*- Build string (static).
-
cc->side_str(): const char*-
"server"or"client"(static).
-
-
cc->os_str(): const char*- Operating system (static).
-
cc->arch_str(): const char*- Architecture (static).
-
cc->is_hung(): int-
1while the watchdog is handling a freeze (heartbeat and Windows window-probe detectors),0otherwise and before init.
-
-
cc->hung_since_ms(): unsigned long long- Milliseconds since the watchdog declared the current freeze,
0if not hung.
- Milliseconds since the watchdog declared the current freeze,
-
cc->stall_class(): int- What the hung thread is stuck in:
STALL_UNKNOWN=0,STALL_NATIVE=1,STALL_PHYSICS=2,STALL_LUA_INTERP=3,STALL_LUA_JIT=4.
- What the hung thread is stuck in:
-
cc->stall_class_name(int cls): const char*-
"unknown"/"native"/"physics"/"lua"/"lua-jit"for a class number.
-
-
cc->ms_since_pulse(): unsigned long long- Milliseconds since the last heartbeat pulse,
0if none has happened yet. - Build your own liveness logic on this if
is_hungis too unstable.
- Milliseconds since the last heartbeat pulse,
-
cc->config_get(const char* name): const char*- Read-only lookup of launch/runtime settings (
CRASHCAPTURE_*names, with or without the prefix),NULLfor unknown keys.
- Read-only lookup of launch/runtime settings (
-
cc->report_dir(): const char*- Report folder as configured (may be relative).
-
cc->log_path(): const char*- Path of the report/session file currently open, else
NULL. - The buffer is only valid until the next report opens, copy it if you keep it.
- Path of the report/session file currently open, else
-
cc->uptime_ms(): unsigned long long- Milliseconds since crash capture initialized.
-
cc->map_name(): const char*- Current map name,
NULLwhen unknown.
- Current map name,
-
cc->dump(const char* reason): int- Returns
1written,0skipped (not initialized, or another dump/report already in flight - fail-fast, never blocks). -
reasonis truncated to 256 chars. - Any live thread, never a crash context.
- Returns
-
cc->grace(int seconds): void- Suppress freeze detection for the next N seconds (planned heavy work).
- Clamped to
0 < seconds <= 86400.
-
cc->backtrace(unsigned long long* out, int max): int- Walk the calling thread's native stack (return addresses), up to
max, returns the count written. - Live threads only, not from crash/signal context.
- Walk the calling thread's native stack (return addresses), up to
-
cc->resolve(unsigned long long addr, char* out, unsigned outsz): int- Symbolize one address (
"name+0xoff (file:line)"when debug info allows). - Returns
1wrote a name,0failed or the symbol engine is busy (shared with the report path, fail-fast, never blocks).
- Symbolize one address (
-
cc->pulse(): void- Feed the heartbeat (only useful if crash capture is not already pulsing via the plugin GameFrame or the Lua timer).
-
cc->set_data(const char* key, const char* value): int- Register a key/value line shown in the report's "Third-party data" section.
- Both strings are copied (key <= 47 chars, value <= 255 chars) and stay until overwritten or cleared.
- Returns
1stored,0table full.
-
cc->clear_data(const char* key): void- Remove a key previously set with
set_data.
- Remove a key previously set with
-
cc->add_section(const char* name, CCSectionFn fn, void* user): int- Register a report section that runs at report-write time.
-
cc->remove_section(const char* name): void- Unregister a section added with
add_section. - Call before your module unloads, or its callback pointer dangles.
- Unregister a section added with
A section is a callback that runs at report-write time (crash, freeze, or dump).
Its output lands in the report under ## <name>.
The callback runs wrapped in crash capture's crash protection: if it faults, the section is skipped so the report survives.
That protection only helps if the callback stays minimal - no allocation, no locks, no engine calls.
Write the section through the writer you are given:
-
w->print(ctx, const char* text)- append one or more lines of text (include your own
\n).
- append one or more lines of text (include your own
-
w->format(ctx, const char* fmt, ...)- printf-style append; output is truncated to ~1024 chars per call.
The void* user you pass to add_section is round-tripped back to your callback untouched.
Warning
Not calling remove_section before your module unloads can lead to crashes if the callback pointer is freed or moved.
A minimal third-party binary module: discovery + retry, a report section with fresh data captured at crash time, and key/value data.
Build it like any GMod binary module (see Building) and require it after require("crashcapture"); call MyThing_Tick() from your own timer or hook so it runs on the game thread.
#include "crashcapture_api.h"
#include <stdio.h>
#ifdef _WIN32
#define MY_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_EXPORT extern "C" __attribute__((visibility("default")))
#endif
struct lua_State;
static CCAPI* cc = nullptr;
static int g_ticks = 0;
static int g_offenders = 0;
static void MyThingSection(void* user, CCSectionCtx* w)
{
w->format(w, "ticks=%d offenders=%d\n", g_ticks, g_offenders);
}
static bool EnsureApi()
{
if (cc) return true;
cc = CC_ENSURE_API();
if (!cc) return false;
cc->add_section("mything", MyThingSection, nullptr);
cc->set_data("mything.ticks", "0");
return true;
}
MY_EXPORT int gmod13_open(struct lua_State* L)
{
(void)L;
EnsureApi(); // crashcapture may not be loaded yet; retry via MyThing_Tick
return 0;
}
MY_EXPORT int gmod13_close(struct lua_State* L)
{
(void)L;
if (cc) {
cc->remove_section("mything");
cc->clear_data("mything.ticks");
cc = nullptr;
}
return 0;
}
MY_EXPORT void MyThing_Tick()
{
g_ticks++;
if (!EnsureApi()) return;
if (cc->is_hung()) {
// the watchdog is already on it, this tick will be reported
}
}CCAPI starts with size and version.
New functions are appended, so callers should validate with CC_API_OK(api, minSize) using the size of the trailing subset they use.
That keeps a module built against a newer header working with an older plugin, as long as it only relies on the fields it sized for.
Getting started
Usage
Features
For module developers