For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 _penter callback 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 .slmap file next to each binary, mapping each function's RVA (which changes on every compile) to its stable fully qualified name (FQN).

  • Only the .slmap files must travel with their binaries. The tracer DLLs, token, and build session ID can live centrally (on the PATH) 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 _penter callback 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 .slmap file per binary so that coverage can be tied back to stable, human-readable function names (see the next section).

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?

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:

File
What it is
Where it lives

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:

  1. Deploy the build output (for example, .\build\Debug\*), keeping each .slmap beside its binary.

  2. Make the tracer discoverable — either add the agent directory to the PATH, or copy sltracer_x64.dll / sltracer_x86.dll next to the application .exes under test.

  3. 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.

Last updated

Was this helpful?