blob: e46f782b596dc0bd3f136c4a1839df01b261cfff [file] [log] [blame] [edit]
// 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:convert';
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 {
args = args.toList();
bool dryRun = args.remove('--dry-run');
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, silent: dryRun);
final loadingBias = loadingBiasOf(sharedObject);
final uprobeFileOffset = (uprobeAddress + loadingBias).toRadixString(16);
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);
if (!dryRun) {
File('/sys/kernel/tracing/uprobe_events').writeAsStringSync(probe);
}
}
Future<int> _computeProbesVirtualAddress(
String sharedObject, String targetSymbol,
{required bool silent}) 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;
if (!silent) {
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'), []);
var jsonOffsets = json.decode(offsets)['offsets'] as List;
for (var offset in jsonOffsets) {
if (offset
case {
'class': 'Thread',
'name': 'top_offset',
'value': final String value
} when int.tryParse(value) != null) {
return value;
}
}
throw 'Did not find expect json entry in offsets_extractor output.';
}
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;
}