[vm] Do not obfuscate code or object symbols in the static symbol table.

Also changes pkg/native_stack_traces to return a parsed MachO file
if the MachO file doesn't contain DWARF information, so we can examine
its static symbol information for the new test.

Issue: https://github.com/flutter/flutter/issues/124715

TEST=vm/dart/unobfuscated_static_symbols

Change-Id: I07d3ced56eeba852ebe4178dfd2b66ebb899eb76
Cq-Include-Trybots: luci.dart.try:vm-aot-dwarf-linux-product-x64-try,vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-mac-product-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/305720
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Tess Strickland 2023-05-31 08:06:08 +00:00 committed by Commit Queue
parent 9fecc29dbb
commit ecf1968122
11 changed files with 593 additions and 215 deletions

View file

@ -1,3 +1,8 @@
## 0.5.6
- Added retrieval of the static symbol table contents for use in Dart tests.
- Don't require Mach-O files to contain DWARF to be read by the Mach-O reader.
## 0.5.5
- Fixed issue introduced by 0.5.4.

View file

@ -29,6 +29,7 @@ abstract class DwarfContainer {
String? get buildId;
Iterable<DwarfContainerSymbol> get staticSymbols;
DwarfContainerStringTable? get debugStringTable;
DwarfContainerStringTable? get debugLineStringTable;

View file

@ -1153,6 +1153,7 @@ class Elf extends DwarfContainer {
/// Returns an iterable of the symbols in the static symbol table(s).
/// The ordering of the symbols is not guaranteed.
@override
Iterable<Symbol> get staticSymbols sync* {
for (final section in namedSections('.symtab')) {
final symtab = section as SymbolTable;

View file

@ -426,7 +426,7 @@ class MachO extends DwarfContainer {
final MachOHeader _header;
final List<LoadCommand> _commands;
final SymbolTable _symbolTable;
final SegmentCommand _dwarfSegment;
final SegmentCommand? _dwarfSegment;
final StringTable? _debugStringTable;
final StringTable? _debugLineStringTable;
@ -456,23 +456,21 @@ class MachO extends DwarfContainer {
final dwarfSegment = commands
.whereType<SegmentCommand?>()
.firstWhere((sc) => sc!.segname == '__DWARF', orElse: () => null);
if (dwarfSegment == null) {
return null;
}
final debugStringTableSection = dwarfSegment.sections['__debug_str'];
StringTable? debugStringTable;
if (debugStringTableSection != null) {
debugStringTable =
StringTable.fromReader(debugStringTableSection.shrink(reader));
}
final debugLineStringTableSection =
dwarfSegment.sections['__debug_line_str'];
StringTable? debugLineStringTable;
if (debugLineStringTableSection != null) {
debugLineStringTable =
StringTable.fromReader(debugLineStringTableSection.shrink(reader));
if (dwarfSegment != null) {
final debugStringTableSection = dwarfSegment.sections['__debug_str'];
if (debugStringTableSection != null) {
debugStringTable =
StringTable.fromReader(debugStringTableSection.shrink(reader));
}
final debugLineStringTableSection =
dwarfSegment.sections['__debug_line_str'];
if (debugLineStringTableSection != null) {
debugLineStringTable =
StringTable.fromReader(debugLineStringTableSection.shrink(reader));
}
}
// Set the wordSize and endian of the original reader before returning.
@ -498,6 +496,7 @@ class MachO extends DwarfContainer {
MachO.fromReader(Reader.fromFile(MachO.handleDSYM(fileName)));
bool get isDSYM => _header.isDSYM;
bool get hasDwarf => _dwarfSegment != null;
Reader applyWordSizeAndEndian(Reader reader) =>
Reader.fromTypedData(reader.bdata,
@ -508,13 +507,13 @@ class MachO extends DwarfContainer {
@override
Reader abbreviationsTableReader(Reader containerReader) =>
_dwarfSegment.sections['__debug_abbrev']!.shrink(containerReader);
_dwarfSegment!.sections['__debug_abbrev']!.shrink(containerReader);
@override
Reader lineNumberInfoReader(Reader containerReader) =>
_dwarfSegment.sections['__debug_line']!.shrink(containerReader);
_dwarfSegment!.sections['__debug_line']!.shrink(containerReader);
@override
Reader debugInfoReader(Reader containerReader) =>
_dwarfSegment.sections['__debug_info']!.shrink(containerReader);
_dwarfSegment!.sections['__debug_info']!.shrink(containerReader);
@override
int? get vmStartAddress => _symbolTable[constants.vmSymbolName]?.value;
@ -545,6 +544,9 @@ class MachO extends DwarfContainer {
return bestSym;
}
@override
Iterable<Symbol> get staticSymbols => _symbolTable.values;
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
@ -748,11 +750,15 @@ class UniversalBinary {
(macho.isolateStartAddress == null))) {
continue;
}
if (macho.isDSYM) {
if (!arches.containsKey(cpuType)) {
arches[cpuType] = arch;
} else if (macho.isDSYM) {
// Always take a dSYM section above a non-dSYM section. If there are
// multiple dSYM sections for some reason, the last one read is fine.
arches[cpuType] = arch;
} else if (!arches.containsKey(cpuType)) {
} else if (!contents[arches[cpuType]!]!.hasDwarf) {
// If the old section didn't have DWARF information but the new one
// does, take it instead.
arches[cpuType] = arch;
}
contents[arch] = macho;
@ -794,7 +800,7 @@ class UniversalBinary {
..writeln('')
..writeln('')
..writeln('----------------------------------------------------------')
..writeln(' DWARF-containing Mach-O Contents for $cpuType')
..writeln(' Selected Mach-O Contents for $cpuType')
..writeln('----------------------------------------------------------')
..writeln('');
_contents[_arches[cpuType]!]!.writeToStringBuffer(buffer);

View file

@ -1,5 +1,5 @@
name: native_stack_traces
version: 0.5.5
version: 0.5.6
description: Utilities for working with non-symbolic stack traces.
repository: https://github.com/dart-lang/sdk/tree/main/pkg/native_stack_traces

View file

@ -0,0 +1,281 @@
// Copyright (c) 2023, 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 test ensures that when running in obfuscated mode, the AOT compiler
// generates a snapshot with obfuscated runtime information, but that static
// symbol tables in the unstripped snapshot and/or separate debugging
// information remains unobfuscated.
// OtherResources=use_save_debugging_info_flag_program.dart
import "dart:io";
import "dart:math";
import "dart:typed_data";
import 'package:expect/expect.dart';
import 'package:native_stack_traces/elf.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:native_stack_traces/src/dwarf_container.dart';
import 'package:native_stack_traces/src/macho.dart';
import 'package:path/path.dart' as path;
import 'use_flag_test_helper.dart';
Future<void> main(List<String> args) async {
if (!isAOTRuntime) {
return; // Running in JIT: AOT binaries not available.
}
if (Platform.isAndroid) {
return; // SDK tree and dart_bootstrap not available on the test device.
}
// These are the tools we need to be available to run on a given platform:
if (!await testExecutable(genSnapshot)) {
throw "Cannot run test as $genSnapshot not available";
}
if (!await testExecutable(dartPrecompiledRuntime)) {
throw "Cannot run test as $dartPrecompiledRuntime not available";
}
if (!File(platformDill).existsSync()) {
throw "Cannot run test as $platformDill does not exist";
}
await withTempDir('unobfuscated-static-symbols-test', (String tempDir) async {
final cwDir = path.dirname(Platform.script.toFilePath());
final script =
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
final scriptDill = path.join(tempDir, 'flag_program.dill');
// Compile script to Kernel IR.
await run(genKernel, <String>[
'--aot',
'--platform=$platformDill',
'-o',
scriptDill,
script,
]);
await checkElf(tempDir, scriptDill);
await checkAssembly(tempDir, scriptDill);
});
}
const commonGenSnapshotArgs = <String>[
// Make sure that the runs are deterministic so we can depend on the same
// snapshot being generated each time.
'--deterministic',
];
Future<void> checkElf(String tempDir, String scriptDill) async {
// Run the AOT compiler without Dwarf stack trace, once without obfuscation,
// once with obfuscation, and once with obfuscation and saving debugging
// information.
final scriptUnobfuscatedSnapshot = path.join(tempDir, 'unobfuscated-elf.so');
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--snapshot-kind=app-aot-elf',
'--elf=$scriptUnobfuscatedSnapshot',
scriptDill,
]);
final unobfuscatedCase = TestCase(
scriptUnobfuscatedSnapshot, Elf.fromFile(scriptUnobfuscatedSnapshot)!);
final scriptObfuscatedOnlySnapshot =
path.join(tempDir, 'obfuscated-only-elf.so');
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--obfuscate',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptObfuscatedOnlySnapshot',
scriptDill,
]);
final obfuscatedOnlyCase = TestCase(scriptObfuscatedOnlySnapshot,
Elf.fromFile(scriptObfuscatedOnlySnapshot)!);
final scriptObfuscatedSnapshot = path.join(tempDir, 'obfuscated-elf.so');
final scriptDebuggingInfo = path.join(tempDir, 'obfuscated-debug-elf.so');
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--obfuscate',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptObfuscatedSnapshot',
'--save-debugging-info=$scriptDebuggingInfo',
scriptDill,
]);
final obfuscatedCase = TestCase(
scriptObfuscatedSnapshot,
Elf.fromFile(scriptObfuscatedSnapshot)!,
Elf.fromFile(scriptDebuggingInfo)!);
await checkCases(unobfuscatedCase, <TestCase>[
obfuscatedOnlyCase,
obfuscatedCase,
]);
}
Future<void> checkAssembly(String tempDir, String scriptDill) async {
// Currently there are no appropriate buildtools on the simulator trybots as
// normally they compile to ELF and don't need them for compiling assembly
// snapshots.
if (isSimulator || (!Platform.isLinux && !Platform.isMacOS)) return;
// Run the AOT compiler without Dwarf stack trace, once without obfuscation,
// once with obfuscation, and once with obfuscation and saving debugging
// information.
final scriptUnobfuscatedAssembly =
path.join(tempDir, 'unobfuscated-assembly.S');
final scriptUnobfuscatedSnapshot =
path.join(tempDir, 'unobfuscated-assembly.so');
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--snapshot-kind=app-aot-assembly',
'--assembly=$scriptUnobfuscatedAssembly',
scriptDill,
]);
await assembleSnapshot(
scriptUnobfuscatedAssembly, scriptUnobfuscatedSnapshot);
final unobfuscatedCase = TestCase(
scriptUnobfuscatedSnapshot,
Platform.isMacOS
? MachO.fromFile(scriptUnobfuscatedSnapshot)!
: Elf.fromFile(scriptUnobfuscatedSnapshot)!);
final scriptObfuscatedOnlyAssembly =
path.join(tempDir, 'obfuscated-only-assembly.S');
final scriptObfuscatedOnlySnapshot =
path.join(tempDir, 'obfuscated-only-assembly.so');
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--obfuscate',
'--snapshot-kind=app-aot-assembly',
'--assembly=$scriptObfuscatedOnlyAssembly',
scriptDill,
]);
await assembleSnapshot(
scriptObfuscatedOnlyAssembly, scriptObfuscatedOnlySnapshot);
final obfuscatedOnlyCase = TestCase(
scriptObfuscatedOnlySnapshot,
Platform.isMacOS
? MachO.fromFile(scriptObfuscatedOnlySnapshot)!
: Elf.fromFile(scriptObfuscatedOnlySnapshot)!);
await checkCases(unobfuscatedCase, <TestCase>[
obfuscatedOnlyCase,
]);
}
class TestCase {
final String snapshotPath;
final DwarfContainer container;
final DwarfContainer? debuggingInfoContainer;
TestCase(this.snapshotPath, this.container, [this.debuggingInfoContainer]);
}
Future<void> checkCases(
TestCase unobfuscated, List<TestCase> obfuscateds) async {
checkStaticSymbolTables(unobfuscated, obfuscateds);
await checkTraces(unobfuscated, obfuscateds);
}
Future<void> checkTraces(
TestCase unobfuscated, List<TestCase> obfuscateds) async {
// Run the resulting scripts, saving the stack traces.
final expectedTrace = await runError(dartPrecompiledRuntime, <String>[
unobfuscated.snapshotPath,
]);
print('');
print("Original stack trace:");
expectedTrace.forEach(print);
final obfuscatedTraces = <List<String>>[];
for (int i = 0; i < obfuscateds.length; i++) {
obfuscatedTraces.add(await runError(dartPrecompiledRuntime, <String>[
obfuscateds[i].snapshotPath,
]));
print('');
print("Obfuscated stack trace ${i + 1}:");
obfuscatedTraces[i].forEach(print);
if (i != 0) {
// Compare with the previous trace, as all obfuscated traces should be
// the same as the obfuscation is deterministic.
Expect.deepEquals(obfuscatedTraces[i - 1], obfuscatedTraces[i]);
}
}
// The unobfuscated trace should differ from all obfuscated traces.
Expect.isNotEmpty(obfuscateds);
final gotTrace = obfuscatedTraces[0];
Expect.equals(expectedTrace.length, gotTrace.length);
bool differs = false;
for (int i = 0; i < expectedTrace.length; i++) {
if (expectedTrace[i] != gotTrace[i]) {
differs = true;
}
}
Expect.isTrue(
differs, 'The obfuscated traces are identical to the unobfuscated trace');
}
void checkStaticSymbolTables(TestCase expected, List<TestCase> cases) {
final expectedSymbolNames =
expected.container.staticSymbols.map((o) => o.name);
if (expected.debuggingInfoContainer != null) {
expectSimilarStaticSymbols(expectedSymbolNames,
expected.debuggingInfoContainer!.staticSymbols.map((o) => o.name));
}
for (final got in cases) {
expectSimilarStaticSymbols(
expectedSymbolNames, got.container.staticSymbols.map((o) => o.name));
if (got.debuggingInfoContainer != null) {
expectSimilarStaticSymbols(expectedSymbolNames,
got.debuggingInfoContainer!.staticSymbols.map((o) => o.name));
}
}
}
const kMaxPercentAllowedDifferences = 0.01;
void expectSimilarStaticSymbols(
Iterable<String> expected, Iterable<String> got) {
final allowedDifferences =
(expected.length * kMaxPercentAllowedDifferences).floor();
// There are cases where we cannot assume that we have the exact same symbols
// in both snapshots (e.g., because we're using an assembler that adds
// symbols with randomly generated names). Instead, we compare them manually,
// counting the number of symbols not found in one or the other, and allow
// a small number of differences. (We generate _a lot_ of static symbols, so
// if the vast majority match we can assume that no obfuscation happened.)
final onlyExpected = <String>[];
for (final name in expected) {
if (!got.contains(name)) {
onlyExpected.add(name);
}
}
print('');
print('Symbols found only in expected:');
onlyExpected.forEach(print);
final onlyGot = <String>[];
for (final name in got) {
if (!expected.contains(name)) {
onlyGot.add(name);
}
}
print('');
print('Symbols found only in got:');
onlyGot.forEach(print);
final differences = onlyExpected.length + onlyGot.length;
Expect.isTrue(
differences <= allowedDifferences,
'Got $differences different symbols, which is '
'more than $allowedDifferences.');
}

View file

@ -6577,22 +6577,26 @@ static void CreateAppAOTSnapshot(
const bool generate_debug = debug_callback_data != nullptr;
auto* const deobfuscation_trie =
strip ? nullptr : ImageWriter::CreateReverseObfuscationTrie(T);
if (as_elf) {
StreamingWriteStream elf_stream(kInitialSize, callback, callback_data);
StreamingWriteStream debug_stream(generate_debug ? kInitialDebugSize : 0,
callback, debug_callback_data);
auto const dwarf = strip ? nullptr : new (Z) Dwarf(Z);
auto const dwarf = strip ? nullptr : new (Z) Dwarf(Z, deobfuscation_trie);
auto const elf = new (Z) Elf(Z, &elf_stream, Elf::Type::Snapshot, dwarf);
// Re-use the same DWARF object if the snapshot is unstripped.
auto const debug_elf =
generate_debug ? new (Z) Elf(Z, &debug_stream, Elf::Type::DebugInfo,
strip ? new (Z) Dwarf(Z) : dwarf)
: nullptr;
generate_debug
? new (Z) Elf(Z, &debug_stream, Elf::Type::DebugInfo,
strip ? new (Z) Dwarf(Z, deobfuscation_trie) : dwarf)
: nullptr;
BlobImageWriter image_writer(T, &vm_snapshot_instructions,
&isolate_snapshot_instructions, debug_elf,
elf);
&isolate_snapshot_instructions,
deobfuscation_trie, debug_elf, elf);
FullSnapshotWriter writer(Snapshot::kFullAOT, &vm_snapshot_data,
&isolate_snapshot_data, &image_writer,
&image_writer);
@ -6615,10 +6619,11 @@ static void CreateAppAOTSnapshot(
auto const elf = generate_debug
? new (Z) Elf(Z, &debug_stream, Elf::Type::DebugInfo,
new (Z) Dwarf(Z))
new (Z) Dwarf(Z, deobfuscation_trie))
: nullptr;
AssemblyImageWriter image_writer(T, &assembly_stream, strip, elf);
AssemblyImageWriter image_writer(T, &assembly_stream, deobfuscation_trie,
strip, elf);
FullSnapshotWriter writer(Snapshot::kFullAOT, &vm_snapshot_data,
&isolate_snapshot_data, &image_writer,
&image_writer);

View file

@ -91,52 +91,9 @@ class InliningNode : public ZoneAllocated {
InliningNode* children_next;
};
template <typename T>
Trie<T>* Trie<T>::AddString(Zone* zone,
Trie<T>* trie,
const char* key,
const T* value) {
ASSERT(key != nullptr);
if (trie == nullptr) {
trie = new (zone) Trie<T>();
}
if (*key == '\0') {
ASSERT(trie->value_ == nullptr);
trie->value_ = value;
} else {
auto const index = ChildIndex(*key);
ASSERT(index >= 0 && index < kNumValidChars);
trie->children_[index] =
AddString(zone, trie->children_[index], key + 1, value);
}
return trie;
}
template <typename T>
const T* Trie<T>::Lookup(const Trie<T>* trie, const char* key, intptr_t* end) {
intptr_t i = 0;
for (; key[i] != '\0'; i++) {
auto const index = ChildIndex(key[i]);
ASSERT(index < kNumValidChars);
if (index < 0) {
if (end == nullptr) return nullptr;
break;
}
// Still find the longest valid trie prefix when no stored value.
if (trie == nullptr) continue;
trie = trie->children_[index];
}
if (end != nullptr) {
*end = i;
}
if (trie == nullptr) return nullptr;
return trie->value_;
}
Dwarf::Dwarf(Zone* zone)
Dwarf::Dwarf(Zone* zone, const Trie<const char>* deobfuscation_trie)
: zone_(zone),
reverse_obfuscation_trie_(CreateReverseObfuscationTrie(zone)),
deobfuscation_trie_(deobfuscation_trie),
codes_(zone, 1024),
code_to_label_(zone),
functions_(zone, 1024),
@ -365,7 +322,8 @@ void Dwarf::WriteAbstractFunctions(DwarfWriteStream* stream) {
name = function.QualifiedUserVisibleName();
script = function.script();
const intptr_t file = LookupScript(script);
auto const name_cstr = Deobfuscate(name.ToCString());
auto const name_cstr =
ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, name.ToCString());
stream->RegisterAbstractOrigin(i);
stream->uleb128(kAbstractFunction);
@ -906,7 +864,8 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
} else {
uri = script.url();
ASSERT(!uri.IsNull());
uri_cstr = Deobfuscate(uri.ToCString());
uri_cstr = ImageWriter::Deobfuscate(zone_, deobfuscation_trie_,
uri.ToCString());
}
RELEASE_ASSERT(strlen(uri_cstr) != 0);
@ -947,49 +906,6 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
});
}
const char* Dwarf::Deobfuscate(const char* cstr) {
if (reverse_obfuscation_trie_ == nullptr) return cstr;
TextBuffer buffer(256);
// Used to avoid Zone-allocating strings if no deobfuscation was performed.
bool changed = false;
intptr_t i = 0;
while (cstr[i] != '\0') {
intptr_t offset;
auto const value = reverse_obfuscation_trie_->Lookup(cstr + i, &offset);
if (offset == 0) {
// The first character was an invalid key element (that isn't the null
// terminator due to the while condition), copy it and skip to the next.
buffer.AddChar(cstr[i++]);
} else if (value != nullptr) {
changed = true;
buffer.AddString(value);
} else {
buffer.AddRaw(reinterpret_cast<const uint8_t*>(cstr + i), offset);
}
i += offset;
}
if (!changed) return cstr;
return OS::SCreate(zone_, "%s", buffer.buffer());
}
Trie<const char>* Dwarf::CreateReverseObfuscationTrie(Zone* zone) {
auto const map_array = IsolateGroup::Current()->obfuscation_map();
if (map_array == nullptr) return nullptr;
Trie<const char>* trie = nullptr;
for (intptr_t i = 0; map_array[i] != nullptr; i += 2) {
auto const key = map_array[i];
auto const value = map_array[i + 1];
ASSERT(value != nullptr);
// Don't include identity mappings.
if (strcmp(key, value) == 0) continue;
// Otherwise, any value in the obfuscation map should be a valid key.
ASSERT(Trie<const char>::IsValidKey(value));
trie = Trie<const char>::AddString(zone, trie, value, key);
}
return trie;
}
#endif // DART_PRECOMPILER
} // namespace dart

