Why Does SeaLights Use Static Instrumentation for C/C++ (MSVC)?
TL;DR
C++ compiles directly to native machine code, so — like Go — there is no runtime VM to hook into. SeaLights uses build-time static instrumentation.
On MSVC, instrumentation adds a lightweight
_pentercallback at the start of each function to track execution during test runs. Your business logic and control flow stay unchanged.The scan also writes a
.slmapfile next to each binary, mapping each function's RVA (which changes on every compile) to its stable fully qualified name (FQN).Only the
.slmapfiles must travel with their binaries. The tracer DLLs, token, and build session ID can live centrally (on thePATH) or come from environment variables — they don't need to go into your application installer.Run the instrumented build in test environments only, and keep production on your regular build.
For implementation guidance or questions about your specific environment, please don't hesitate to contact your SeaLights support representative or solutions engineer.
Why doesn't C/C++ support runtime instrumentation like Java or .NET?
Java and .NET run on an intermediate runtime — the JVM and the CLR. That runtime lets tools attach as agents or profilers and modify bytecode/IL in memory at runtime to collect coverage.
C++ has no such layer. It compiles straight to native machine code, with no intermediate representation and no runtime hook exposed during execution. That means runtime instrumentation is not possible for C++, and coverage instrumentation has to happen earlier — at build time.
What does SeaLights do instead?
During the build, SeaLights performs static instrumentation:
On MSVC, a lightweight
_pentercallback is added at the start of every function, using the compiler's function-entry hook. When the function runs, the callback records it — this is what enables method-level coverage.The change is limited to that entry hook. The function's own logic and control flow stay exactly as written — nothing related to your business logic is touched.
Alongside the compiled binaries, the scanner writes one
.slmapfile per binary so that coverage can be tied back to stable, human-readable function names (see the next section).
What SeaLights does with static instrumentation in C++ is conceptually the same as what happens "behind the scenes" during dynamic instrumentation in Java/.NET. The difference is when it happens — at build time instead of at runtime.
When the SeaLights runtime isn't present — for example, in a production build — no coverage is collected, and there is no coverage-related runtime overhead.
What is the .slmap file, and why must it sit next to its binary?
.slmap file, and why must it sit next to its binary?At runtime, SeaLights needs to know which function ran and match it to the same function in your source — reliably, and comparably across builds. Native code makes that hard: a function's location inside a binary, its RVA (relative virtual address), changes on every compilation, even when the source is identical. The RVA alone can't identify a function from one build to the next.
The .slmap solves this. It's a small binary file, specific to MSVC, laid out as:
<header> <number of entries> followed, for each function, by <RVA> <hash of the function's FQN>
RVA — where the function sits inside that exact
.dll/.exe. Changes every compile.FQN — the function's fully qualified name, which is stable across builds.
At runtime, the flow is: the _penter hook reports the RVA that executed → the .slmap resolves that RVA to the stable FQN → coverage maps to the same function no matter how the addresses shifted.
Because those RVAs are only valid for the exact binary they were generated from, each .slmap must sit beside its own binary. It can't be reused, centralized, or published as a separate package.
What files does SeaLights add, and where do they go?
SeaLights adds files in two groups, with two different placement rules:
sltracer_x64.dll / sltracer_x86.dll
The runtime tracer that _penter calls into. Same for every build.
Central, on the PATH (or copied next to the application .exes).
Token
The agent authentication token.
A secret environment variable (recommended), or a token file in the central location.
buildSessionID.txt
The SeaLights-generated UUID for the build. Version-specific value.
A central file kept in sync with the deployed version, an environment variable, or bundled in the application installer for simplicity.
.slmap files
Per-binary RVA → FQN map.
Beside each binary (mandatory).
Only the .slmap files are tied to your binaries. Everything else can be managed centrally or through environment variables, so it doesn't need to go into your application installer.
How do we deploy safely (test vs. production)?
The goal is simple: instrumented binaries run in test environments only, and production keeps its regular build.
Recommended — separate builds. Produce an instrumented build for testing and a clean, uninstrumented build for production. Your CI/CD picks the right one per environment. This gives the strongest separation and keeps production completely untouched.
To deploy into a test environment:
Deploy the build output (for example,
.\build\Debug\*), keeping each.slmapbeside its binary.Make the tracer discoverable — either add the agent directory to the
PATH, or copysltracer_x64.dll/sltracer_x86.dllnext to the application.exes under test.Provide the token (a secret environment variable is cleanest) and the build session ID.
Making the tracer discoverable via PATH (PowerShell):
Since the tracer lives in the SeaLights agent bundle that's already required on the test machine, adding the agent directory to PATH is usually all you need.
Where possible, run your coverage tests against the Debug build. Release-mode optimizations — inlining, dead-code elimination, identical-code folding — can drop or merge functions that were instrumented at scan time. Those functions then never fire their _penter hook at runtime and can appear as uncovered even when the code effectively ran. A Debug build keeps functions intact, so the coverage you see lines up with what was scanned.
Related
Why Does SeaLights Use Static Instrumentation for Go? — the same build-time approach, applied to Go.
Last updated
Was this helpful?

