Small .NET 10 sample program for manual rr investigation.
dotnet build samples/RrSample/RrSample.csproj -c DebugBuild first so the rr trace focuses on the sample execution instead of the SDK build.
cc -shared -fPIC -O2 -Wall -Wextra \
-o tools/rr-glibc-guard-workaround.so \
tools/rr-glibc-guard-workaround.c \
-pthread -ldl
rr record \
--env=DOTNET_PerfMapEnabled=1 \
--env=LD_PRELOAD="$PWD/tools/rr-glibc-guard-workaround.so" \
--print-trace-dir=1 \
dotnet samples/RrSample/bin/Debug/net10.0/RrSample.dll manual-runDOTNET_PerfMapEnabled=1 keeps the runtime's normal ReadyToRun and tiered compilation behavior, while asking .NET to emit perf maps and jit dumps for managed/JIT symbolization during manual investigation.
The preload helper is a local compatibility workaround for rr 5.9 with glibc 2.43/Linux 7.0, where glibc uses MADV_GUARD_INSTALL for pthread stack guard pages and rr 5.9 aborts on that madvise advice.
rr replay /path/to/traceOn Zen CPUs, rr may also require disabling the hardware SpecLockMap optimization. Without that system-level workaround, recording can complete but replay can fail with a tick mismatch.
The tools/RrClrmdProbe prototype treats an rr replay point like a dump-like data source. It starts rr replay, connects GDB/MI to rr's remote debug server, implements CLRMD's IDataReader, and lets CLRMD inspect the replayed .NET runtime through rr-backed memory reads.
dotnet run --project tools/RrClrmdProbe/RrClrmdProbe.csproj -- \
/home/kuinox/.local/share/rr/dotnet-5 \
5000Current proof points:
- CLRMD finds
libcoreclr.soin the replayed process. - CLRMD locates the .NET 10 DAC,
libmscordaccore.so. ClrRuntimecan be created from rr/gdb-backed memory.- The GC heap reports
CanWalkHeap = true. - Managed threads are enumerated.
- Managed metadata such as the
System.StringMethodTable can be resolved.
Current limitations:
- Stack walking depends on GDB register reads for the rr replay thread context.
- Source-level managed local names/slots are not implemented yet. The DAP currently exposes frame metadata and CLRMD stack GC roots for each frame.
- Memory reads go through GDB/MI and are page-cached, but are still not optimized for broad heap scans.
src/RrDotNet.Dap is an initial DAP adapter using Draco.Dap. It exposes the rr replay point as a stopped debug session backed by the CLRMD-over-rr/gdb reader.
Build:
dotnet build src/RrDotNet.Dap/RrDotNet.Dap.csproj -c DebugLaunch arguments:
{
"type": "rr-dotnet",
"request": "launch",
"name": "rr .NET snapshot",
"program": "dotnet",
"args": [
"src/RrDotNet.Dap/bin/Debug/net10.0/RrDotNet.Dap.dll"
],
"trace": "/home/kuinox/.local/share/rr/dotnet-5",
"event": "5000"
}Current DAP support:
initializelaunch/attachthreadsstackTracewith best-effort managed frames from CLRMDscopesvariableswith trace/runtime/module summary, per-frame method/IP/SP metadata, and CLRMD stack rootssetBreakpointsreturns unverified placeholderscontinue,stepIn,next,stepOut,stepBack, andreverseContinueroute to rr through GDB/MIterminate
The adapter is still early, but execution requests now move the rr replay through GDB/MI. The next important steps are source breakpoint binding and true source-level local-variable inspection.
The local debugger contribution lives in vscode-extension/rr-dotnet.
For this workstation it has also been installed into:
/home/kuinox/.vscode/extensions/kuinox.rr-dotnet-0.0.1
If VS Code still says the rr-dotnet debug type is unsupported, refresh that install:
mkdir -p /home/kuinox/.vscode/extensions/kuinox.rr-dotnet-0.0.1
cp vscode-extension/rr-dotnet/package.json \
/home/kuinox/.vscode/extensions/kuinox.rr-dotnet-0.0.1/package.jsonThen reload VS Code and use a launch config like:
{
"name": "rr .NET snapshot",
"type": "rr-dotnet",
"request": "launch",
"trace": "/home/kuinox/.local/share/rr/dotnet-5",
"event": "5000"
}Useful manual landmarks in the sample:
ComputeScoreFibonacciBuildChecksumAsyncCheckpointThrowKnownFailureBackgroundCounter