View file

@ -8,6 +8,7 @@
#include "vm/allocation.h"
#include "vm/hash.h"
#include "vm/hash_map.h"
#include "vm/image_snapshot.h"
#include "vm/object.h"
#include "vm/zone.h"
@ -124,80 +125,6 @@ struct DwarfCodeKeyValueTrait {
template <typename T>
using DwarfCodeMap = DirectChainedHashMap<DwarfCodeKeyValueTrait<T>>;
template <typename T>
class Trie : public ZoneAllocated {
public:
// Returns whether [key] is a valid trie key (that is, a C string that
// contains only characters for which charIndex returns a non-negative value).
static bool IsValidKey(const char* key) {
for (intptr_t i = 0; key[i] != '\0'; i++) {
if (ChildIndex(key[i]) < 0) return false;
}
return true;
}
// Adds a binding of [key] to [value] in [trie]. Assumes that the string in
// [key] is a valid trie key and does not already have a value in [trie].
//
// If [trie] is nullptr, then a new trie is created and a pointer to the new
// trie is returned. Otherwise, [trie] will be returned.
static Trie<T>* AddString(Zone* zone,
Trie<T>* trie,
const char* key,
const T* value);
// Adds a binding of [key] to [value]. Assumes that the string in [key] is a
// valid trie key and does not already have a value in this trie.
void AddString(Zone* zone, const char* key, const T* value) {
AddString(zone, this, key, value);
}
// Looks up the value stored for [key] in [trie]. If one is not found, then
// nullptr is returned.
//
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
// trie key prefix will be used for the lookup and the value pointed to by
// [end] is set to the index after that prefix. Otherwise, the whole [key]
// is used.
static const T* Lookup(const Trie<T>* trie,
const char* key,
intptr_t* end = nullptr);
// Looks up the value stored for [key]. If one is not found, then nullptr is
// returned.
//
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
// trie key prefix will be used for the lookup and the value pointed to by
// [end] is set to the index after that prefix. Otherwise, the whole [key]
// is used.
const T* Lookup(const char* key, intptr_t* end = nullptr) const {
return Lookup(this, key, end);
}
private:
// Currently, only the following characters can appear in obfuscated names:
// '_', '@', '0-9', 'a-z', 'A-Z'
static constexpr intptr_t kNumValidChars = 64;
Trie() {
for (intptr_t i = 0; i < kNumValidChars; i++) {
children_[i] = nullptr;
}
}
static intptr_t ChildIndex(char c) {
if (c == '_') return 0;
if (c == '@') return 1;
if (c >= '0' && c <= '9') return ('9' - c) + 2;
if (c >= 'a' && c <= 'z') return ('z' - c) + 12;
if (c >= 'A' && c <= 'Z') return ('Z' - c) + 38;
return -1;
}
const T* value_ = nullptr;
Trie<T>* children_[kNumValidChars];
};
class DwarfWriteStream : public ValueObject {
public:
DwarfWriteStream() {}
@ -228,7 +155,7 @@ class DwarfWriteStream : public ValueObject {
class Dwarf : public ZoneAllocated {
public:
explicit Dwarf(Zone* zone);
explicit Dwarf(Zone* zone, const Trie<const char>* deobfuscation_trie);
const ZoneGrowableArray<const Code*>& codes() const { return codes_; }
@ -317,11 +244,8 @@ class Dwarf : public ZoneAllocated {
void WriteLineNumberProgramFromCodeSourceMaps(
LineNumberProgramWriter* writer);
const char* Deobfuscate(const char* cstr);
static Trie<const char>* CreateReverseObfuscationTrie(Zone* zone);
Zone* const zone_;
Trie<const char>* const reverse_obfuscation_trie_;
const Trie<const char>* const deobfuscation_trie_;
ZoneGrowableArray<const Code*> codes_;
DwarfCodeMap<intptr_t> code_to_label_;
ZoneGrowableArray<const Function*> functions_;

View file

@ -172,7 +172,13 @@ bool ObjectOffsetTrait::IsKeyEqual(Pair pair, Key key) {
}
#if !defined(DART_PRECOMPILED_RUNTIME)
#if defined(DART_PRECOMPILER)
ImageWriter::ImageWriter(Thread* t,
bool generates_assembly,
const Trie<const char>* deobfuscation_trie)
#else
ImageWriter::ImageWriter(Thread* t, bool generates_assembly)
#endif
: thread_(ASSERT_NOTNULL(t)),
zone_(t->zone()),
next_data_offset_(0),
@ -180,7 +186,9 @@ ImageWriter::ImageWriter(Thread* t, bool generates_assembly)
objects_(),
instructions_(),
#if defined(DART_PRECOMPILER)
namer_(t->zone(), /*for_assembly=*/generates_assembly),
namer_(t->zone(),
deobfuscation_trie,
/*for_assembly=*/generates_assembly),
#endif
image_type_(TagObjectTypeAsReadOnly(zone_, "Image")),
instructions_section_type_(
@ -1099,25 +1107,32 @@ class DwarfAssemblyStream : public DwarfWriteStream {
DISALLOW_COPY_AND_ASSIGN(DwarfAssemblyStream);
};
static inline Dwarf* AddDwarfIfUnstripped(Zone* zone, bool strip, Elf* elf) {
static inline Dwarf* AddDwarfIfUnstripped(
Zone* zone,
bool strip,
Elf* elf,
const Trie<const char>* deobfuscation_trie) {
if (!strip) {
if (elf != nullptr) {
// Reuse the existing DWARF object.
ASSERT(elf->dwarf() != nullptr);
return elf->dwarf();
}
return new (zone) Dwarf(zone);
return new (zone) Dwarf(zone, deobfuscation_trie);
}
return nullptr;
}
AssemblyImageWriter::AssemblyImageWriter(Thread* thread,
BaseWriteStream* stream,
bool strip,
Elf* debug_elf)
: ImageWriter(thread, /*generates_assembly=*/true),
AssemblyImageWriter::AssemblyImageWriter(
Thread* thread,
BaseWriteStream* stream,
const Trie<const char>* deobfuscation_trie,
bool strip,
Elf* debug_elf)
: ImageWriter(thread, /*generates_assembly=*/true, deobfuscation_trie),
assembly_stream_(stream),
assembly_dwarf_(AddDwarfIfUnstripped(zone_, strip, debug_elf)),
assembly_dwarf_(
AddDwarfIfUnstripped(zone_, strip, debug_elf, deobfuscation_trie)),
debug_elf_(debug_elf),
label_to_symbol_name_(zone_) {
// Set up the label mappings for the section symbols for use in relocations.
@ -1185,13 +1200,37 @@ void ImageWriter::SnapshotTextObjectNamer::AddNonUniqueNameFor(
}
} else if (object.IsClass()) {
const char* name = Class::Cast(object).UserVisibleNameCString();
buffer->AddString(name);
const char* deobfuscated_name =
ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, name);
buffer->AddString(deobfuscated_name);
} else if (object.IsAbstractType()) {
AbstractType::Cast(object).PrintName(Object::kUserVisibleName, buffer);
const AbstractType& type = AbstractType::Cast(object);
if (deobfuscation_trie_ == nullptr) {
// Print directly to the output buffer.
type.PrintName(Object::kUserVisibleName, buffer);
} else {
// Use an intermediate buffer for deobfuscation purposes.
ZoneTextBuffer temp_buffer(zone_);
type.PrintName(Object::kUserVisibleName, &temp_buffer);
const char* deobfuscated_name = ImageWriter::Deobfuscate(
zone_, deobfuscation_trie_, temp_buffer.buffer());
buffer->AddString(deobfuscated_name);
}
} else if (object.IsFunction()) {
const Function& func = Function::Cast(object);
func.PrintName({Object::kUserVisibleName, Object::NameDisambiguation::kNo},
buffer);
NameFormattingParams params(
{Object::kUserVisibleName, Object::NameDisambiguation::kNo});
if (deobfuscation_trie_ == nullptr) {
// Print directly to the output buffer.
func.PrintName(params, buffer);
} else {
// Use an intermediate buffer for deobfuscation purposes.
ZoneTextBuffer temp_buffer(zone_);
func.PrintName(params, &temp_buffer);
const char* deobfuscated_name = ImageWriter::Deobfuscate(
zone_, deobfuscation_trie_, temp_buffer.buffer());
buffer->AddString(deobfuscated_name);
}
} else if (object.IsCompressedStackMaps()) {
buffer->AddString("CompressedStackMaps");
} else if (object.IsPcDescriptors()) {
@ -1274,6 +1313,52 @@ const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor(
return printer.buffer();
}
Trie<const char>* ImageWriter::CreateReverseObfuscationTrie(Thread* thread) {
auto* const zone = thread->zone();
auto* const map_array = thread->isolate_group()->obfuscation_map();
if (map_array == nullptr) return nullptr;
Trie<const char>* trie = nullptr;
for (intptr_t i = 0; map_array[i] != nullptr; i += 2) {
auto const key = map_array[i];
auto const value = map_array[i + 1];
ASSERT(value != nullptr);
// Don't include identity mappings.
if (strcmp(key, value) == 0) continue;
// Otherwise, any value in the obfuscation map should be a valid key.
ASSERT(Trie<const char>::IsValidKey(value));
trie = Trie<const char>::AddString(zone, trie, value, key);
}
return trie;
}
const char* ImageWriter::Deobfuscate(Zone* zone,
const Trie<const char>* trie,
const char* cstr) {
if (trie == nullptr) return cstr;
TextBuffer buffer(256);
// Used to avoid Zone-allocating strings if no deobfuscation was performed.
bool changed = false;
intptr_t i = 0;
while (cstr[i] != '\0') {
intptr_t offset;
auto const value = trie->Lookup(cstr + i, &offset);
if (offset == 0) {
// The first character was an invalid key element (that isn't the null
// terminator due to the while condition), copy it and skip to the next.
buffer.AddChar(cstr[i++]);
} else if (value != nullptr) {
changed = true;
buffer.AddString(value);
} else {
buffer.AddRaw(reinterpret_cast<const uint8_t*>(cstr + i), offset);
}
i += offset;
}
if (!changed) return cstr;
return OS::SCreate(zone, "%s", buffer.buffer());
}
void AssemblyImageWriter::WriteBss(bool vm) {
EnterSection(ProgramSection::Bss, vm, ImageWriter::kBssAlignment);
auto const entry_count =
@ -1636,12 +1721,22 @@ intptr_t AssemblyImageWriter::Align(intptr_t alignment,
}
#endif // defined(DART_PRECOMPILER)
#if defined(DART_PRECOMPILER)
BlobImageWriter::BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
const Trie<const char>* deobfuscation_trie,
Elf* debug_elf,
Elf* elf)
: ImageWriter(thread, /*generates_assembly=*/false, deobfuscation_trie),
#else
BlobImageWriter::BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
Elf* debug_elf,
Elf* elf)
: ImageWriter(thread, /*generates_assembly=*/false),
#endif
vm_instructions_(vm_instructions),
isolate_instructions_(isolate_instructions),
elf_(elf),

View file

@ -233,9 +233,134 @@ struct ImageWriterCommand {
};
};
#if defined(DART_PRECOMPILER)
template <typename T>
class Trie : public ZoneAllocated {
public:
// Returns whether [key] is a valid trie key (that is, a C string that
// contains only characters for which charIndex returns a non-negative value).
static bool IsValidKey(const char* key) {
for (intptr_t i = 0; key[i] != '\0'; i++) {
if (ChildIndex(key[i]) < 0) return false;
}
return true;
}
// Adds a binding of [key] to [value] in [trie]. Assumes that the string in
// [key] is a valid trie key and does not already have a value in [trie].
//
// If [trie] is nullptr, then a new trie is created and a pointer to the new
// trie is returned. Otherwise, [trie] will be returned.
static Trie<T>* AddString(Zone* zone,
Trie<T>* trie,
const char* key,
const T* value);
// Adds a binding of [key] to [value]. Assumes that the string in [key] is a
// valid trie key and does not already have a value in this trie.
void AddString(Zone* zone, const char* key, const T* value) {
AddString(zone, this, key, value);
}
// Looks up the value stored for [key] in [trie]. If one is not found, then
// nullptr is returned.
//
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
// trie key prefix will be used for the lookup and the value pointed to by
// [end] is set to the index after that prefix. Otherwise, the whole [key]
// is used.
static const T* Lookup(const Trie<T>* trie,
const char* key,
intptr_t* end = nullptr);
// Looks up the value stored for [key]. If one is not found, then nullptr is
// returned.
//
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
// trie key prefix will be used for the lookup and the value pointed to by
// [end] is set to the index after that prefix. Otherwise, the whole [key]
// is used.
const T* Lookup(const char* key, intptr_t* end = nullptr) const {
return Lookup(this, key, end);
}
private:
// Currently, only the following characters can appear in obfuscated names:
// '_', '@', '0-9', 'a-z', 'A-Z'
static constexpr intptr_t kNumValidChars = 64;
Trie() {
for (intptr_t i = 0; i < kNumValidChars; i++) {
children_[i] = nullptr;
}
}
static intptr_t ChildIndex(char c) {
if (c == '_') return 0;
if (c == '@') return 1;
if (c >= '0' && c <= '9') return ('9' - c) + 2;
if (c >= 'a' && c <= 'z') return ('z' - c) + 12;
if (c >= 'A' && c <= 'Z') return ('Z' - c) + 38;
return -1;
}
const T* value_ = nullptr;
Trie<T>* children_[kNumValidChars];
};
template <typename T>
Trie<T>* Trie<T>::AddString(Zone* zone,
Trie<T>* trie,
const char* key,
const T* value) {
ASSERT(key != nullptr);
if (trie == nullptr) {
trie = new (zone) Trie<T>();
}
if (*key == '\0') {
ASSERT(trie->value_ == nullptr);
trie->value_ = value;
} else {
auto const index = ChildIndex(*key);
ASSERT(index >= 0 && index < kNumValidChars);
trie->children_[index] =
AddString(zone, trie->children_[index], key + 1, value);
}
return trie;
}
template <typename T>
const T* Trie<T>::Lookup(const Trie<T>* trie, const char* key, intptr_t* end) {
intptr_t i = 0;
for (; key[i] != '\0'; i++) {
auto const index = ChildIndex(key[i]);
ASSERT(index < kNumValidChars);
if (index < 0) {
if (end == nullptr) return nullptr;
break;
}
// Still find the longest valid trie prefix when no stored value.
if (trie == nullptr) continue;
trie = trie->children_[index];
}
if (end != nullptr) {
*end = i;
}
if (trie == nullptr) return nullptr;
return trie->value_;
}
#endif
class ImageWriter : public ValueObject {
public:
explicit ImageWriter(Thread* thread, bool generates_assembly);
#if defined(DART_PRECOMPILER)
ImageWriter(Thread* thread,
bool generates_assembly,
const Trie<const char>* deobfuscation_trie = nullptr);
#else
ImageWriter(Thread* thread, bool generates_assembly);
#endif
virtual ~ImageWriter() {}
// Alignment constants used in writing ELF or assembly snapshots.
@ -328,6 +453,11 @@ class ImageWriter : public ValueObject {
// The initial 1 is to ensure the result is positive.
return 1 + 2 * static_cast<int>(section) + ((shared || vm) ? 0 : 1);
}
static Trie<const char>* CreateReverseObfuscationTrie(Thread* thread);
static const char* Deobfuscate(Zone* zone,
const Trie<const char>* trie,
const char* str);
#endif
protected:
@ -514,8 +644,11 @@ class ImageWriter : public ValueObject {
#if defined(DART_PRECOMPILER)
class SnapshotTextObjectNamer : ValueObject {
public:
explicit SnapshotTextObjectNamer(Zone* zone, bool for_assembly)
explicit SnapshotTextObjectNamer(Zone* zone,
const Trie<const char>* deobfuscation_trie,
bool for_assembly)
: zone_(ASSERT_NOTNULL(zone)),
deobfuscation_trie_(deobfuscation_trie),
lib_(Library::Handle(zone)),
cls_(Class::Handle(zone)),
parent_(Function::Handle(zone)),
@ -548,6 +681,7 @@ class ImageWriter : public ValueObject {
void ModifyForAssembly(BaseTextBuffer* buffer);
Zone* const zone_;
const Trie<const char>* const deobfuscation_trie_;
Library& lib_;
Class& cls_;
Function& parent_;
@ -643,6 +777,7 @@ class AssemblyImageWriter : public ImageWriter {
public:
AssemblyImageWriter(Thread* thread,
BaseWriteStream* stream,
const Trie<const char>* deobfuscation_trie = nullptr,
bool strip = false,
Elf* debug_elf = nullptr);
void Finalize();
@ -699,11 +834,20 @@ class AssemblyImageWriter : public ImageWriter {
class BlobImageWriter : public ImageWriter {
public:
#if defined(DART_PRECOMPILER)
BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
const Trie<const char>* deobfuscation_trie = nullptr,
Elf* debug_elf = nullptr,
Elf* elf = nullptr);
#else
BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
Elf* debug_elf = nullptr,
Elf* elf = nullptr);
#endif
private:
virtual void WriteBss(bool vm);