Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Place a dynamic probe on a .NET Core method
#!/usr/bin/env python
# USAGE: place-probe [-h] [--dry-run] [--debug] PID METHOD
# This tool helps place dynamic probes on .NET methods that were
# CrossGen-generated (compiled ahead of time). To use the tool,
# the CrossGen-generated assemblies need to have perfmaps generated
# by CrossGen /CreatePerfMap, expected in the /tmp directory.
# Copyright (C) 2018, Sasha Goldshtein
# Licensed under the MIT License
import argparse
import os
import re
import subprocess
class Section(object):
def __init__(self, start, perms, offset, path):
self.start = int(start, 16)
self.perms = perms
self.offset = int(offset, 16)
self.path = path
def assembly_from_map_file(map_file):
return re.match("/tmp/(.*)\.ni\.{.*}.map", map_file).group(1) + ".dll"
def all_sections(pid):
sections = {}
with open("/proc/%d/maps" % pid, "r") as maps:
for line in maps:
match = re.match(r"(\S+)-\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+(\S+)", line.strip())
if match is None:
start, perms, offset, path =, 2, 3, 4)
if '/' not in path:
filename = os.path.basename(path)
section = Section(start, perms, offset, path)
if filename in sections:
sections[filename] = [section]
return sections
def place_probe(path, offset):
command = "sudo perf probe -x %s --add 0x%x" % (path, offset)
if args.dry_run:
subprocess.check_call(command, shell=True)
parser = argparse.ArgumentParser(description="Place dynamic tracing probes on a managed method " +
"that resides in a crossgen-compiled assembly. For .NET Core on Linux.",
epilog="EXAMPLE: ./ 1234 'Thread::Sleep'")
parser.add_argument("pid", type=int, help="the dotnet process id")
parser.add_argument("symbol", type=str, help="the symbol on which to place the probe")
parser.add_argument("--dry-run", action="store_true",
help="print the symbol and the command but don't place the probe")
parser.add_argument("--debug", action="store_true", help="print diagnostic information")
args = parser.parse_args()
sections = all_sections(
output = subprocess.check_output("grep '%s' /tmp/*.ni.*.map" % args.symbol,
for line in output.strip().split('\n'):
parts = line.split()
map_file, address = parts[0].split(':')
address = int(address, 16)
assembly = assembly_from_map_file(map_file)
symbol = str.join(' ', parts[2:])
if args.dry_run:
print("\n" + symbol)
first_section = sections[assembly][0]
exec_section = [section for section in sections[assembly] if 'r-xp' == section.perms][0]
offset_from_first = exec_section.start - first_section.start
offset_in_file = exec_section.offset
final_address = address - offset_from_first + offset_in_file
if args.debug:
print("address: %x, offset_from_first: %x, offset_in_file: %x, final_address: %x" %
(address, offset_from_first, offset_in_file, final_address))
place_probe(exec_section.path, final_address)
sudo docker run -v $(CURDIR):/app -it --rm microsoft/dotnet:sdk dotnet new console -o /app
sudo docker run -it --rm -v $(CURDIR):/app microsoft/dotnet:sdk bash -c \
"cd /app && dotnet publish -c Release -o /app/out -r linux-x64"
out/crossgen /Platform_Assemblies_Paths out out/app.dll
mv out/ out/app.dll
COMPlus_PerfMapEnabled=1 $(CURDIR)/out/app
./ generate `pidof app`
sudo perf record -e probe_System:* -p `pidof app` -g -- sleep 10
sudo perf probe --del=*
sudo chown ubuntu
perf script
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.