[vm] Allocation profiling via uprobes

This CL adds basic infrastructure and tooling to perform allocation
profiling using uprobes.

See runtime/tools/profiling/README.md for more details.

TEST=manually tested, requires root access

Cq-Include-Trybots: luci.dart.try:vm-aot-mac-release-arm64-try
Change-Id: Id68d181740dbf227a12d8cdba84b11a9518e75a7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382405
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Vyacheslav Egorov
2024-10-08 13:56:45 +00:00
committed by Commit Queue
parent e1dc36994a
commit 284e9e91c8
20 changed files with 3295 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
+3
View File
@@ -0,0 +1,3 @@
## 0.1.0
- Initial version.
+64
View File
@@ -0,0 +1,64 @@
Various tools for low level profing of code running on the Dart VM.
# Uprobe based profiling
[uprobes](https://www.kernel.org/doc/html/latest/trace/uprobetracer.html) is
a user-space dynamic tracing mechanism. Using this mechanism the kernel can
be instructed to place a tracepoint at a particular file offset within a
specific binary. Whenever this tracepoint is hit the kernel will fetch values
from the execution context based on the uprobe's description and emit an event.
A developer can subscribe to uprobe events in a few different ways including
[perf_event_open](https://man7.org/linux/man-pages/man2/perf_event_open.2.html)
syscall. uprobes have been enabled by default on all newish Linux kernels
(4.14+), however they are only truly usable on Android/ARM64 starting from
5.10+. `bin/set_uprobe.dart` is a helper script for placing uprobes inside
binaries and using this for profiling.
The core workflow looks like this:
```console
$ sudo $(which dart) runtime/tools/profiling/bin/set_uprobe.dart probeName symbol binary
```
This will create an uprobe with name `probeName` which triggers whenever
the given `symbol` inside the given `binary` is called. You can then record
an event (and collect the call stack) using:
```console
$ sudo perf record -g -e uprobes:probeName ...
```
## Allocation profiling with uprobes
AOT compiler can emit a special probe point (`stub AllocationProbePoint`) which
triggers for each new space allocation from generated code. `set_uprobe` script
has special support for this probe point: it will configure probe point to
record additional information (address of allocated object, allocation top and
cid of the allocated object) allowing to post process collected data into
an actual allocation profile.
Start by compiling your application with `--generate-probe-points`:
```console
$ pkg/vm/tool/precompiler2 --generate-probe-points test.dart test.aot
```
Then install uprobe on `AllocationProbePoint`:
```console
$ sudo $(which dart) runtime/tools/profiling/bin/set_uprobe.dart alloc AllocationProbePoint test.aot
```
Record the profile:
```
$ sudo perf record -g -e uprobes:alloc out/ReleaseX64/dart_precompiled_runtime test.aot
$ sudo chmod 0755 perf.data
```
Produce a coalesced allocation profile from the recording:
```
$ dart runtime/tools/profiling/bin/convert_allocation_profile.dart perf.data
$ pprof -flame pprof.profile
```
@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,537 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'dart:typed_data';
import 'dart:ffi';
import 'package:fixnum/fixnum.dart' hide Int32;
import 'package:profiling/src/perf/perf_data.dart';
import 'package:profiling/src/symbols.dart';
import 'package:profiling/src/pprof/generated/profile.pb.dart' as pprof;
/// `PERF_RECORD_SAMPLE` with the following optional fields:
///
/// `PERF_SAMPLE_IP`, `PERF_SAMPLE_TID`, `PERF_SAMPLE_TIME`, `PERF_SAMPLE_CALLCHAIN`,
/// `PERF_SAMPLE_CPU`, `PERF_SAMPLE_PERIOD`, `PERF_SAMPLE_RAW`.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L947
final class SampleEvent extends Struct {
external EventHeader header;
/// Enabled by `PERF_SAMPLE_IP`
@Uint64()
external int ip;
/// Enabled by `PERF_SAMPLE_TID`
@Uint32()
external int pid;
/// Enabled by `PERF_SAMPLE_TID`
@Uint32()
external int tid;
/// Enabled by `PERF_SAMPLE_TIME`
@Uint64()
external int time;
/// Enabled by `PERF_SAMPLE_CPU`
@Uint32()
external int cpu;
/// Enabled by `PERF_SAMPLE_CPU`
@Uint32()
external int res;
/// Enabled by `PERF_SAMPLE_PERIOD`
@Uint64()
external int period;
/// Enabled by `PERF_SAMPLE_CALLCHAIN`
@Uint64()
external int nr;
/// Enabled by `PERF_SAMPLE_CALLCHAIN`
@Array.variable()
external Array<Uint64> ips;
}
/// Data recorded by the probe stored in a `PERF_SAMPLE_RAW`.
///
/// The `size` field is part of `PERF_SAMPLE_RAW` encoding the rest are
/// part of probe data itself. Format for the recorded data can be recovered
/// by loading tracepoint information from an optional section identified
/// by `HEADER_TRACING_DATA` ([OptionalSection.tracingData]). However encoding
/// of that section is extremely bespoke (see `trace-event-read.c` below), so
/// instead of fiddling with that we simply hardcode expected format of the
/// probe. This obviously needs to be kept in sync with `set_uprobe.dart`
/// script.
///
/// ```
/// $ sudo cat /sys/kernel/tracing/events/uprobes/alloc/format
/// name: alloc
/// ID: 1976
/// format:
/// field:unsigned short common_type; offset:0; size:2; signed:0;
/// field:unsigned char common_flags; offset:2; size:1; signed:0;
/// field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
/// field:int common_pid; offset:4; size:4; signed:1;
///
/// field:unsigned long __probe_ip; offset:8; size:8; signed:0;
/// field:s64 addr; offset:16; size:8; signed:1;
/// field:s64 top; offset:24; size:8; signed:1;
/// field:u32 cid; offset:32; size:4; signed:0;
/// print fmt: "(%lx) addr=%Ld top=%Ld cid=%u", REC->__probe_ip, REC->addr, REC->top, REC->cid
/// ```
///
/// [^1]: https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/trace-event-read.c#L375
@Packed(1)
final class ProbeData extends Struct {
@Uint32()
external int size;
@Uint16()
external int commonType;
@Uint8()
external int commonFlags;
@Uint8()
external int commonPreemptCount;
@Int32()
external int commonPid;
@Uint64()
external int probeIp;
@Uint64()
external int addr;
@Uint64()
external int top;
@Uint32()
external int cid;
@override
String toString() =>
'Probe{addr=${addr.formatAsAddress()},top=${top.formatAsAddress()},cid=$cid}';
}
/// Lazily populated mapping between file offsets in a binary and profile
/// location ids.
///
/// This class handles convertion of the file offset to the corresponding
/// symbol name and futher into corresponding location id inside the profile.
final class SymbolsIndex {
final ProfileBuilder profileBuilder;
final Symbols symbols;
final List<Int64?> ids;
SymbolsIndex(this.profileBuilder, this.symbols)
: ids = List<Int64?>.filled(symbols.fileOffsets.length, null);
static final lineRe =
RegExp(r"^(?<addr>[0-9a-f]+)\s+(?<typ>\w+)\s+(?<name>.*)$");
/// Return location id corresponding to the given [fileOffset].
///
/// This function will lazily allocate new ids as necessary by
/// calling [ProfileBuilder.addSymbol].
Int64? symbolId(int fileOffset) {
final index = symbols.symbolIndex(fileOffset);
if (index != null) {
return (ids[index] ??= profileBuilder.addSymbol(symbols.names[index]));
}
return null;
}
}
final class Mapping {
final int baseAddress;
final int length;
final String path;
final int offset;
Mapping({
required this.baseAddress,
required this.length,
required this.path,
required this.offset,
});
}
/// Symbols information for the whole address space.
final class AddressSpaceSymbols {
/// Base addresses for mapping ranges.
///
/// To simplify search we also add ranges that don't have any symbols here.
/// Consider for example that we have two mappings `[A, A')` and `[B, B')`
/// with symbols (`Sym(A)` and `Sym(B)` respectively). In this case:
/// * [baseAddresses] will contain `[0, A, A', B, B']` and
/// * [symbolsIndexes] will contain `[null, SA, null, SymB, null]`.
final Int64List baseAddresses;
/// Symbol indexes corresponding to mappings in [baseAddresses].
final List<SymbolsIndex?> symbolsIndexes;
/// File offsets corresponding to mappings in [baseAddresses].
final Int64List fileOffsets;
AddressSpaceSymbols._(
this.baseAddresses, this.symbolsIndexes, this.fileOffsets);
Int64? symbolId(int address) {
// We use linear search because we assume the number of mappings
// is very small (~2).
final limit = baseAddresses.length - 1;
for (var i = 0; i < limit; i++) {
final start = baseAddresses[i];
final end = baseAddresses[i + 1];
if (start <= address && address < end) {
final fileOffset = address - start + fileOffsets[i];
return symbolsIndexes[i]?.symbolId(fileOffset);
}
}
return null;
}
/// Construct [AddressSpaceSymbols] from [Mapping] records loaded from
/// `perf.data`.
static AddressSpaceSymbols fromMappings(
List<Mapping> mappings, ProfileBuilder profileBuilder) {
// Try loading symbols for each mapping and keep those that
// actually have symbols. Sort resulting list by base address.
final mappingsWithSymbols = <(Mapping, SymbolsIndex)>[];
for (var event in mappings) {
final symbolsIndex = profileBuilder.symbolsIndexFor(event.path);
if (symbolsIndex != null) {
mappingsWithSymbols.add((event, symbolsIndex));
}
}
mappingsWithSymbols
.sort((a, b) => a.$1.baseAddress.compareTo(b.$1.baseAddress));
// Build `AddressSpaceSymbols` from mappings with symbols.
//
// Note: we need to accomodate for a situation when two mappings are
// adjacent. However we assume that number of mappings is rather small
// so we don't optimize this code too much.
final result = <({int baseAddress, SymbolsIndex? index, int fileOffset})>[];
void addEntry({
required int baseAddress,
required SymbolsIndex? index,
required int fileOffset,
}) {
if (result.isNotEmpty && result.last.baseAddress == baseAddress) {
// Collapse end of the previous mapping and the start of the new
// mapping.
if (result.last.index != null) {
throw StateError('Unexpected intersection of address ranges');
}
result.removeLast();
}
result.add(
(baseAddress: baseAddress, index: index, fileOffset: fileOffset));
}
addEntry(baseAddress: 0, index: null, fileOffset: 0);
for (var e in mappingsWithSymbols) {
addEntry(
baseAddress: e.$1.baseAddress,
index: e.$2,
fileOffset: e.$1.offset,
);
addEntry(
baseAddress: e.$1.baseAddress + e.$1.length,
index: null,
fileOffset: 0,
);
}
// Split result into individual components.
return AddressSpaceSymbols._(
Int64List.fromList(
result.map((e) => e.baseAddress).toList(growable: false),
),
result.map((e) => e.index).toList(growable: false),
Int64List.fromList(
result.map((e) => e.fileOffset).toList(growable: false),
),
);
}
}
/// A trie node representing a callstack frame.
///
/// To minimize the size of the produced `pprof.profile` we collapse all
/// matching callstacks into a single `pprof.Sample` entry in the profile.
/// This is done by through a simple [trie][1] data structure.
///
/// Nodes corresponding to callstacks from original profile will have not-null
/// non-zero [totalBytes] associated with them. Path to these nodes should
/// be flushed into [pprof.Profile] as individual [pprof.Sample] entries
/// at the end of conversion. See [flushTo].
///
/// [1]: https://en.wikipedia.org/wiki/Trie
final class CallStackTrieNode {
/// [pprof.Profile] location id corresponding to this frame.
final Int64 id;
/// Total number of bytes allocated by this frame.
int totalBytes = 0;
/// Callees of this frame.
late final Map<Int64, CallStackTrieNode> children =
<Int64, CallStackTrieNode>{};
CallStackTrieNode({required this.id});
CallStackTrieNode operator [](Int64 id) =>
children[id] ??= CallStackTrieNode(id: id);
void flushTo(pprof.Profile profile, List<Int64> path) {
if (totalBytes != 0) {
profile.sample.add(
pprof.Sample(locationId: path.reversed)..value.add(Int64(totalBytes)),
);
}
for (var child in children.values) {
path.add(child.id);
child.flushTo(profile, path);
path.removeLast();
}
}
}
/// Helper for building [pprof.Profile].
///
/// It takes care of indexing symbols and managing their ids.
final class ProfileBuilder {
final profile = pprof.Profile();
final symbolTable = <String, Int64>{};
final locationIds = <String, Int64>{};
final callStackTrieRoot = CallStackTrieNode(id: Int64(-1));
ProfileBuilder() {
addString("");
profile.sampleType.add(pprof.ValueType(
type: addString('space'),
unit: addString('bytes'),
));
}
Int64 addString(String str) {
var id = symbolTable[str];
if (id != null) {
return id;
}
id = symbolTable[str] = Int64(symbolTable.length);
profile.stringTable.add(str);
return id;
}
Int64 addSymbol(String symbol) {
var id = locationIds[symbol];
if (id != null) {
return id;
}
id = locationIds[symbol] = Int64(locationIds.length + 1);
profile.function.add(pprof.Function_(id: id, name: addString(symbol)));
profile.location
.add(pprof.Location(id: id, line: [pprof.Line(functionId: id)]));
return id;
}
SymbolsIndex? symbolsIndexFor(String path) {
final symbols = Symbols.load(path);
if (symbols == null) {
return null;
}
return SymbolsIndex(this, symbols);
}
pprof.Profile finishProfile() {
callStackTrieRoot.flushTo(profile, []);
return profile;
}
}
/// Helper for converting raw callstack into its symbolized form.
///
/// We assume that callstack for each new sample usually shares its prefix
/// (e.g. outermost callers, like `main`) with the previously processed
/// sample. This allows us to reuse location ids from the previous sample
/// for the large portion of the stack.
final class SymbolizedCallStackBuilder {
/// Depth of the current stack.
int depth = 0;
/// Raw addresses for each frame in the caller to callee order.
///
/// We do not clear this array between samples allowing us to detect
/// situations when we can reuse entries. Only entries `0..depth-1`
/// correspond to the current stack. Other entries originate from
/// previous samples and might be out of sync with the current sample.
///
/// (`0` is the outermost caller, `1` is its callee, etc).
final pcs = Int64List(200);
/// Trie nodes corresponding to each frame in the stack.
///
/// For entries in the `0..depth-2` range `nodes[i]` is parent of
/// `nodes[i+1]` .
///
final nodes = List<CallStackTrieNode?>.filled(200, null);
/// Trie node for the last frame (either `nodes[depth-1]` or root trie node
/// if [depth] is `0`).
CallStackTrieNode last;
/// `true` when the callstack which is currently being built matches
/// the prefix of the previous callstack.
bool prefixMatches = true;
SymbolizedCallStackBuilder(ProfileBuilder builder)
: last = builder.callStackTrieRoot;
void add(int pc, AddressSpaceSymbols syms) {
if (pcs[depth] != pc) {
// Mismatch between newly added `pc` and the `pc` we have from the
// previous sample. We need to lookup location id for it.
final id = syms.symbolId(pc);
if (id == null) {
// No symbol - drop the frame.
return;
}
pcs[depth] = pc;
last = last[id];
// We might still hit the same node in the trie.
if (nodes[depth] != last) {
nodes[depth] = last;
// From here onward we can't reuse `nodes` because the path has
// diverged.
prefixMatches = false;
}
} else if (!prefixMatches) {
// Address might match - but the path we got here might be different. This
// means we can reuse `id` from the node, but not the node itself.
final id = nodes[depth]!.id;
nodes[depth] = last = last[id];
} else {
// This pc *and* the all previous nodes match. We can just reuse
// the node.
last = nodes[depth]!;
}
depth++;
}
void addTo(ProfileBuilder builder, int allocatedBytes) {
last.totalBytes += allocatedBytes;
}
void reset(ProfileBuilder builder) {
depth = 0;
last = builder.callStackTrieRoot;
prefixMatches = true;
}
}
@pragma('vm:never-inline')
pprof.Profile buildProfileFromPerfData(String path) {
final raf = File(path).openSync();
final profileBuilder = ProfileBuilder();
final perfData = PerfData(raf);
// Check that input file has expected format.
final allAttrs = perfData.readAttrs();
if (allAttrs.length != 1) {
perfData.reportError(
'Expected single perf_event_attrs structure, got ${allAttrs.length}');
}
final attrs = allAttrs.first;
if (attrs.type != TypeId.tracepoint) {
perfData.reportError('Expected to find a file with tracepoint events');
}
const expectedSampleFormat = SampleFormat.ip |
SampleFormat.tid |
SampleFormat.time |
SampleFormat.callchain |
SampleFormat.cpu |
SampleFormat.period |
SampleFormat.raw;
if (attrs.sampleType != expectedSampleFormat) {
perfData.reportError(
'Expected to sample format ${SampleFormat.format(expectedSampleFormat)}'
' got ${SampleFormat.format(attrs.sampleType)}: difference '
'${SampleFormat.format(attrs.sampleType ^ expectedSampleFormat)}');
}
final mappings = <Mapping>[];
perfData.readEvents((type, chunk, pos) {
if (type == EventType.mmap2) {
final event = Struct.create<Mmap2Event>(chunk, pos);
mappings.add(Mapping(
baseAddress: event.addr,
length: event.len,
path: event.filename.toStringFromZeroTerminated(),
offset: event.pgoffs,
));
} else if (type == EventType.sample && mappings.isNotEmpty) {
// TODO: we miss one sample here.
return false; // Break iteration.
}
return true;
});
final syms = AddressSpaceSymbols.fromMappings(mappings, profileBuilder);
final stack = SymbolizedCallStackBuilder(profileBuilder);
perfData.readEvents((type, chunk, pos) {
if (type == EventType.sample) {
final sample = Struct.create<SampleEvent>(chunk, pos);
final probeData = Struct.create<ProbeData>(
chunk, pos + sizeOf<SampleEvent>() + sample.nr * 8);
for (var i = sample.nr - 1; i > 1; i--) {
stack.add(sample.ips[i], syms);
}
if (stack.depth > 0) {
// Accumulate [totalBytes] in the last node.
stack.last.totalBytes += probeData.top - probeData.addr - 1;
}
// Reset the stack for the next sample.
stack.reset(profileBuilder);
}
return true;
});
print('All data loaded - creating profile.');
return profileBuilder.finishProfile();
}
void main(List<String> args) async {
final perfDataPath = args[0];
print('loading $perfDataPath');
final profile = buildProfileFromPerfData(perfDataPath);
print('created ${profile.sample.length} samples');
print('Serializing proto (pprof.profile)');
final result = profile.writeToBuffer();
print('... ${result.length} bytes');
File('pprof.profile').writeAsBytesSync(result);
print('Done');
}
+135
View File
@@ -0,0 +1,135 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:profiling/src/elf_utils.dart';
// TODO(vegorov): update this to support Android ARM64 both for standalone
// binaries and Flutter applications. Prototype code for that is available
// in https://dart-review.googlesource.com/c/sdk/+/239661.
void main(List<String> args) async {
if (args.length != 3) {
print(
'Usage: pkg/vm/tool/set_uprobe.dart <probe-name> <symbol> <AOT snapshot SO file>');
exit(-1);
}
final [probeName, symbol, sharedObject] = args;
final uprobeAddress =
await _computeProbesVirtualAddress(sharedObject, symbol);
final loadingBias = loadingBiasOf(sharedObject);
final uprobeFileOffset = (uprobeAddress + loadingBias).toRadixString(16);
final soName = p.basename(sharedObject);
final soPath = p.canonicalize(p.absolute(sharedObject));
// TODO(vegorov) ARM64 support
final threadRegister = "r14";
final resultRegister = "ax";
final uprobeFormat = symbol == 'AllocationProbePoint'
? 'addr=%$resultRegister:s64 top=+${await _getThreadTopOffset()}(%$threadRegister):s64 cid=-1(%$resultRegister):b20@12/32'
: '';
final probe = 'p:$probeName $soPath:0x$uprobeFileOffset $uprobeFormat';
print(probe);
File('/sys/kernel/tracing/uprobe_events').writeAsStringSync(probe);
}
Future<int> _computeProbesVirtualAddress(
String sharedObject, String targetSymbol) async {
int offset = 0;
if (targetSymbol == 'AllocationProbePoint') {
offset = await _determineAllocProbeOffset(sharedObject);
}
final targetRe = RegExp('\\b$targetSymbol\\b');
final matches = <String, int>{
for (final (:addr, :name) in textSymbolsOf(sharedObject))
if (targetRe.hasMatch(name)) name: addr,
};
if (matches.isEmpty) {
throw 'Symbol $targetSymbol not found in $sharedObject';
}
if (matches.length != 1) {
throw 'Multiple symbols match: ${matches.keys}';
}
final entry = matches.entries.single;
print('placing uprobe on ${entry.key} at '
'0x${entry.value.toRadixString(16)}+$offset');
return entry.value + offset;
}
// `AllocationProbePoint` stub should have a probe placed at a place where
// stack frame is properly setup so that unwinding succeeds. The stub itself
// contains a dummy test immediate instruction which encodes the offset at
// which the probe should be placed.
Future<int> _determineAllocProbeOffset(String sharedObject) async {
// Dump SO file to get the address of the interesting symbol.
final disassembly = await _exec('llvm-objdump', [
'--disassemble-symbols=stub AllocationProbePoint',
'-Mintel',
sharedObject,
]);
// We are looking for `test al, imm` or `tst x0, #imm` where `imm` is a
// hexadecimal immediate encoding offset to the probe point within the stub.
final pattern = RegExp(
r'^\s+[a-f0-9]+:(( [a-f0-9]{2})+| [a-f0-9]{8})\s+(test|tst)\s+(al|x0),\s+#?0x(?<offset>[0-9a-f]+)\s*$',
multiLine: true);
final match = pattern.firstMatch(disassembly);
if (match == null) {
print(disassembly);
throw StateError(
'failed to find test-immediate instruction encoding the probe offset');
}
return int.parse(match.namedGroup('offset')!, radix: 16);
}
Future<String> _getThreadTopOffset() async {
// TODO(vegorov) ARM64 support
final sdkSrc = Platform.script.resolve('../../../..').toFilePath();
await _exec(
'ninja', ['-C', 'out/ReleaseX64', '-j1000', '-l64', 'offsets_extractor'],
workingDirectory: sdkSrc);
final offsets =
await _exec(p.join(sdkSrc, 'out/ReleaseX64/offsets_extractor'), []);
final line = offsets
.split('\n')
.firstWhere((line) => line.contains('Thread_top_offset'));
final offset = RegExp(r' = (?<offset>0x[a-f\d]+);$')
.firstMatch(line)!
.namedGroup('offset')!;
return int.parse(offset).toString();
}
Future<String> _exec(String executable, List<String> args,
{String? workingDirectory}) async {
final result =
await Process.run(executable, args, workingDirectory: workingDirectory);
if (result.exitCode != 0) {
throw StateError('''
Failed to run $executable ${args.join(' ')}
stdout:
${result.stdout}
stderr:
${result.stderr}
''');
}
return result.stdout as String;
}
@@ -0,0 +1,44 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
/// Compute the difference between virtual address and the file offset of the
/// TEXT section. It can be used to convert virtual addresses into
/// file offsets.
int loadingBiasOf(String path) {
final data = Process.runSync('llvm-readelf', ['-l', path]).stdout.split("\n");
for (var line in data) {
line = line.trim();
if (line.startsWith("LOAD") && line.contains("R E")) {
final components = line.split(RegExp(r"\s+"));
final fileOffset = int.parse(components[1]);
final virtAddr = int.parse(components[2]);
return virtAddr - fileOffset;
}
}
throw StateError('Unable to determine loading bias for $path');
}
/// Iterate over all symbols in TEXT section of the given binary.
Iterable<({int addr, String name})> textSymbolsOf(String path) {
// Run `nm -C` on a binary to extract demangled (-C) symbols.
final output = Process.runSync('/usr/bin/nm', ['-C', path]);
final result = (output.stdout as String).split('\n');
if (output.exitCode != 0) throw 'failed to run nm';
// Parse `nm` output looking for `t` (TEXT) symbols. Each line
// has the following format:
final lineRe = RegExp(r"^(?<addr>[0-9a-f]+)\s+(?<typ>\w+)\s+(?<name>.*)$");
// final symbols = <(int, String)>[];
return result.map((line) {
final m = lineRe.firstMatch(line);
if (m != null && m.namedGroup('typ') == 't') {
final addr = int.parse(m.namedGroup('addr')!, radix: 16);
final name = m.namedGroup('name')!;
return (addr: addr, name: name);
}
return null;
}).nonNulls;
}
@@ -0,0 +1,630 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// This library file contains data structures and helper methods for parsing
/// `perf.data` files produced by `perf` tool on Linux.
///
/// Format of this file is documented in:
///
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/Documentation/perf.data-file-format.txt
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h
/// * https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h
///
library;
import 'dart:ffi';
import 'dart:io';
import 'dart:math' as math;
import 'dart:typed_data';
/// `struct perf_header`: header of the `perf.data` file.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L64
final class Header extends Struct {
@Array(8)
external Array<Uint8> magic;
@Uint64()
external int size;
@Uint64()
external int attrSize;
external FileSection attrs;
external FileSection data;
external FileSection eventTypes;
@Uint64()
external int flags;
@Array(3)
external Array<Uint64> flags1;
}
/// `struct perf_file_section`: section inside `perf.data` file.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L59
final class FileSection extends Struct {
@Uint64()
external int offset;
@Uint64()
external int size;
@override
String toString() {
return 'PerfFileSection{offset=$offset,size=$size}';
}
}
/// Optional sections inside `perf.data` file.
///
/// The section is present iff corresponding bit in [PerfHeader.flags] is set.
///
/// `PerfFileSection` descriptors for present sections will follow in sequence
/// immediately after the data section (i.e. the first `PerfFileSection` will
/// be located at `header.data.offset + header.data.size` offset).
///
/// See [PerfData.readOptionalSectionHeaders].
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/tools/perf/util/header.h#L15
enum OptionalSection {
reserved,
tracingData,
buildId,
hostname,
osRelease,
version,
arch,
nrCpus,
cpuDesc,
cpuId,
totalMem,
cmdLine,
eventDesc,
cpuTopology,
numaTopology,
branchStack,
groupDesc,
auxTrace,
stat,
cache,
sampleTime,
sampleTopology,
clockId,
dirFormat,
bpfProgInfo,
bpfBtf,
compressed,
cpuPmuCaps,
clockData,
hybridTopology,
pmuCaps
}
/// `perf_event_header`: common header of all event entries.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L815
final class EventHeader extends Struct {
@Uint32()
external int type;
@Uint16()
external int misc;
@Uint16()
external int size;
@override
String toString() => 'PerfEventHeader{type=$type,misc=$misc,size=$size}';
}
/// `perf_event_attr`: configuration of the event monitored by `perf`.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L389
final class EventAttr extends Struct {
/// Major type: hardware/software/tracepoint/etc.
///
/// See [EventType].
@Uint32()
external int type;
@Uint32()
external int size;
/// Type specific configuration information.
@Uint64()
external int config;
@Uint64()
external int samplePeriodOrFreq;
@Uint64()
external int sampleType;
@Uint64()
external int readFormat;
/// Various bit fields which we currently don't care about.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L414
@Uint64()
external int flags;
@Uint32()
external int wakeupEventOrWatermark;
@Uint32()
external int bpType;
/// Union of `bp_addr`/`kprobe_func`/`uprobe_path`/`config1`
@Uint64()
external int config1;
/// Union of `bp_len`/`kprobe_addr`/`probe_offset`/`config2`
@Uint64()
external int config2;
/// One of `enum perf_branch_sample_type`
@Uint64()
external int branchSampleType;
/// Defines set of user regs to dump on samples.
/// See asm/perf_regs.h for details.
@Uint64()
external int sampleRegsUser;
/// Defines size of the user stack to dump on samples.
@Uint32()
external int sampleStackUser;
@Int32()
external int clockid;
/// Defines set of regs to dump for each sample
/// state captured on:
/// - precise = 0: PMU interrupt
/// - precise > 0: sampled instruction
///
/// See asm/perf_regs.h for details.
@Uint64()
external int sampleRegsIntr;
/// Wakeup watermark for AUX area
@Uint32()
external int auxWatermark;
@Uint16()
external int sampleMaxStack;
@Uint16()
external int reserved2;
@Uint32()
external int auxSampleSize;
@Uint32()
external int reserved3;
/// User provided data if sigtrap=1, passed back to user via
/// siginfo_t::si_perf_data, e.g. to permit user to identify the event.
/// Note, siginfo_t::si_perf_data is long-sized, and sig_data will be
/// truncated accordingly on 32 bit architectures.
@Uint64()
external int sigData;
/// Extension of config2
@Uint64()
external int config3;
}
/// `enum perf_event_type`: type of the recorded event.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L838
extension type const EventType(int _) implements int {
/// `PERF_RECORD_MMAP`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L879
static const mmap = EventType(1);
/// `PERF_RECORD_SAMPLE`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L947
static const sample = EventType(9);
/// `PERF_RECORD_MMAP2`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L1035
static const mmap2 = EventType(10);
}
/// `enum perf_type_id`
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L29
extension type const TypeId(int index) implements int {
static const hardware = TypeId(0);
static const software = TypeId(1);
static const tracepoint = TypeId(2);
static const hwCache = TypeId(3);
static const raw = TypeId(4);
static const breakpoint = TypeId(5);
}
/// `enum perf_event_sample_format`: additional information recorded for sample.
///
/// Bits that can be set in [PerfEventAttr.sampleType] to request information
/// in the overflow packets.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L139
extension type const SampleFormat(int bit) implements int {
/// `PERF_SAMPLE_IP`
static const ip = SampleFormat(1 << 0);
/// `PERF_SAMPLE_TID`
static const tid = SampleFormat(1 << 1);
/// `PERF_SAMPLE_TIME`
static const time = SampleFormat(1 << 2);
/// `PERF_SAMPLE_ADDR`
static const addr = SampleFormat(1 << 3);
/// `PERF_SAMPLE_READ`
static const read = SampleFormat(1 << 4);
/// `PERF_SAMPLE_CALLCHAIN`
static const callchain = SampleFormat(1 << 5);
/// `PERF_SAMPLE_ID`
static const id = SampleFormat(1 << 6);
/// `PERF_SAMPLE_CPU`
static const cpu = SampleFormat(1 << 7);
/// `PERF_SAMPLE_PERIOD`
static const period = SampleFormat(1 << 8);
/// `PERF_SAMPLE_STREAM_ID`
static const streamId = SampleFormat(1 << 9);
/// `PERF_SAMPLE_RAW`
static const raw = SampleFormat(1 << 10);
/// `PERF_SAMPLE_BRANCH_STACK`
static const branchStack = SampleFormat(1 << 11);
/// `PERF_SAMPLE_REGS_USER`
static const regsUser = SampleFormat(1 << 12);
/// `PERF_SAMPLE_STACK_USER`
static const stackUser = SampleFormat(1 << 13);
/// `PERF_SAMPLE_WEIGHT`
static const weight = SampleFormat(1 << 14);
/// `PERF_SAMPLE_DATA_SRC`
static const dataSrc = SampleFormat(1 << 15);
/// `PERF_SAMPLE_IDENTIFIER`
static const identifier = SampleFormat(1 << 16);
/// `PERF_SAMPLE_TRANSACTION`
static const transaction = SampleFormat(1 << 17);
/// `PERF_SAMPLE_REGS_INTR`
static const regsIntr = SampleFormat(1 << 18);
/// `PERF_SAMPLE_PHYS_ADDR`
static const physAddr = SampleFormat(1 << 19);
/// `PERF_SAMPLE_AUX`
static const aux = SampleFormat(1 << 20);
/// `PERF_SAMPLE_CGROUP`
static const cgroup = SampleFormat(1 << 21);
/// `PERF_SAMPLE_DATA_PAGE_SIZE`
static const dataPageSize = SampleFormat(1 << 22);
/// `PERF_SAMPLE_CODE_PAGE_SIZE`
static const codePageSize = SampleFormat(1 << 23);
/// `PERF_SAMPLE_WEIGHT_STRUCT`
static const weightStruct = SampleFormat(1 << 24);
static const bitNames = {
ip: "ip",
tid: "tid",
time: "time",
addr: "addr",
read: "read",
callchain: "callchain",
id: "id",
cpu: "cpu",
period: "period",
streamId: "streamId",
raw: "raw",
branchStack: "branchStack",
regsUser: "regsUser",
stackUser: "stackUser",
weight: "weight",
dataSrc: "dataSrc",
identifier: "identifier",
transaction: "transaction",
regsIntr: "regsIntr",
physAddr: "physAddr",
aux: "aux",
cgroup: "cgroup",
dataPageSize: "dataPageSize",
codePageSize: "codePageSize",
weightStruct: "weightStruct",
};
static String format(int mask) {
return SampleFormat.bitNames.entries
.where((e) => (mask & e.key) != 0)
.map((e) => e.value)
.join('|');
}
}
/// `PERF_RECORD_MMAP`
///
/// The `MMAP` events record the `PROT_EXEC` mappings so that we can
/// correlate userspace `IP`s to code.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L879
final class MmapEvent extends Struct {
external EventHeader header;
@Uint32()
external int pid;
@Uint32()
external int tid;
@Uint64()
external int addr;
@Uint64()
external int len;
@Uint64()
external int pgoffs;
@Array.variable()
external Array<Uint8> filename;
@override
String toString() => 'MmapEvent{addr=${addr.formatAsAddress()},'
'len=$len,pgoffs=$pgoffs,'
'filename=${filename.toStringFromZeroTerminated()}}';
}
final class BuildId extends Struct {
@Uint8()
external int size;
@Uint8()
external int reserved1;
@Uint16()
external int reserved2;
@Array(20)
external Array<Uint8> buildId;
}
final class Ino extends Struct {
@Uint32()
external int maj;
@Uint32()
external int min;
@Uint64()
external int ino;
@Uint64()
external int inoGeneration;
}
final class BuildIdOrIno extends Union {
external BuildId buildId;
external Ino ino;
}
/// `PERF_RECORD_MMAP2`
///
/// The `MMAP2` records are an augmented version of `MMAP` (see [MapEvent]),
/// they add `maj`, `min`, `ino` numbers to be used to uniquely identify each
/// mapping.
///
/// https://github.com/torvalds/linux/blob/3e9bff3bbe1355805de919f688bef4baefbfd436/include/uapi/linux/perf_event.h#L1035
final class Mmap2Event extends Struct {
external EventHeader header;
@Uint32()
external int pid;
@Uint32()
external int tid;
@Uint64()
external int addr;
@Uint64()
external int len;
@Uint64()
external int pgoffs;
external BuildIdOrIno buildIdOrIno;
@Uint32()
external int prot;
@Uint32()
external int flags;
@Array.variable()
external Array<Uint8> filename;
@override
String toString() => 'Mmap2Event{addr=${addr.formatAsAddress()},'
'len=$len,pgoffs=$pgoffs,'
'filename=${filename.toStringFromZeroTerminated()}}';
}
extension ArrayToString on Array<Uint8> {
String toStringFromFixedLength(int length) =>
String.fromCharCodes([for (var i = 0; i < length; i++) this[i]]);
String toStringFromZeroTerminated() {
final sb = StringBuffer();
for (var i = 0; this[i] != 0; i++) {
sb.writeCharCode(this[i]);
}
return sb.toString();
}
}
extension FormatAsAddress on int {
String formatAsAddress() => toRadixString(16);
}
const int kb = 1024;
const int mb = 1024 * kb;
final class StreamingSectionReader {
final RandomAccessFile f;
final FileSection section;
final chunk = Uint8List(256 * mb);
/// Number of bytes available in the chunk.
int chunkBytes = 0;
/// Offset from the start of the section to the start of the chunk.
int chunkOffset = 0;
/// Position within the chunk.
int pos = 0;
StreamingSectionReader(this.f, this.section) {
f.setPositionSync(section.offset);
refill();
}
bool ensure(int bytes) {
if (chunkBytes < (pos + bytes)) {
refill();
}
return chunkBytes >= (pos + bytes);
}
void refill() {
final int leftOverBytes = chunkBytes - pos;
for (int i = 0; i < leftOverBytes; i++) {
chunk[i] = chunk[i + pos];
}
chunkOffset += pos;
pos = 0;
// Are there any more bytes left to read?
if (chunkOffset >= section.size) {
chunkBytes = 0;
return;
}
print(
"processed $chunkOffset bytes of ${section.size} total (${(chunkOffset / section.size * 100).floor()} %)");
final bytesAlreadyRead = chunkOffset + leftOverBytes;
final bytesToRead =
math.min(section.size - bytesAlreadyRead, chunk.length - leftOverBytes);
final bytesRead =
f.readIntoSync(chunk, leftOverBytes, leftOverBytes + bytesToRead);
chunkBytes = bytesRead + leftOverBytes;
}
}
final class PerfData {
final RandomAccessFile f;
final Header header;
PerfData(this.f)
: header = Struct.create<Header>(f.readSync(sizeOf<Header>())) {
final magic = header.magic.toStringFromFixedLength(8);
if (magic != 'PERFILE2') {
reportError('Incorrect magic in ${f.path} - $magic');
}
}
List<EventAttr> readAttrs() {
f.setPositionSync(header.attrs.offset);
final attrs = f.readSync(header.attrs.size);
final result = <EventAttr>[];
int pos = 0;
while (pos + sizeOf<EventAttr>() < attrs.length) {
final attr = Struct.create<EventAttr>(attrs, pos);
result.add(attr);
pos += attr.size;
}
return result;
}
Map<OptionalSection, FileSection> readOptionalSectionHeaders() {
f.setPositionSync(header.data.offset + header.data.size);
final optionalHeaders =
f.readSync(sizeOf<FileSection>() * OptionalSection.values.length);
int headerIndex = 0;
return {
for (final flag in OptionalSection.values)
if (header.flags & (1 << flag.index) != 0)
flag: Struct.create<FileSection>(
optionalHeaders, sizeOf<FileSection>() * headerIndex++),
};
}
late final _dataReader = StreamingSectionReader(f, header.data);
@pragma('vm:prefer-inline')
void readEvents(bool Function(int type, Uint8List chunk, int pos) callback) {
final reader = _dataReader;
while (true) {
if (!reader.ensure(sizeOf<EventHeader>())) {
// No more events.
return;
}
// Note: `reader.ensure` might refill the chunk and invalidate
// created struct so extract values eagerly.
final EventHeader(:type, :size) =
Struct.create<EventHeader>(reader.chunk, reader.pos);
if (!reader.ensure(size)) {
return;
}
// At this point we are guaranteed to have the whole event in the chunk
// starting at `reader.pos`.
if (!callback(type, reader.chunk, reader.pos)) {
reader.pos += size;
return;
}
reader.pos += size;
}
}
Never reportError(String message) => throw ParseError(f.path, message);
}
final class ParseError extends Error {
final String file;
final String message;
ParseError(this.file, this.message);
@override
String toString() => 'Failed to parse $file: $message';
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
//
// Generated code. Do not modify.
// source: profile.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
@@ -0,0 +1,243 @@
//
// Generated code. Do not modify.
// source: profile.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use profileDescriptor instead')
const Profile$json = {
'1': 'Profile',
'2': [
{
'1': 'sample_type',
'3': 1,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.ValueType',
'10': 'sampleType'
},
{
'1': 'sample',
'3': 2,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Sample',
'10': 'sample'
},
{
'1': 'mapping',
'3': 3,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Mapping',
'10': 'mapping'
},
{
'1': 'location',
'3': 4,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Location',
'10': 'location'
},
{
'1': 'function',
'3': 5,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Function',
'10': 'function'
},
{'1': 'string_table', '3': 6, '4': 3, '5': 9, '10': 'stringTable'},
{'1': 'drop_frames', '3': 7, '4': 1, '5': 3, '10': 'dropFrames'},
{'1': 'keep_frames', '3': 8, '4': 1, '5': 3, '10': 'keepFrames'},
{'1': 'time_nanos', '3': 9, '4': 1, '5': 3, '10': 'timeNanos'},
{'1': 'duration_nanos', '3': 10, '4': 1, '5': 3, '10': 'durationNanos'},
{
'1': 'period_type',
'3': 11,
'4': 1,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.ValueType',
'10': 'periodType'
},
{'1': 'period', '3': 12, '4': 1, '5': 3, '10': 'period'},
{'1': 'comment', '3': 13, '4': 3, '5': 3, '10': 'comment'},
{
'1': 'default_sample_type',
'3': 14,
'4': 1,
'5': 3,
'10': 'defaultSampleType'
},
],
};
/// Descriptor for `Profile`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List profileDescriptor = $convert.base64Decode(
'CgdQcm9maWxlElMKC3NhbXBsZV90eXBlGAEgAygLMjIucGVyZmV0dG8udGhpcmRfcGFydHkucG'
'VyZnRvb2xzLnByb2ZpbGVzLlZhbHVlVHlwZVIKc2FtcGxlVHlwZRJHCgZzYW1wbGUYAiADKAsy'
'Ly5wZXJmZXR0by50aGlyZF9wYXJ0eS5wZXJmdG9vbHMucHJvZmlsZXMuU2FtcGxlUgZzYW1wbG'
'USSgoHbWFwcGluZxgDIAMoCzIwLnBlcmZldHRvLnRoaXJkX3BhcnR5LnBlcmZ0b29scy5wcm9m'
'aWxlcy5NYXBwaW5nUgdtYXBwaW5nEk0KCGxvY2F0aW9uGAQgAygLMjEucGVyZmV0dG8udGhpcm'
'RfcGFydHkucGVyZnRvb2xzLnByb2ZpbGVzLkxvY2F0aW9uUghsb2NhdGlvbhJNCghmdW5jdGlv'
'bhgFIAMoCzIxLnBlcmZldHRvLnRoaXJkX3BhcnR5LnBlcmZ0b29scy5wcm9maWxlcy5GdW5jdG'
'lvblIIZnVuY3Rpb24SIQoMc3RyaW5nX3RhYmxlGAYgAygJUgtzdHJpbmdUYWJsZRIfCgtkcm9w'
'X2ZyYW1lcxgHIAEoA1IKZHJvcEZyYW1lcxIfCgtrZWVwX2ZyYW1lcxgIIAEoA1IKa2VlcEZyYW'
'1lcxIdCgp0aW1lX25hbm9zGAkgASgDUgl0aW1lTmFub3MSJQoOZHVyYXRpb25fbmFub3MYCiAB'
'KANSDWR1cmF0aW9uTmFub3MSUwoLcGVyaW9kX3R5cGUYCyABKAsyMi5wZXJmZXR0by50aGlyZF'
'9wYXJ0eS5wZXJmdG9vbHMucHJvZmlsZXMuVmFsdWVUeXBlUgpwZXJpb2RUeXBlEhYKBnBlcmlv'
'ZBgMIAEoA1IGcGVyaW9kEhgKB2NvbW1lbnQYDSADKANSB2NvbW1lbnQSLgoTZGVmYXVsdF9zYW'
'1wbGVfdHlwZRgOIAEoA1IRZGVmYXVsdFNhbXBsZVR5cGU=');
@$core.Deprecated('Use valueTypeDescriptor instead')
const ValueType$json = {
'1': 'ValueType',
'2': [
{'1': 'type', '3': 1, '4': 1, '5': 3, '10': 'type'},
{'1': 'unit', '3': 2, '4': 1, '5': 3, '10': 'unit'},
],
};
/// Descriptor for `ValueType`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List valueTypeDescriptor = $convert.base64Decode(
'CglWYWx1ZVR5cGUSEgoEdHlwZRgBIAEoA1IEdHlwZRISCgR1bml0GAIgASgDUgR1bml0');
@$core.Deprecated('Use sampleDescriptor instead')
const Sample$json = {
'1': 'Sample',
'2': [
{'1': 'location_id', '3': 1, '4': 3, '5': 4, '10': 'locationId'},
{'1': 'value', '3': 2, '4': 3, '5': 3, '10': 'value'},
{
'1': 'label',
'3': 3,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Label',
'10': 'label'
},
],
};
/// Descriptor for `Sample`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sampleDescriptor = $convert.base64Decode(
'CgZTYW1wbGUSHwoLbG9jYXRpb25faWQYASADKARSCmxvY2F0aW9uSWQSFAoFdmFsdWUYAiADKA'
'NSBXZhbHVlEkQKBWxhYmVsGAMgAygLMi4ucGVyZmV0dG8udGhpcmRfcGFydHkucGVyZnRvb2xz'
'LnByb2ZpbGVzLkxhYmVsUgVsYWJlbA==');
@$core.Deprecated('Use labelDescriptor instead')
const Label$json = {
'1': 'Label',
'2': [
{'1': 'key', '3': 1, '4': 1, '5': 3, '10': 'key'},
{'1': 'str', '3': 2, '4': 1, '5': 3, '10': 'str'},
{'1': 'num', '3': 3, '4': 1, '5': 3, '10': 'num'},
{'1': 'num_unit', '3': 4, '4': 1, '5': 3, '10': 'numUnit'},
],
};
/// Descriptor for `Label`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List labelDescriptor = $convert.base64Decode(
'CgVMYWJlbBIQCgNrZXkYASABKANSA2tleRIQCgNzdHIYAiABKANSA3N0chIQCgNudW0YAyABKA'
'NSA251bRIZCghudW1fdW5pdBgEIAEoA1IHbnVtVW5pdA==');
@$core.Deprecated('Use mappingDescriptor instead')
const Mapping$json = {
'1': 'Mapping',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 4, '10': 'id'},
{'1': 'memory_start', '3': 2, '4': 1, '5': 4, '10': 'memoryStart'},
{'1': 'memory_limit', '3': 3, '4': 1, '5': 4, '10': 'memoryLimit'},
{'1': 'file_offset', '3': 4, '4': 1, '5': 4, '10': 'fileOffset'},
{'1': 'filename', '3': 5, '4': 1, '5': 3, '10': 'filename'},
{'1': 'build_id', '3': 6, '4': 1, '5': 3, '10': 'buildId'},
{'1': 'has_functions', '3': 7, '4': 1, '5': 8, '10': 'hasFunctions'},
{'1': 'has_filenames', '3': 8, '4': 1, '5': 8, '10': 'hasFilenames'},
{'1': 'has_line_numbers', '3': 9, '4': 1, '5': 8, '10': 'hasLineNumbers'},
{
'1': 'has_inline_frames',
'3': 10,
'4': 1,
'5': 8,
'10': 'hasInlineFrames'
},
],
};
/// Descriptor for `Mapping`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List mappingDescriptor = $convert.base64Decode(
'CgdNYXBwaW5nEg4KAmlkGAEgASgEUgJpZBIhCgxtZW1vcnlfc3RhcnQYAiABKARSC21lbW9yeV'
'N0YXJ0EiEKDG1lbW9yeV9saW1pdBgDIAEoBFILbWVtb3J5TGltaXQSHwoLZmlsZV9vZmZzZXQY'
'BCABKARSCmZpbGVPZmZzZXQSGgoIZmlsZW5hbWUYBSABKANSCGZpbGVuYW1lEhkKCGJ1aWxkX2'
'lkGAYgASgDUgdidWlsZElkEiMKDWhhc19mdW5jdGlvbnMYByABKAhSDGhhc0Z1bmN0aW9ucxIj'
'Cg1oYXNfZmlsZW5hbWVzGAggASgIUgxoYXNGaWxlbmFtZXMSKAoQaGFzX2xpbmVfbnVtYmVycx'
'gJIAEoCFIOaGFzTGluZU51bWJlcnMSKgoRaGFzX2lubGluZV9mcmFtZXMYCiABKAhSD2hhc0lu'
'bGluZUZyYW1lcw==');
@$core.Deprecated('Use locationDescriptor instead')
const Location$json = {
'1': 'Location',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 4, '10': 'id'},
{'1': 'mapping_id', '3': 2, '4': 1, '5': 4, '10': 'mappingId'},
{'1': 'address', '3': 3, '4': 1, '5': 4, '10': 'address'},
{
'1': 'line',
'3': 4,
'4': 3,
'5': 11,
'6': '.perfetto.third_party.perftools.profiles.Line',
'10': 'line'
},
{'1': 'is_folded', '3': 5, '4': 1, '5': 8, '10': 'isFolded'},
],
};
/// Descriptor for `Location`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List locationDescriptor = $convert.base64Decode(
'CghMb2NhdGlvbhIOCgJpZBgBIAEoBFICaWQSHQoKbWFwcGluZ19pZBgCIAEoBFIJbWFwcGluZ0'
'lkEhgKB2FkZHJlc3MYAyABKARSB2FkZHJlc3MSQQoEbGluZRgEIAMoCzItLnBlcmZldHRvLnRo'
'aXJkX3BhcnR5LnBlcmZ0b29scy5wcm9maWxlcy5MaW5lUgRsaW5lEhsKCWlzX2ZvbGRlZBgFIA'
'EoCFIIaXNGb2xkZWQ=');
@$core.Deprecated('Use lineDescriptor instead')
const Line$json = {
'1': 'Line',
'2': [
{'1': 'function_id', '3': 1, '4': 1, '5': 4, '10': 'functionId'},
{'1': 'line', '3': 2, '4': 1, '5': 3, '10': 'line'},
],
};
/// Descriptor for `Line`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List lineDescriptor = $convert.base64Decode(
'CgRMaW5lEh8KC2Z1bmN0aW9uX2lkGAEgASgEUgpmdW5jdGlvbklkEhIKBGxpbmUYAiABKANSBG'
'xpbmU=');
@$core.Deprecated('Use function_Descriptor instead')
const Function_$json = {
'1': 'Function',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 4, '10': 'id'},
{'1': 'name', '3': 2, '4': 1, '5': 3, '10': 'name'},
{'1': 'system_name', '3': 3, '4': 1, '5': 3, '10': 'systemName'},
{'1': 'filename', '3': 4, '4': 1, '5': 3, '10': 'filename'},
{'1': 'start_line', '3': 5, '4': 1, '5': 3, '10': 'startLine'},
],
};
/// Descriptor for `Function`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List function_Descriptor = $convert.base64Decode(
'CghGdW5jdGlvbhIOCgJpZBgBIAEoBFICaWQSEgoEbmFtZRgCIAEoA1IEbmFtZRIfCgtzeXN0ZW'
'1fbmFtZRgDIAEoA1IKc3lzdGVtTmFtZRIaCghmaWxlbmFtZRgEIAEoA1IIZmlsZW5hbWUSHQoK'
'c3RhcnRfbGluZRgFIAEoA1IJc3RhcnRMaW5l');
@@ -0,0 +1,13 @@
//
// Generated code. Do not modify.
// source: profile.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'profile.pb.dart';
@@ -0,0 +1,230 @@
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Profile is a common stacktrace profile format.
//
// Measurements represented with this format should follow the
// following conventions:
//
// - Consumers should treat unset optional fields as if they had been
// set with their default value.
//
// - When possible, measurements should be stored in "unsampled" form
// that is most useful to humans. There should be enough
// information present to determine the original sampled values.
//
// - On-disk, the serialized proto must be gzip-compressed.
//
// - The profile is represented as a set of samples, where each sample
// references a sequence of locations, and where each location belongs
// to a mapping.
// - There is a N->1 relationship from sample.location_id entries to
// locations. For every sample.location_id entry there must be a
// unique Location with that id.
// - There is an optional N->1 relationship from locations to
// mappings. For every nonzero Location.mapping_id there must be a
// unique Mapping with that id.
syntax = "proto3";
// This is in perfetto.third_party to avoid clashing with potential other
// copies of this proto.
package perfetto.third_party.perftools.profiles;
option java_package = "com.google.perftools.profiles";
option java_outer_classname = "ProfileProto";
message Profile {
// A description of the samples associated with each Sample.value.
// For a cpu profile this might be:
// [["cpu","nanoseconds"]] or [["wall","seconds"]] or [["syscall","count"]]
// For a heap profile, this might be:
// [["allocations","count"], ["space","bytes"]],
// If one of the values represents the number of events represented
// by the sample, by convention it should be at index 0 and use
// sample_type.unit == "count".
repeated ValueType sample_type = 1;
// The set of samples recorded in this profile.
repeated Sample sample = 2;
// Mapping from address ranges to the image/binary/library mapped
// into that address range. mapping[0] will be the main binary.
repeated Mapping mapping = 3;
// Useful program location
repeated Location location = 4;
// Functions referenced by locations
repeated Function function = 5;
// A common table for strings referenced by various messages.
// string_table[0] must always be "".
repeated string string_table = 6;
// frames with Function.function_name fully matching the following
// regexp will be dropped from the samples, along with their successors.
// Index into string table.
int64 drop_frames = 7;
// frames with Function.function_name fully matching the following
// regexp will be kept, even if it matches drop_functions.
// Index into string table.
int64 keep_frames = 8;
// The following fields are informational, do not affect
// interpretation of results.
// Time of collection (UTC) represented as nanoseconds past the epoch.
int64 time_nanos = 9;
// Duration of the profile, if a duration makes sense.
int64 duration_nanos = 10;
// The kind of events between sampled ocurrences.
// e.g [ "cpu","cycles" ] or [ "heap","bytes" ]
ValueType period_type = 11;
// The number of events between sampled occurrences.
int64 period = 12;
// Freeform text associated to the profile.
// Indices into string table.
repeated int64 comment = 13;
// Index into the string table of the type of the preferred sample
// value. If unset, clients should default to the last sample value.
int64 default_sample_type = 14;
}
// ValueType describes the semantics and measurement units of a value.
message ValueType {
// Index into string table.
int64 type = 1;
// Index into string table.
int64 unit = 2;
}
// Each Sample records values encountered in some program
// context. The program context is typically a stack trace, perhaps
// augmented with auxiliary information like the thread-id, some
// indicator of a higher level request being handled etc.
message Sample {
// The ids recorded here correspond to a Profile.location.id.
// The leaf is at location_id[0].
repeated uint64 location_id = 1;
// The type and unit of each value is defined by the corresponding
// entry in Profile.sample_type. All samples must have the same
// number of values, the same as the length of Profile.sample_type.
// When aggregating multiple samples into a single sample, the
// result has a list of values that is the elemntwise sum of the
// lists of the originals.
repeated int64 value = 2;
// label includes additional context for this sample. It can include
// things like a thread id, allocation size, etc
repeated Label label = 3;
}
message Label {
// Index into string table
int64 key = 1;
// At most one of the following must be present
// Index into string table
int64 str = 2;
int64 num = 3;
// Should only be present when num is present.
// Specifies the units of num.
// Use arbitrary string (for example, "requests") as a custom count unit.
// If no unit is specified, consumer may apply heuristic to deduce the unit.
// Consumers may also interpret units like "bytes" and "kilobytes" as memory
// units and units like "seconds" and "nanoseconds" as time units,
// and apply appropriate unit conversions to these.
// Index into string table
int64 num_unit = 4;
}
message Mapping {
// Unique nonzero id for the mapping.
uint64 id = 1;
// Address at which the binary (or DLL) is loaded into memory.
uint64 memory_start = 2;
// The limit of the address range occupied by this mapping.
uint64 memory_limit = 3;
// Offset in the binary that corresponds to the first mapped address.
uint64 file_offset = 4;
// The object this entry is loaded from. This can be a filename on
// disk for the main binary and shared libraries, or virtual
// abstractions like "[vdso]".
// Index into string table
int64 filename = 5;
// A string that uniquely identifies a particular program version
// with high probability. E.g., for binaries generated by GNU tools,
// it could be the contents of the .note.gnu.build-id field.
// Index into string table
int64 build_id = 6;
// The following fields indicate the resolution of symbolic info.
bool has_functions = 7;
bool has_filenames = 8;
bool has_line_numbers = 9;
bool has_inline_frames = 10;
}
// Describes function and line table debug information.
message Location {
// Unique nonzero id for the location. A profile could use
// instruction addresses or any integer sequence as ids.
uint64 id = 1;
// The id of the corresponding profile.Mapping for this location.
// It can be unset if the mapping is unknown or not applicable for
// this profile type.
uint64 mapping_id = 2;
// The instruction address for this location, if available. It
// should be within [Mapping.memory_start...Mapping.memory_limit]
// for the corresponding mapping. A non-leaf address may be in the
// middle of a call instruction. It is up to display tools to find
// the beginning of the instruction if necessary.
uint64 address = 3;
// Multiple line indicates this location has inlined functions,
// where the last entry represents the caller into which the
// preceding entries were inlined.
//
// E.g., if memcpy() is inlined into printf:
// line[0].function_name == "memcpy"
// line[1].function_name == "printf"
repeated Line line = 4;
// Provides an indication that multiple symbols map to this location's
// address, for example due to identical code folding by the linker. In that
// case the line information above represents one of the multiple
// symbols. This field must be recomputed when the symbolization state of the
// profile changes.
bool is_folded = 5;
}
message Line {
// The id of the corresponding profile.Function for this line.
uint64 function_id = 1;
// Line number in source code.
int64 line = 2;
}
message Function {
// Unique nonzero id for the function.
uint64 id = 1;
// Name of the function, in human-readable form if available.
// Index into string table
int64 name = 2;
// Name of the function, as identified by the system.
// For instance, it can be a C++ mangled name.
// Index into string table
int64 system_name = 3;
// Source file containing the function.
// Index into string table
int64 filename = 4;
// Line number in source file.
int64 start_line = 5;
}
@@ -0,0 +1,72 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:typed_data';
import 'package:profiling/src/elf_utils.dart';
/// Symbols of a TEXT section of a binary indexed by their file offset.
class Symbols {
final Uint32List fileOffsets;
final List<String> names;
Symbols._(this.fileOffsets, this.names);
/// Given the [fileOffset] find a symbol it falls into.
///
/// We assume that symbol with index `i` starts at `fileOffsets[i]`
/// and ends at `fileOffset[i+1]`.
int? symbolIndex(int fileOffset) {
int lo = 0;
int hi = fileOffsets.length - 1;
while (lo <= hi) {
int mid = ((hi - lo + 1) >> 1) + lo;
if (fileOffset < fileOffsets[mid]) {
hi = mid - 1;
} else if ((mid != hi) && (fileOffset >= fileOffsets[mid + 1])) {
lo = mid + 1;
} else {
return mid;
}
}
return null;
}
String? lookupName(int fileOffset) {
final index = symbolIndex(fileOffset);
return index != null ? names[index] : null;
}
/// Try loading symbols from the binary at [path].
static Symbols? load(String path) {
try {
final loadingBias = loadingBiasOf(path);
final symbols = textSymbolsOf(path).toList(growable: false);
if (symbols.isEmpty) {
return null;
}
// Ensure symbols are sorted to be able to use binary search.
symbols.sort((a, b) => a.addr.compareTo(b.addr));
// `nm` prints virtual addresses - convert these to file offsets
// using loading bias.
final fileOffsets = Uint32List(symbols.length);
final names = List.generate(symbols.length, (i) => symbols[i].name);
for (var i = 0; i < symbols.length; i++) {
if (symbols[i].addr < loadingBias) {
throw StateError(
'unexpected: virtual address ${symbols[i].addr} of symbol '
'${symbols[i].name} is less than loading bias $loadingBias');
}
fileOffsets[i] = symbols[i].addr - loadingBias;
}
return Symbols._(fileOffsets, names);
} catch (_) {
print('failed to load symbols from $path');
return null;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
name: profiling
description: Utilities for low-level profiling of Dart code
version: 0.1.0
publish_to: none
environment:
sdk: '>=3.6.0 <4.0.0'
dependencies:
protobuf: ^3.1.0
fixnum: ^1.1.0
path: ^1.9.0
dev_dependencies:
lints: ^4.0.0
test: ^1.24.0
@@ -3314,6 +3314,12 @@ void StubCodeCompiler::GenerateSubtype7TestCacheStub() {
GenerateSubtypeNTestCacheStub(assembler, 7);
}
#ifndef DART_TARGET_SUPPORTS_PROBE_POINTS
void StubCodeCompiler::GenerateAllocationProbePointStub() {
__ Stop("allocation probes are not supported on this platform");
}
#endif
} // namespace compiler
} // namespace dart
@@ -28,6 +28,10 @@
#define __ assembler->
namespace dart {
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
DECLARE_FLAG(bool, generate_probe_points);
#endif
namespace compiler {
// Ensures that [R0] is a new object, if not it will be added to the remembered
@@ -1303,6 +1307,46 @@ void StubCodeCompiler::GenerateNoSuchMethodDispatcherStub() {
GenerateNoSuchMethodDispatcherBody(assembler);
}
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
void StubCodeCompiler::GenerateAllocationProbePointStub() {
if (!FLAG_generate_probe_points) {
__ Stop("unexpected invocation of an allocation probe");
return;
}
// Create a frame on the stack so that we could properly unwind.
// Our .eh_frame is very simple and specifies
// CFA := FP+16; FP := *(FP+0); LR := *(FP+1);
// for the whole .text section.
__ EnterStubFrame();
// Restore native stack pointer (CSP). Dart SP is currently not
// the same because we do not follow ABI which requires native SP
// to be 16 bytes aligned. Restoring CSP is important for simpleperf
// to be able to unwind the stack - as it copies stack range starting
// at CSP before unwinding. If CSP is not restored we copy wrong part
// of the stack (CSP is bumped almost to the end of the thread stack).
__ andi(CSP, SP, Immediate(~15));
// Probe will be placed here by `runtime/tools/profiling/bin/set_uprobe.dart`.
const intptr_t probe_offset = __ CodeSize();
__ SetupCSPFromThread(THR);
__ LeaveStubFrame();
__ Ret();
// This dummy instruction is encoding offset to the probe point. It will be
// used by set_uprobe.dart script.
__ TestImmediate(R0, probe_offset);
}
#endif
static void InvokeAllocationProbePoint(Assembler* assembler) {
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
if (FLAG_precompiled_mode && FLAG_generate_probe_points) {
__ EnterStubFrame();
__ Call(StubCode::AllocationProbePoint());
__ LeaveStubFrame();
}
#endif
}
// Called for inline allocation of arrays.
// Input registers (preserved):
// LR: return address.
@@ -1442,6 +1486,7 @@ void StubCodeCompiler::GenerateAllocateArrayStub() {
// Done allocating and initializing the array.
// AllocateArrayABI::kResultReg: new object.
// AllocateArrayABI::kLengthReg: array length as Smi (preserved).
InvokeAllocationProbePoint(assembler);
__ ret();
// Unable to allocate the array using the fast inline code, just call
@@ -1478,6 +1523,7 @@ void StubCodeCompiler::GenerateAllocateMintSharedWithFPURegsStub() {
Label slow_case;
__ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
InvokeAllocationProbePoint(assembler);
__ Ret();
__ Bind(&slow_case);
@@ -1496,6 +1542,7 @@ void StubCodeCompiler::GenerateAllocateMintSharedWithoutFPURegsStub() {
Label slow_case;
__ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
InvokeAllocationProbePoint(assembler);
__ Ret();
__ Bind(&slow_case);
@@ -1923,6 +1970,7 @@ void StubCodeCompiler::GenerateAllocateContextStub() {
// Done allocating and initializing the context.
// R0: new object.
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -1999,6 +2047,7 @@ void StubCodeCompiler::GenerateCloneContextStub() {
// Done allocating and initializing the context.
// R0: new object.
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -2345,6 +2394,7 @@ static void GenerateAllocateObjectHelper(Assembler* assembler,
__ Bind(&not_parameterized_case);
} // kClsIdReg = R4, kTypeOffsetReg = R5
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -3940,6 +3990,7 @@ void StubCodeCompiler::GenerateAllocateTypedDataArrayStub(intptr_t cid) {
__ b(&loop, UNSIGNED_LESS);
__ WriteAllocationCanary(R1); // Fix overshoot.
InvokeAllocationProbePoint(assembler);
__ Ret();
__ Bind(&call_runtime);
+41 -2
View File
@@ -31,6 +31,11 @@
#define __ assembler->
namespace dart {
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
DECLARE_FLAG(bool, generate_probe_points);
#endif
namespace compiler {
// Ensures that [RAX] is a new object, if not it will be added to the remembered
@@ -1240,6 +1245,34 @@ void StubCodeCompiler::GenerateNoSuchMethodDispatcherStub() {
GenerateNoSuchMethodDispatcherBody(assembler, /*receiver_reg=*/RDX);
}
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
void StubCodeCompiler::GenerateAllocationProbePointStub() {
if (!FLAG_generate_probe_points) {
__ Stop("unexpected invocation of an allocation probe");
return;
}
__ EnterStubFrame();
// Probe will be placed here by `runtime/tools/profiling/bin/set_uprobe.dart`.
const intptr_t probe_offset = __ CodeSize();
__ LeaveStubFrame();
__ Ret();
// This dummy instruction is encoding offset to the probe point. It will be
// used by set_uprobe.dart script.
__ TestImmediate(RAX, Immediate(probe_offset));
}
#endif
static void InvokeAllocationProbePoint(Assembler* assembler) {
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
if (FLAG_precompiled_mode && FLAG_generate_probe_points) {
__ EnterStubFrame();
__ Call(StubCode::AllocationProbePoint());
__ LeaveStubFrame();
}
#endif
}
// Called for inline allocation of arrays.
// Input registers (preserved):
// AllocateArrayABI::kLengthReg: array length as Smi.
@@ -1360,6 +1393,8 @@ void StubCodeCompiler::GenerateAllocateArrayStub() {
__ cmpq(RDI, RCX);
__ j(UNSIGNED_LESS, &loop);
__ WriteAllocationCanary(RCX);
InvokeAllocationProbePoint(assembler);
__ ret();
// Unable to allocate the array using the fast inline code, just call
@@ -1393,6 +1428,7 @@ void StubCodeCompiler::GenerateAllocateMintSharedWithFPURegsStub() {
Label slow_case;
__ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
InvokeAllocationProbePoint(assembler);
__ Ret();
__ Bind(&slow_case);
@@ -1411,6 +1447,7 @@ void StubCodeCompiler::GenerateAllocateMintSharedWithoutFPURegsStub() {
Label slow_case;
__ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
InvokeAllocationProbePoint(assembler);
__ Ret();
__ Bind(&slow_case);
@@ -1859,6 +1896,7 @@ void StubCodeCompiler::GenerateAllocateContextStub() {
// Done allocating and initializing the context.
// RAX: new object.
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -1879,7 +1917,6 @@ void StubCodeCompiler::GenerateAllocateContextStub() {
// RAX: new object
// Restore the frame pointer.
__ LeaveStubFrame();
__ ret();
}
@@ -1930,6 +1967,7 @@ void StubCodeCompiler::GenerateCloneContextStub() {
// Done allocating and initializing the context.
// RAX: new object.
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -1952,7 +1990,6 @@ void StubCodeCompiler::GenerateCloneContextStub() {
// RAX: new object
// Restore the frame pointer.
__ LeaveStubFrame();
__ ret();
}
@@ -2266,6 +2303,7 @@ static void GenerateAllocateObjectHelper(Assembler* assembler,
__ Bind(&not_parameterized_case);
} // kTypeOffsetReg = RDI;
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&slow_case);
@@ -3869,6 +3907,7 @@ void StubCodeCompiler::GenerateAllocateTypedDataArrayStub(intptr_t cid) {
__ j(UNSIGNED_LESS, &loop, Assembler::kNearJump);
__ WriteAllocationCanary(RCX); // Fix overshoot.
InvokeAllocationProbePoint(assembler);
__ ret();
__ Bind(&call_runtime);
+7
View File
@@ -24,6 +24,13 @@ namespace dart {
DECLARE_FLAG(bool, precompiled_mode);
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
DEFINE_FLAG(bool,
generate_probe_points,
false,
"Generate probe points for installation of user space probes");
#endif
StubCode::StubCodeEntry StubCode::entries_[kNumStubEntries] = {
#if defined(DART_PRECOMPILED_RUNTIME)
#define STUB_CODE_DECLARE(name) {nullptr, #name},
+9
View File
@@ -18,6 +18,14 @@ namespace dart {
V(LazySpecializeTypeTest) \
V(LazySpecializeNullableTypeTest)
#if (defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID)) && \
(defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64))
// Currently we support probe points only Linux and Android (X64 and ARM64).
#define DART_TARGET_SUPPORTS_PROBE_POINTS 1
#endif
#define PROBE_POINT_STUBS_LIST(V) V(AllocationProbePoint)
// List of stubs created in the VM isolate, these stubs are shared by different
// isolates running in this dart process.
#define VM_STUB_CODE_LIST(V) \
@@ -28,6 +36,7 @@ namespace dart {
V(WriteBarrier) \
V(WriteBarrierWrappers) \
V(ArrayWriteBarrier) \
PROBE_POINT_STUBS_LIST(V) \
V(AllocateArray) \
V(AllocateMint) \
V(AllocateDouble) \