Skip to content

Instantly share code, notes, and snippets.

Embed
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:
continue
start, perms, offset, path = match.group(1, 2, 3, 4)
if '/' not in path:
continue
filename = os.path.basename(path)
section = Section(start, perms, offset, path)
if filename in sections:
sections[filename].append(section)
else:
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:
print(command)
else:
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: ./place-probe.py 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(args.pid)
output = subprocess.check_output("grep '%s' /tmp/*.ni.*.map" % args.symbol,
shell=True)
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)
create:
sudo docker run -v $(CURDIR):/app -it --rm microsoft/dotnet:sdk dotnet new console -o /app
build:
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/app.ni.exe out/app.dll
run:
COMPlus_PerfMapEnabled=1 $(CURDIR)/out/app
generate:
./dotnet-mapgen-v3.py generate `pidof app`
record:
sudo perf record -e probe_System:* -p `pidof app` -g -- sleep 10
sudo perf probe --del=*
sudo chown ubuntu perf.data
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.