Reland "[vm, gen_snapshot] Add app-aot-macho-dylib option for AOT snapshots."

This is a reland of commit 38ef28a058

Fixes:

* Fix comparisons in ASSERT_EQUAL statements on 32-bit architectures.
* Take simulated architectures into account when deciding whether
  to use dlopen() for native shared object formats.
* Fix struct/field name collision for GCC.
* Use CPU_TYPE_ANY/CPU_SUBTYPE_ANY for architectures that do not
  have more specific cpu_type_t/cpu_subtype_t constants defined.

Original change's description:
> [vm, gen_snapshot] Add app-aot-macho-dylib option for AOT snapshots.
>
> This is the initial framework for creating snapshots as Mach-O dynamic
> libraries. Note that this framework is not 100% feature complete
> compared to generating Mach-O snapshots via assembly. In particular,
> the directly-compiled Mach-O dylib does not yet contain compact
> unwinding information.
>
> Other changes:
>
> * Adds UuidCommand to the native_stack_traces package's Mach-O reader,
>   which now appropriately returns the UUID as the build ID for Mach-O
>   shared objects.
>
> * Adds Utils::Basename(path) for portably retrieving the basename
>   from a path. (Returns nullptr for all arguments where it is not
>   currently implemented on Fuchsia or Windows.)
>
> * Adjusts vm/timeline.h to avoid pulling in <mach_o/loader.h> on MacOS,
>   as that interferes with uses of the namespaced Mach-O definitions
>   in platform/mach_o.h.
>
> * Only attempt to dlopen() a snapshot if ELF is the native format
>   for the host platform or the snapshot is not an ELF shared object.
>   If dlopen() is used, report the error message if it fails rather
>   than attempting to manually load the snapshot as an ELF shared object.
>
> * Fix the magic number stored in DylibAppSnapshot for loaded non-ELF
>   dynamic libraries.
>
> * Remove the detection of reverse-endian Mach-O magic numbers in
>   DartUtils::SniffForMagicNumber(), since all our Mach-O related code
>   assumes host-endian Mach-O files and so there's no point other than
>   to give a slightly better error message when failing.
>
> TEST=vm/dart/exported_symbols_test
>      vm/dart/unobfuscated_static_symbols_test
>      vm/dart/use_dwarf_stack_traces_flag_test
>      vm/cc/CanDetectMachOFiles
>
> Issue: https://github.com/dart-lang/sdk/issues/60307
> Change-Id: Idf5b49d6c6d035ab033509613212b95520d65965
> Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-mac-release-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-fuchsia-release-x64-try,vm-fuchsia-release-arm64-try
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415020
> Reviewed-by: Slava Egorov <vegorov@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>

TEST=vm/dart/exported_symbols_test
     vm/dart/unobfuscated_static_symbols_test
     vm/dart/use_dwarf_stack_traces_flag_test
     vm/cc/CanDetectMachOFiles
     ci on trybots that failed on the original CL

Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-mac-release-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-fuchsia-release-x64-try,vm-fuchsia-release-arm64-try,vm-linux-debug-ia32-try,vm-aot-linux-debug-simarm_x64-try,vm-aot-linux-debug-simriscv32-try,vm-aot-linux-debug-simriscv64-try,vm-aot-linux-release-simarm_x64-try,vm-gcc-linux-try,vm-ubsan-linux-release-arm64-try
Change-Id: Iaffea0ddc6173100c8b5b2a9fe46c45f4f611a2e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/431240
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Tess Strickland
2025-05-27 06:30:20 -07:00
committed by Commit Queue
parent 38ea3a8f5e
commit 6b53073eae
37 changed files with 4071 additions and 469 deletions
+3
View File
@@ -1,3 +1,6 @@
## 0.6.1
- Add handling for Mach-O UUID load commands.
## 0.6.1-wip
- Update SDK constraint to `^3.5.0`.
+29 -1
View File
@@ -124,6 +124,7 @@ class LoadCommand {
static const LC_SEGMENT = 0x1;
static const LC_SYMTAB = 0x2;
static const LC_SEGMENT_64 = 0x19;
static const LC_UUID = 0x1b;
static LoadCommand fromReader(Reader reader) {
final start = reader.offset; // cmdsize includes size of cmd and cmdsize.
@@ -139,6 +140,9 @@ class LoadCommand {
case LC_SYMTAB:
command = SymbolTableCommand.fromReader(reader, cmd, cmdsize);
break;
case LC_UUID:
command = UuidCommand.fromReader(reader, cmd, cmdsize);
break;
default:
break;
}
@@ -324,6 +328,29 @@ class SymbolTableCommand extends LoadCommand {
}
}
class UuidCommand extends LoadCommand {
Uint8List uuid;
static const kUuidSize = 16;
UuidCommand._(super.cmd, super.cmdsize, this.uuid) : super._();
static UuidCommand fromReader(Reader reader, int cmd, int cmdsize) {
final uuid = Uint8List.sublistView(
reader.bytes, reader.offset, reader.offset + kUuidSize);
return UuidCommand._(cmd, cmdsize, uuid);
}
String get uuidString => uuid.map((i) => paddedHex(i, 1)).join();
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('UUID: ')
..write(uuidString);
}
}
class MachOHeader {
final int magic;
final int cputype;
@@ -523,7 +550,8 @@ class MachO extends DwarfContainer {
_symbolTable[constants.isolateSymbolName]?.value;
@override
String? get buildId => null;
String? get buildId =>
_commands.whereType<UuidCommand>().firstOrNull?.uuidString;
@override
DwarfContainerStringTable? get debugStringTable => _debugStringTable;
+2 -2
View File
@@ -1,5 +1,5 @@
name: native_stack_traces
version: 0.6.1-wip
version: 0.6.1
description: Utilities for working with non-symbolic stack traces.
repository: https://github.com/dart-lang/sdk/tree/main/pkg/native_stack_traces
@@ -13,7 +13,7 @@ executables:
dependencies:
args: ^2.0.0
path: ^1.8.0
path: ^1.9.0
# We use 'any' version constraints here as we get our package versions from
# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
+24
View File
@@ -424,6 +424,13 @@ typedef Dart_Handle (*Dart_CreateAppAOTSnapshotAsElfsType)(
bool,
Dart_StreamingWriteCallback,
Dart_StreamingCloseCallback);
typedef Dart_Handle (*Dart_CreateAppAOTSnapshotAsBinaryType)(
Dart_AotBinaryFormat,
Dart_StreamingWriteCallback,
void*,
bool,
void*,
const char*);
typedef Dart_Handle (*Dart_CreateVMAOTSnapshotAsAssemblyType)(
Dart_StreamingWriteCallback,
void*);
@@ -724,6 +731,8 @@ static Dart_CreateAppAOTSnapshotAsElfType Dart_CreateAppAOTSnapshotAsElfFn =
NULL;
static Dart_CreateAppAOTSnapshotAsElfsType Dart_CreateAppAOTSnapshotAsElfsFn =
NULL;
static Dart_CreateAppAOTSnapshotAsBinaryType
Dart_CreateAppAOTSnapshotAsBinaryFn = NULL;
static Dart_CreateVMAOTSnapshotAsAssemblyType
Dart_CreateVMAOTSnapshotAsAssemblyFn = NULL;
static Dart_SortClassesType Dart_SortClassesFn = NULL;
@@ -1293,6 +1302,9 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
Dart_CreateAppAOTSnapshotAsElfsFn =
(Dart_CreateAppAOTSnapshotAsElfsType)GetProcAddress(
process, "Dart_CreateAppAOTSnapshotAsElfs");
Dart_CreateAppAOTSnapshotAsBinaryFn =
(Dart_CreateAppAOTSnapshotAsBinaryType)GetProcAddress(
process, "Dart_CreateAppAOTSnapshotAsBinary");
Dart_CreateVMAOTSnapshotAsAssemblyFn =
(Dart_CreateVMAOTSnapshotAsAssemblyType)GetProcAddress(
process, "Dart_CreateVMAOTSnapshotAsAssembly");
@@ -2551,6 +2563,18 @@ Dart_Handle Dart_CreateAppAOTSnapshotAsElfs(
close_callback);
}
Dart_Handle Dart_CreateAppAOTSnapshotAsBinary(
Dart_AotBinaryFormat format,
Dart_StreamingWriteCallback callback,
void* callback_data,
bool stripped,
void* debug_callback_data,
const char* identifier) {
return Dart_CreateAppAOTSnapshotAsBinaryFn(format, callback, callback_data,
stripped, debug_callback_data,
identifier);
}
Dart_Handle Dart_CreateVMAOTSnapshotAsAssembly(
Dart_StreamingWriteCallback callback,
void* callback_data) {
+16 -16
View File
@@ -14,6 +14,7 @@
#include "include/dart_native_api.h"
#include "platform/assert.h"
#include "platform/globals.h"
#include "platform/mach_o.h"
#include "platform/utils.h"
// Return the error from the containing function if handle is in error handle.
@@ -34,9 +35,6 @@ dart::SimpleHashMap* DartUtils::environment_ = nullptr;
MagicNumberData appjit_magic_number = {8, {0xdc, 0xdc, 0xf6, 0xf6, 0, 0, 0, 0}};
MagicNumberData aotelf_magic_number = {4, {0x7F, 0x45, 0x4C, 0x46, 0x0}};
MagicNumberData aotmacho32_magic_number = {4, {0xFE, 0xED, 0xFA, 0xCE}};
MagicNumberData aotmacho64_magic_number = {4, {0xFE, 0xED, 0xFA, 0xCF}};
MagicNumberData aotmacho64_arm64_magic_number = {4, {0xCF, 0xFA, 0xED, 0xFE}};
MagicNumberData aotcoff_arm32_magic_number = {2, {0x01, 0xC0}};
MagicNumberData aotcoff_arm64_magic_number = {2, {0xAA, 0x64}};
MagicNumberData aotcoff_riscv32_magic_number = {2, {0x50, 0x32}};
@@ -404,9 +402,8 @@ DartUtils::MagicNumber DartUtils::SniffForMagicNumber(const char* filename) {
MagicNumber magic_number = DartUtils::kUnknownMagicNumber;
ASSERT(kMaxMagicNumberSize == appjit_magic_number.length);
ASSERT(aotelf_magic_number.length <= appjit_magic_number.length);
ASSERT(aotmacho32_magic_number.length <= appjit_magic_number.length);
ASSERT(aotmacho64_magic_number.length <= appjit_magic_number.length);
ASSERT(aotmacho64_arm64_magic_number.length <= appjit_magic_number.length);
ASSERT(static_cast<intptr_t>(sizeof(mach_o::mach_header::magic)) <=
appjit_magic_number.length);
ASSERT(aotcoff_arm32_magic_number.length <= appjit_magic_number.length);
ASSERT(aotcoff_arm64_magic_number.length <= appjit_magic_number.length);
ASSERT(aotcoff_riscv32_magic_number.length <= appjit_magic_number.length);
@@ -453,16 +450,19 @@ DartUtils::MagicNumber DartUtils::SniffForMagicNumber(const uint8_t* buffer,
return kAotELFMagicNumber;
}
if (CheckMagicNumber(buffer, buffer_length, aotmacho32_magic_number)) {
return kAotMachO32MagicNumber;
}
if (CheckMagicNumber(buffer, buffer_length, aotmacho64_magic_number)) {
return kAotMachO64MagicNumber;
}
if (CheckMagicNumber(buffer, buffer_length, aotmacho64_arm64_magic_number)) {
return kAotMachO64Arm64MagicNumber;
// Mach-O magic numbers are reported by whether the endianness of the file
// matches the endianness of the system. Here, we only bother looking for
// host-endian magic numbers, as our Mach-O parsing code won't handle the
// reverse endian case.
if (static_cast<intptr_t>(sizeof(mach_o::mach_header::magic)) <=
buffer_length) {
const uint32_t magic =
reinterpret_cast<const mach_o::mach_header*>(buffer)->magic;
if (magic == mach_o::MH_MAGIC) {
return kAotMachO32MagicNumber;
} else if (magic == mach_o::MH_MAGIC_64) {
return kAotMachO64MagicNumber;
}
}
if (CheckMagicNumber(buffer, buffer_length, aotcoff_arm32_magic_number)) {
+20 -2
View File
@@ -260,9 +260,10 @@ class DartUtils {
kKernelListMagicNumber,
kGzipMagicNumber,
kAotELFMagicNumber,
// Only the host-endian magic numbers are recognized, not the reverse-endian
// ("cigam") ones, as we can't load a reverse-endian snapshot anyway.
kAotMachO32MagicNumber,
kAotMachO64MagicNumber,
kAotMachO64Arm64MagicNumber,
kAotCoffARM32MagicNumber,
kAotCoffARM64MagicNumber,
kAotCoffRISCV32MagicNumber,
@@ -278,7 +279,24 @@ class DartUtils {
(number <= DartUtils::kAotCoffRISCV64MagicNumber);
}
// Checks if the buffer is a script snapshot, kernel file, or gzip file.
// Returns the bitsize corresponding to the magic number if the bitsize
// is specified by the magic number, otherwise returns -1.
static intptr_t MagicNumberBitSize(MagicNumber number) {
if (number == DartUtils::kAotMachO32MagicNumber ||
number == DartUtils::kAotCoffARM32MagicNumber ||
number == DartUtils::kAotCoffRISCV32MagicNumber) {
return 32;
}
if (number == DartUtils::kAotMachO64MagicNumber ||
number == DartUtils::kAotCoffARM64MagicNumber ||
number == DartUtils::kAotCoffRISCV64MagicNumber) {
return 64;
}
return -1;
}
// Checks if the file is a script snapshot, kernel file, or gzip file
// by reading the first kMaxMagicNumberSize bytes of the file.
static MagicNumber SniffForMagicNumber(const char* filename);
// Checks if the buffer is a script snapshot, kernel file, or gzip file.
+109 -71
View File
@@ -78,6 +78,7 @@ enum SnapshotKind {
kAppJIT,
kAppAOTAssembly,
kAppAOTElf,
kAppAOTMachODylib,
kVMAOTAssembly,
};
static SnapshotKind snapshot_kind = kCore;
@@ -90,6 +91,7 @@ static const char* const kSnapshotKindNames[] = {
"app-jit",
"app-aot-assembly",
"app-aot-elf",
"app-aot-macho-dylib",
"vm-aot-assembly",
nullptr,
// clang-format on
@@ -108,6 +110,7 @@ static const char* const kSnapshotKindNames[] = {
V(blobs_container_filename, blobs_container_filename) \
V(assembly, assembly_filename) \
V(elf, elf_filename) \
V(macho, macho_filename) \
V(loading_unit_manifest, loading_unit_manifest_filename) \
V(save_debugging_info, debugging_info_filename) \
V(save_obfuscation_map, obfuscation_map_filename)
@@ -137,6 +140,7 @@ DEFINE_CB_OPTION(ProcessEnvironmentOption);
static bool IsSnapshottingForPrecompilation() {
return (snapshot_kind == kAppAOTAssembly) || (snapshot_kind == kAppAOTElf) ||
(snapshot_kind == kAppAOTMachODylib) ||
(snapshot_kind == kVMAOTAssembly);
}
@@ -176,6 +180,15 @@ static void PrintUsage() {
"[--save-obfuscation-map=<map-filename>] \n"
"<dart-kernel-file> \n"
" \n"
"To create an AOT application snapshot as an Mach-O dynamic library (dylib): \n"
"--snapshot_kind=app-aot-macho-dylib \n"
"--macho=<output-file> \n"
"[--strip] \n"
"[--obfuscate] \n"
"[--save-debugging-info=<debug-filename>] \n"
"[--save-obfuscation-map=<map-filename>] \n"
"<dart-kernel-file> \n"
" \n"
"AOT snapshots can be obfuscated: that is all identifiers will be renamed \n"
"during compilation. This mode is enabled with --obfuscate flag. Mapping \n"
"between original and obfuscated names can be serialized as a JSON array \n"
@@ -267,6 +280,15 @@ static int ParseArguments(int argc,
}
break;
}
case kAppAOTMachODylib: {
if (macho_filename == nullptr) {
Syslog::PrintErr(
"Building an AOT snapshot as a Mach-O dynamic library requires "
" specifying an output file for --macho.\n\n");
return -1;
}
break;
}
case kAppAOTAssembly:
case kVMAOTAssembly: {
if (assembly_filename == nullptr) {
@@ -597,78 +619,99 @@ static void NextElfCallback(void* callback_data,
static void CreateAndWritePrecompiledSnapshot() {
ASSERT(IsSnapshottingForPrecompilation());
Dart_Handle result;
if (snapshot_kind == kVMAOTAssembly) {
File* file = OpenFile(assembly_filename);
RefCntReleaseScope<File> rs(file);
Dart_Handle result =
Dart_CreateVMAOTSnapshotAsAssembly(StreamingWriteCallback, file);
CHECK_RESULT(result);
return;
}
Dart_AotBinaryFormat format;
const char* kind_str = nullptr;
const char* filename = nullptr;
// Default to the assembly ones just to avoid having to type-specify here.
auto* next_callback = NextAsmCallback;
auto* create_multiple_callback = Dart_CreateAppAOTSnapshotAsAssemblies;
switch (snapshot_kind) {
case kAppAOTAssembly:
kind_str = "assembly code";
filename = assembly_filename;
format = Dart_AotBinaryFormat_Assembly;
break;
case kAppAOTElf:
kind_str = "ELF library";
filename = elf_filename;
format = Dart_AotBinaryFormat_Elf;
next_callback = NextElfCallback;
create_multiple_callback = Dart_CreateAppAOTSnapshotAsElfs;
break;
case kAppAOTMachODylib:
kind_str = "MachO dynamic library";
filename = macho_filename;
format = Dart_AotBinaryFormat_MachO_Dylib;
// Not currently implemented.
next_callback = nullptr;
create_multiple_callback = nullptr;
break;
default:
UNREACHABLE();
}
ASSERT(kind_str != nullptr);
ASSERT(filename != nullptr);
// Precompile with specified embedder entry points
result = Dart_Precompile();
Dart_Handle result = Dart_Precompile();
CHECK_RESULT(result);
if (strip && (debugging_info_filename == nullptr)) {
Syslog::PrintErr(
"Warning: Generating %s without DWARF debugging"
" information.\n",
kind_str);
}
char* identifier = Utils::Basename(filename);
// Create a precompiled snapshot.
if (snapshot_kind == kAppAOTAssembly) {
if (strip && (debugging_info_filename == nullptr)) {
Syslog::PrintErr(
"Warning: Generating assembly code without DWARF debugging"
" information.\n");
if (loading_unit_manifest_filename == nullptr) {
File* file = OpenFile(filename);
RefCntReleaseScope<File> rs(file);
File* debug_file = nullptr;
if (debugging_info_filename != nullptr) {
debug_file = OpenFile(debugging_info_filename);
}
if (loading_unit_manifest_filename == nullptr) {
File* file = OpenFile(assembly_filename);
RefCntReleaseScope<File> rs(file);
File* debug_file = nullptr;
if (debugging_info_filename != nullptr) {
debug_file = OpenFile(debugging_info_filename);
}
result = Dart_CreateAppAOTSnapshotAsAssembly(StreamingWriteCallback, file,
strip, debug_file);
if (debug_file != nullptr) debug_file->Release();
CHECK_RESULT(result);
} else {
File* manifest_file = OpenLoadingUnitManifest();
result = Dart_CreateAppAOTSnapshotAsAssemblies(
NextAsmCallback, manifest_file, strip, StreamingWriteCallback,
StreamingCloseCallback);
CHECK_RESULT(result);
CloseLoadingUnitManifest(manifest_file);
}
if (obfuscate && !strip) {
Syslog::PrintErr(
"Warning: The generated assembly code contains unobfuscated DWARF "
"debugging information.\n"
" To avoid this, use --strip to remove it.\n");
}
} else if (snapshot_kind == kAppAOTElf) {
if (strip && (debugging_info_filename == nullptr)) {
Syslog::PrintErr(
"Warning: Generating ELF library without DWARF debugging"
" information.\n");
}
if (loading_unit_manifest_filename == nullptr) {
File* file = OpenFile(elf_filename);
RefCntReleaseScope<File> rs(file);
File* debug_file = nullptr;
if (debugging_info_filename != nullptr) {
debug_file = OpenFile(debugging_info_filename);
}
result = Dart_CreateAppAOTSnapshotAsElf(StreamingWriteCallback, file,
strip, debug_file);
if (debug_file != nullptr) debug_file->Release();
CHECK_RESULT(result);
} else {
File* manifest_file = OpenLoadingUnitManifest();
result = Dart_CreateAppAOTSnapshotAsElfs(NextElfCallback, manifest_file,
strip, StreamingWriteCallback,
StreamingCloseCallback);
CHECK_RESULT(result);
CloseLoadingUnitManifest(manifest_file);
}
if (obfuscate && !strip) {
Syslog::PrintErr(
"Warning: The generated ELF library contains unobfuscated DWARF "
"debugging information.\n"
" To avoid this, use --strip to remove it and "
"--save-debugging-info=<...> to save it to a separate file.\n");
result = Dart_CreateAppAOTSnapshotAsBinary(
format, StreamingWriteCallback, file, strip, debug_file, identifier);
if (debug_file != nullptr) debug_file->Release();
if (identifier != nullptr) {
free(identifier);
identifier = nullptr;
}
CHECK_RESULT(result);
} else {
UNREACHABLE();
ASSERT(create_multiple_callback != nullptr);
ASSERT(next_callback != nullptr);
File* manifest_file = OpenLoadingUnitManifest();
result = create_multiple_callback(next_callback, manifest_file, strip,
StreamingWriteCallback,
StreamingCloseCallback);
if (identifier != nullptr) {
free(identifier);
identifier = nullptr;
}
CHECK_RESULT(result);
CloseLoadingUnitManifest(manifest_file);
}
if (obfuscate && !strip) {
Syslog::PrintErr(
"Warning: The generated %s contains unobfuscated DWARF "
"debugging information.\n"
" To avoid this, use --strip to remove it.\n",
kind_str);
}
// Serialize obfuscation map if requested.
@@ -769,15 +812,10 @@ static int CreateIsolateAndSnapshot(const CommandLineOptions& inputs) {
break;
case kAppAOTAssembly:
case kAppAOTElf:
case kAppAOTMachODylib:
case kVMAOTAssembly:
CreateAndWritePrecompiledSnapshot();
break;
case kVMAOTAssembly: {
File* file = OpenFile(assembly_filename);
RefCntReleaseScope<File> rs(file);
result = Dart_CreateVMAOTSnapshotAsAssembly(StreamingWriteCallback, file);
CHECK_RESULT(result);
break;
}
default:
UNREACHABLE();
}
+161 -114
View File
@@ -2,10 +2,11 @@
// 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.
#include <memory>
#include "bin/snapshot_utils.h"
#include <cerrno>
#include <memory>
#include "bin/dartutils.h"
#include "bin/dfe.h"
#include "bin/elf_loader.h"
@@ -23,6 +24,15 @@
#define LOG_SECTION_BOUNDARIES false
#if !defined(USING_SIMULATOR)
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) || \
defined(DART_HOST_OS_FUCHSIA)
#define NATIVE_SHARED_OBJECT_FORMAT_ELF 1
#elif defined(DART_HOST_OS_MACOS)
#define NATIVE_SHARED_OBJECT_FORMAT_MACHO 1
#endif
#endif // !defined(USING_SIMULATOR)
namespace dart {
namespace bin {
@@ -145,6 +155,110 @@ static AppSnapshot* TryReadAppSnapshotBlobs(const char* script_name,
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#if defined(DART_PRECOMPILED_RUNTIME)
class DylibAppSnapshot : public AppSnapshot {
public:
DylibAppSnapshot(DartUtils::MagicNumber magic_number,
void* library,
const uint8_t* vm_snapshot_data,
const uint8_t* vm_snapshot_instructions,
const uint8_t* isolate_snapshot_data,
const uint8_t* isolate_snapshot_instructions)
: AppSnapshot(magic_number),
library_(library),
vm_snapshot_data_(vm_snapshot_data),
vm_snapshot_instructions_(vm_snapshot_instructions),
isolate_snapshot_data_(isolate_snapshot_data),
isolate_snapshot_instructions_(isolate_snapshot_instructions) {}
~DylibAppSnapshot() { Utils::UnloadDynamicLibrary(library_); }
void SetBuffers(const uint8_t** vm_data_buffer,
const uint8_t** vm_instructions_buffer,
const uint8_t** isolate_data_buffer,
const uint8_t** isolate_instructions_buffer) {
*vm_data_buffer = vm_snapshot_data_;
*vm_instructions_buffer = vm_snapshot_instructions_;
*isolate_data_buffer = isolate_snapshot_data_;
*isolate_instructions_buffer = isolate_snapshot_instructions_;
}
private:
void* library_;
const uint8_t* vm_snapshot_data_;
const uint8_t* vm_snapshot_instructions_;
const uint8_t* isolate_snapshot_data_;
const uint8_t* isolate_snapshot_instructions_;
};
static AppSnapshot* TryReadAppSnapshotDynamicLibrary(
DartUtils::MagicNumber magic_number,
const char* script_name,
const char** error) {
#if defined(USING_SIMULATOR)
*error = "running on a simulated architecture";
return nullptr;
#else
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_MACOS)
// On Linux and OSX, resolve the script path before passing into dlopen()
// since dlopen will not search the filesystem for paths like 'libtest.so'.
CStringUniquePtr absolute_path(realpath(script_name, nullptr));
script_name = absolute_path.get();
if (script_name == nullptr) {
const intptr_t err = errno;
const int kBufferSize = 1024;
char error_buf[kBufferSize];
Utils::StrError(err, error_buf, kBufferSize);
*error = Utils::SCreate("could not resolve path: %s", error_buf);
return nullptr;
}
#endif
void* library = Utils::LoadDynamicLibrary(script_name, error);
if (library == nullptr) {
#if defined(NATIVE_SHARED_OBJECT_FORMAT_ELF)
if (*error == nullptr && magic_number != DartUtils::kAotELFMagicNumber) {
*error = "not an ELF shared object";
}
#elif defined(NATIVE_SHARED_OBJECT_FORMAT_MACHO)
if (*error == nullptr &&
magic_number != DartUtils::kAotMachO32MagicNumber &&
magic_number != DartUtils::kAotMachO64MagicNumber) {
*error = "not a Mach-O shared object";
}
#endif
if (*error == nullptr) {
*error = "unknown failure loading dynamic library (wrong format?)";
}
return nullptr;
}
const uint8_t* vm_data_buffer = reinterpret_cast<const uint8_t*>(
Utils::ResolveSymbolInDynamicLibrary(library, kVmSnapshotDataCSymbol));
const uint8_t* vm_instructions_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kVmSnapshotInstructionsCSymbol));
const uint8_t* isolate_data_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kIsolateSnapshotDataCSymbol));
if (isolate_data_buffer == nullptr) {
FATAL("Failed to resolve symbol '%s'\n", kIsolateSnapshotDataCSymbol);
}
const uint8_t* isolate_instructions_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kIsolateSnapshotInstructionsCSymbol));
if (isolate_instructions_buffer == nullptr) {
FATAL("Failed to resolve symbol '%s'\n",
kIsolateSnapshotInstructionsCSymbol);
}
return new DylibAppSnapshot(magic_number, library, vm_data_buffer,
vm_instructions_buffer, isolate_data_buffer,
isolate_instructions_buffer);
#endif // defined(USING_SIMULATOR)
}
class ElfAppSnapshot : public AppSnapshot {
public:
ElfAppSnapshot(Dart_LoadedElf* elf,
@@ -184,6 +298,18 @@ static AppSnapshot* TryReadAppSnapshotElf(
uint64_t file_offset,
bool force_load_elf_from_memory = false) {
const char* error = nullptr;
#if defined(NATIVE_SHARED_OBJECT_FORMAT_ELF)
if (file_offset == 0 && !force_load_elf_from_memory) {
// The load as a dynamic library should succeed, since this is a platform
// that natively understands ELF.
if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
DartUtils::kAotELFMagicNumber, script_name, &error)) {
return snapshot;
}
Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
return nullptr;
}
#endif
const uint8_t *vm_data_buffer = nullptr, *vm_instructions_buffer = nullptr,
*isolate_data_buffer = nullptr,
*isolate_instructions_buffer = nullptr;
@@ -220,7 +346,8 @@ static AppSnapshot* TryReadAppSnapshotElf(
AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
const char* container_path) {
// Ensure file is actually MachO-formatted.
if (!IsMachOFormattedBinary(container_path)) {
DartUtils::MagicNumber magic_number;
if (!IsMachOFormattedBinary(container_path, &magic_number)) {
Syslog::PrintErr("Expected a Mach-O binary.\n");
return nullptr;
}
@@ -235,17 +362,19 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
// as the 32-bit header, just with an extra field for alignment, so we can
// safely load a 32-bit header to get all the information we need.
mach_o::mach_header header;
file->ReadFully(&header, sizeof(header));
if (header.magic == mach_o::MH_CIGAM || header.magic == mach_o::MH_CIGAM_64) {
Syslog::PrintErr(
"Expected a host endian header but found a byte-swapped header.\n");
if (!file->ReadFully(&header, sizeof(header))) {
Syslog::PrintErr("Could not read a complete Mach-O header.\n");
return nullptr;
}
if (header.magic == mach_o::MH_MAGIC_64) {
// Set the file position as if we had read a 64-bit header.
file->SetPosition(sizeof(mach_o::mach_header_64));
auto const bitsize = DartUtils::MagicNumberBitSize(magic_number);
if (bitsize == 64) {
// The load commands start immediately after the full header.
if (!file->SetPosition(sizeof(mach_o::mach_header_64))) {
Syslog::PrintErr("Could not read a complete Mach-O 64-bit header.\n");
}
} else {
ASSERT_EQUAL(bitsize, 32);
}
// Now we search through the load commands to find our snapshot note, which
@@ -400,108 +529,29 @@ AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf(
return TryReadAppSnapshotElf(container_path, appended_offset);
}
class DylibAppSnapshot : public AppSnapshot {
public:
DylibAppSnapshot(void* library,
const uint8_t* vm_snapshot_data,
const uint8_t* vm_snapshot_instructions,
const uint8_t* isolate_snapshot_data,
const uint8_t* isolate_snapshot_instructions)
: AppSnapshot(DartUtils::kAotELFMagicNumber),
library_(library),
vm_snapshot_data_(vm_snapshot_data),
vm_snapshot_instructions_(vm_snapshot_instructions),
isolate_snapshot_data_(isolate_snapshot_data),
isolate_snapshot_instructions_(isolate_snapshot_instructions) {}
~DylibAppSnapshot() { Utils::UnloadDynamicLibrary(library_); }
void SetBuffers(const uint8_t** vm_data_buffer,
const uint8_t** vm_instructions_buffer,
const uint8_t** isolate_data_buffer,
const uint8_t** isolate_instructions_buffer) {
*vm_data_buffer = vm_snapshot_data_;
*vm_instructions_buffer = vm_snapshot_instructions_;
*isolate_data_buffer = isolate_snapshot_data_;
*isolate_instructions_buffer = isolate_snapshot_instructions_;
}
private:
void* library_;
const uint8_t* vm_snapshot_data_;
const uint8_t* vm_snapshot_instructions_;
const uint8_t* isolate_snapshot_data_;
const uint8_t* isolate_snapshot_instructions_;
};
static AppSnapshot* TryReadAppSnapshotDynamicLibrary(const char* script_name) {
void* library = Utils::LoadDynamicLibrary(script_name);
if (library == nullptr) {
return nullptr;
}
const uint8_t* vm_data_buffer = reinterpret_cast<const uint8_t*>(
Utils::ResolveSymbolInDynamicLibrary(library, kVmSnapshotDataCSymbol));
const uint8_t* vm_instructions_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kVmSnapshotInstructionsCSymbol));
const uint8_t* isolate_data_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kIsolateSnapshotDataCSymbol));
if (isolate_data_buffer == nullptr) {
FATAL("Failed to resolve symbol '%s'\n", kIsolateSnapshotDataCSymbol);
}
const uint8_t* isolate_instructions_buffer =
reinterpret_cast<const uint8_t*>(Utils::ResolveSymbolInDynamicLibrary(
library, kIsolateSnapshotInstructionsCSymbol));
if (isolate_instructions_buffer == nullptr) {
FATAL("Failed to resolve symbol '%s'\n",
kIsolateSnapshotInstructionsCSymbol);
}
return new DylibAppSnapshot(library, vm_data_buffer, vm_instructions_buffer,
isolate_data_buffer, isolate_instructions_buffer);
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
#if defined(DART_TARGET_OS_MACOS)
bool Snapshot::IsMachOFormattedBinary(const char* filename) {
bool Snapshot::IsMachOFormattedBinary(const char* filename,
DartUtils::MagicNumber* out) {
File* file = File::Open(nullptr, filename, File::kRead);
if (file == nullptr) {
return false;
}
RefCntReleaseScope<File> rs(file);
const uint64_t size = file->Length();
// Parse the first 4 bytes and check the magic numbers.
uint32_t magic;
if (size < sizeof(magic)) {
uint8_t header[DartUtils::kMaxMagicNumberSize];
if (!file->ReadFully(&header, DartUtils::kMaxMagicNumberSize)) {
// The file isn't long enough to contain the magic bytes.
return false;
}
file->SetPosition(0);
file->ReadFully(&magic, sizeof(magic));
// Depending on the magic numbers, check that the size of the file is
// large enough for either a 32-bit or 64-bit header.
switch (magic) {
case mach_o::MH_MAGIC:
case mach_o::MH_CIGAM:
return size >= sizeof(mach_o::mach_header);
case mach_o::MH_MAGIC_64:
case mach_o::MH_CIGAM_64:
return size >= sizeof(mach_o::mach_header_64);
default:
// Not a Mach-O formatted file.
return false;
DartUtils::MagicNumber magic_number =
DartUtils::SniffForMagicNumber(header, sizeof(header));
if (out != nullptr) {
*out = magic_number;
}
return magic_number == DartUtils::kAotMachO32MagicNumber ||
magic_number == DartUtils::kAotMachO64MagicNumber;
}
#endif // defined(DART_TARGET_OS_MACOS)
#if defined(DART_TARGET_OS_WINDOWS)
bool Snapshot::IsPEFormattedBinary(const char* filename) {
@@ -597,23 +647,20 @@ AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
// For testing AOT with the standalone embedder, we also support loading
// from a dynamic library to simulate what happens on iOS.
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_MACOS)
// On Linux and OSX, resolve the script path before passing into dlopen()
// since dlopen will not search the filesystem for paths like 'libtest.so'.
CStringUniquePtr absolute_path(realpath(script_name, nullptr));
script_name = absolute_path.get();
#endif
AppSnapshot* snapshot = nullptr;
if (!force_load_elf_from_memory) {
snapshot = TryReadAppSnapshotDynamicLibrary(script_name);
if (snapshot != nullptr) {
const intptr_t file_offset = 0;
if (magic_number == DartUtils::kAotELFMagicNumber) {
return TryReadAppSnapshotElf(script_name, file_offset,
force_load_elf_from_memory);
} else {
// This is not a format for which we have a non-native loader, so
// attempt to load it as a native dynamic library.
const char* error = nullptr;
if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
magic_number, script_name, &error)) {
return snapshot;
}
Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
}
return TryReadAppSnapshotElf(script_name, /*file_offset=*/0,
force_load_elf_from_memory);
#else
if (magic_number == DartUtils::kAppJITMagicNumber) {
// Return the JIT snapshot.
+2 -3
View File
@@ -46,9 +46,8 @@ class Snapshot {
static void GenerateAppJIT(const char* snapshot_filename);
static void GenerateAppAOTAsAssembly(const char* snapshot_filename);
#if defined(DART_TARGET_OS_MACOS)
static bool IsMachOFormattedBinary(const char* container_path);
#endif
static bool IsMachOFormattedBinary(const char* container_path,
DartUtils::MagicNumber* out = nullptr);
#if defined(DART_TARGET_OS_WINDOWS)
static bool IsPEFormattedBinary(const char* container_path);
#endif
+9 -4
View File
@@ -8,12 +8,11 @@
#include "bin/test_utils.h"
#include "platform/assert.h"
#include "platform/globals.h"
#include "platform/mach_o.h"
#include "vm/unit_test.h"
namespace dart {
#if defined(DART_TARGET_OS_MACOS)
static const unsigned char kMachO32BitLittleEndianHeader[] = {
0xce, 0xfa, 0xed, 0xfe, 0x07, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -56,12 +55,19 @@ static const struct {
TEST_CASE(CanDetectMachOFiles) {
for (uintptr_t i = 0; i < ARRAY_SIZE(kTestcases); i++) {
const auto& testcase = kTestcases[i];
auto const magic =
reinterpret_cast<const mach_o::mach_header*>(testcase.contents)->magic;
const bool host_endian =
magic == mach_o::MH_MAGIC || magic == mach_o::MH_MAGIC_64;
auto* const file =
bin::DartUtils::OpenFile(testcase.filename, /*write=*/true);
bin::DartUtils::WriteFile(testcase.contents, testcase.contents_size, file);
bin::DartUtils::CloseFile(file);
EXPECT(bin::Snapshot::IsMachOFormattedBinary(testcase.filename));
// Only host-endian MachO files are recognized.
EXPECT_EQ(host_endian,
bin::Snapshot::IsMachOFormattedBinary(testcase.filename));
EXPECT(bin::File::Delete(nullptr, testcase.filename));
}
@@ -70,6 +76,5 @@ TEST_CASE(CanDetectMachOFiles) {
bin::test::GetFileName("runtime/bin/snapshot_utils_test.cc");
EXPECT(!bin::Snapshot::IsMachOFormattedBinary(kFilename));
}
#endif
} // namespace dart
+12
View File
@@ -203,6 +203,10 @@ template("library_for_all_configs") {
if (defined(invoker.extra_nonproduct_deps)) {
extra_nonproduct_deps += invoker.extra_nonproduct_deps
}
extra_precompiler_deps = []
if (defined(invoker.extra_precompiler_deps)) {
extra_precompiler_deps += invoker.extra_precompiler_deps
}
foreach(conf, _all_configs) {
target(invoker.target_type, "${target_name}${conf.suffix}") {
forward_variables_from(invoker,
@@ -241,6 +245,9 @@ template("library_for_all_configs") {
sources += snapshot_sources
}
} else {
if (conf.compiler) {
deps += extra_precompiler_deps
}
if (defined(snapshot_sources)) {
not_needed([ "snapshot_sources" ])
}
@@ -271,6 +278,10 @@ template("library_for_all_configs_with_compiler") {
if (defined(invoker.extra_nonproduct_deps)) {
extra_nonproduct_deps += invoker.extra_nonproduct_deps
}
extra_precompiler_deps = []
if (defined(invoker.extra_precompiler_deps)) {
extra_precompiler_deps += invoker.extra_precompiler_deps
}
foreach(conf, _all_configs) {
if (conf.compiler) {
target(invoker.target_type, "${target_name}${conf.suffix}") {
@@ -303,6 +314,7 @@ template("library_for_all_configs_with_compiler") {
sources += snapshot_sources
}
} else {
deps += extra_precompiler_deps
if (defined(snapshot_sources)) {
not_needed([ "snapshot_sources" ])
}
+48 -2
View File
@@ -3987,7 +3987,7 @@ DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id);
*
* The assembly should be compiled as a static or shared library and linked or
* loaded by the embedder. Running this snapshot requires a VM compiled with
* DART_PRECOMPILED_SNAPSHOT. The kDartVmSnapshotData and
* DART_PRECOMPILED_RUNTIME. The kDartVmSnapshotData and
* kDartVmSnapshotInstructions should be passed to Dart_Initialize. The
* kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions should be
* passed to Dart_CreateIsolateGroup.
@@ -4027,7 +4027,7 @@ Dart_CreateAppAOTSnapshotAsAssemblies(
* - _kDartIsolateSnapshotInstructions
*
* The shared library should be dynamically loaded by the embedder.
* Running this snapshot requires a VM compiled with DART_PRECOMPILED_SNAPSHOT.
* Running this snapshot requires a VM compiled with DART_PRECOMPILED_RUNTIME.
* The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to
* Dart_Initialize. The kDartIsolateSnapshotData and
* kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate.
@@ -4054,6 +4054,52 @@ Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback,
Dart_StreamingWriteCallback write_callback,
Dart_StreamingCloseCallback close_callback);
typedef enum {
Dart_AotBinaryFormat_Elf = 0,
Dart_AotBinaryFormat_Assembly = 1,
Dart_AotBinaryFormat_MachO_Dylib = 2,
} Dart_AotBinaryFormat;
/**
* Creates a precompiled snapshot.
* - A root library must have been loaded.
* - Dart_Precompile must have been called.
*
* Outputs a snapshot in the specified binary format defining the symbols
* - _kDartVmSnapshotData
* - _kDartVmSnapshotInstructions
* - _kDartIsolateSnapshotData
* - _kDartIsolateSnapshotInstructions
*
* The shared library should be dynamically loaded by the embedder.
* Running this snapshot requires a VM compiled with DART_PRECOMPILED_RUNTIME.
* The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to
* Dart_Initialize. The kDartIsolateSnapshotData and
* kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate.
*
* The callback will be invoked one or more times to provide the binary output.
*
* If stripped is true, then the binary output will not include DWARF
* debugging sections.
*
* If debug_callback_data is provided, debug_callback_data will be used with
* the callback to provide separate debugging information.
*
* The identifier should be an appropriate string for identifying the resulting
* dynamic library. For example, the identifier is used in ID_DYLIB and
* CODE_SIGNATURE load commands for Mach-O dynamic libraries and for DW_AT_name
* in the Dart progam's root DWARF compilation unit.
*
* \return A valid handle if no error occurs during the operation.
*/
DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle
Dart_CreateAppAOTSnapshotAsBinary(Dart_AotBinaryFormat format,
Dart_StreamingWriteCallback callback,
void* callback_data,
bool stripped,
void* debug_callback_data,
const char* identifier);
/**
* Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes
* kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does
+479 -4
View File
@@ -15,8 +15,48 @@ namespace mach_o {
typedef int cpu_type_t;
typedef int cpu_subtype_t;
// Mask for architecture variant bits.
static constexpr cpu_type_t CPU_ARCH_MASK = 0xff000000;
// CPU with a 64-bit ABI.
static constexpr cpu_type_t CPU_ARCH_ABI64 = 0x01000000;
// Fallback for architectures without more specific constants (e.g.,
// architectures like RISCV that MacOS doesn't run on natively).
static constexpr cpu_type_t CPU_TYPE_ANY = -1;
static constexpr cpu_subtype_t CPU_SUBTYPE_ANY = -1;
// x86-family CPUs.
static constexpr cpu_type_t CPU_TYPE_X86 = 7;
static constexpr cpu_type_t CPU_TYPE_I386 = CPU_TYPE_X86;
static constexpr cpu_type_t CPU_TYPE_X86_64 = CPU_TYPE_X86 | CPU_ARCH_ABI64;
// x86-family CPU subtypes.
constexpr cpu_subtype_t CPU_SUBTYPE_INTEL(uint8_t f, cpu_subtype_t m) {
return f + (m << 4);
}
static constexpr cpu_subtype_t CPU_SUBTYPE_I386_ALL = CPU_SUBTYPE_INTEL(3, 0);
static constexpr cpu_subtype_t CPU_SUBTYPE_X86_ALL = CPU_SUBTYPE_I386_ALL;
static constexpr cpu_subtype_t CPU_SUBTYPE_X86_64_ALL = CPU_SUBTYPE_I386_ALL;
// ARM-family CPUs.
static constexpr cpu_type_t CPU_TYPE_ARM = 12;
static constexpr cpu_type_t CPU_TYPE_ARM64 = CPU_TYPE_ARM | CPU_ARCH_ABI64;
// ARM-family CPU subtypes.
static constexpr cpu_type_t CPU_SUBTYPE_ARM_ALL = 0;
static constexpr cpu_type_t CPU_SUBTYPE_ARM64_ALL = CPU_SUBTYPE_ARM_ALL;
typedef int vm_prot_t;
static constexpr vm_prot_t VM_PROT_NONE = 0x00;
static constexpr vm_prot_t VM_PROT_READ = 0x01;
static constexpr vm_prot_t VM_PROT_WRITE = 0x02;
static constexpr vm_prot_t VM_PROT_EXECUTE = 0x04;
static constexpr vm_prot_t VM_PROT_DEFAULT = (VM_PROT_READ | VM_PROT_WRITE);
static constexpr vm_prot_t VM_PROT_ALL =
(VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
struct mach_header {
uint32_t magic;
cpu_type_t cputype;
@@ -44,18 +84,453 @@ struct mach_header_64 {
static constexpr uint32_t MH_MAGIC_64 = 0xfeedfacf;
static constexpr uint32_t MH_CIGAM_64 = 0xcffaedfe;
// Filetypes for the Mach-O header.
// A relocatable object file (e.g., an executable).
static constexpr uint32_t MH_OBJECT = 0x1;
// A dynamically bound shared library.
static constexpr uint32_t MH_DYLIB = 0x6;
// An object file that only contains debugging information.
static constexpr uint32_t MH_DSYM = 0xa;
// Flag values for the Mach-O header.
// The object file has no undefined references.
static constexpr uint32_t MH_NOUNDEFS = 0x1;
// The object file is an appropriate input for the dynamic linker
// and cannot be statically link edited again.
static constexpr uint32_t MH_DYLDLINK = 0x4;
// The object file does not re-export any of its input dynamic
// libraries.
static constexpr uint32_t MH_NO_REEXPORTED_DYLIBS = 0x100000;
struct load_command {
// The tag that specifies the load command for the following
// bytes. One of the LC_* constants below.
uint32_t cmd;
// The total size of the load command, including cmd and cmdsize.
uint32_t cmdsize;
};
// The description of the LC_* constants are followed by the name of
// the specific C structure describing their contents in parentheses.
// A portion of the file that is mapped into memory when the
// object file is loaded. (segment_command)
static constexpr uint32_t LC_SEGMENT = 0x1;
// The static symbol table. (symtab_command)
static constexpr uint32_t LC_SYMTAB = 0x2;
// The dynamic symbol table. (dysymtab_command)
static constexpr uint32_t LC_DYSYMTAB = 0xb;
// A dynamic library that must be loaded to use this object file.
// (dylib_command)
static constexpr uint32_t LC_LOAD_DYLIB = 0xc;
// The identifier for this dynamic library (for MH_DYLIB files).
// (dylib_command)
static constexpr uint32_t LC_ID_DYLIB = 0xd;
// A 64-bit segment. (segment_command_64)
static constexpr uint32_t LC_SEGMENT_64 = 0x19;
// The UUID, used as a build identifier. (uuid_command)
static constexpr uint32_t LC_UUID = 0x1b;
// The code signature which protects the preceding portion of the object file.
// Must be the last contents in the object file. (linkedit_data_command)
static constexpr uint32_t LC_CODE_SIGNATURE = 0x1d;
// An arbitrary piece of data not specified by the Mach-O format. (note_command)
static constexpr uint32_t LC_NOTE = 0x31;
struct note_command {
uint32_t cmd;
// The target platform and minimum and target OS versions for this object file.
// (build_version_command)
static constexpr uint32_t LC_BUILD_VERSION = 0x32;
struct segment_command {
uint32_t cmd; // LC_SEGMENT
uint32_t cmdsize;
char data_owner[16];
uint64_t offset;
// The name of the segment. Must be unique within a given object file.
char segname[16];
// The starting virtual address and the size of the segment in memory.
uint32_t vmaddr;
uint32_t vmsize;
// The starting file offset and size of the segment in the object file.
// The file size and memory size of the segment may be different, for
// example, if the segment contains zerofill sections.
uint32_t fileoff;
uint32_t filesize;
// The maximum memory protection possible for this segment.
vm_prot_t maxprot;
// The initial memory protection for this segment once loaded.
vm_prot_t initprot;
// The number of sections in the variable-length payload of this load command.
uint32_t nsects;
//
uint32_t flags;
// section_command[]
};
// Contains the same fields as segment_command, but the starting memory
// address and size and the file offset and size are 64-bit fields.
struct segment_command_64 {
uint32_t cmd; // LC_SEGMENT_64
uint32_t cmdsize;
char segname[16];
uint64_t vmaddr;
uint64_t vmsize;
uint64_t fileoff;
uint64_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
// section_command_64[]
};
struct section {
char sectname[16];
char segname[16];
uint32_t addr;
uint32_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
struct section_64 {
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
uint32_t reserved3;
};
static constexpr uint32_t SECTION_TYPE = 0x000000ff;
static constexpr uint32_t SECTION_ATTRIBUTES = 0xffffff00;
// Creates section flags from the type and attributes.
constexpr uint32_t SectionFlags(intptr_t type, intptr_t attributes) {
// Note that the S_* attribute values below do not need shifting.
return (attributes & SECTION_ATTRIBUTES) | (type & SECTION_TYPE);
}
// Section types.
static constexpr uint32_t S_REGULAR = 0x0;
static constexpr uint32_t S_ZEROFILL = 0x1;
static constexpr uint32_t S_GB_ZEROFILL = 0xc;
// Section attributes. Note that these values do not need shifting when
// combining with a type and so the type bits are always 0.
static constexpr uint32_t S_NO_ATTRIBUTES = 0;
// The section only contains instructions.
static constexpr uint32_t S_ATTR_PURE_INSTRUCTIONS = 0x80000000;
// The section only contains information needed for debugging.
// No symbols should refer to this section and it must have type S_REGULAR.
static constexpr uint32_t S_ATTR_DEBUG = 0x02000000;
// The section contains some instructions. Should be set if
// S_ATTR_PURE_INSTRUCTIONS is also set.
static constexpr uint32_t S_ATTR_SOME_INSTRUCTIONS = 0x00000400;
// Special segment and section names used by Mach-O files. Only the
// ones used in our Mach-O writer are listed.
// Segment and section names for the text segment, which also contains
// constant data.
static constexpr char SEG_TEXT[] = "__TEXT";
static constexpr char SECT_TEXT[] = "__text";
static constexpr char SECT_CONST[] = "__const";
// Segment and section names for the data segment, which contains
// non-constant data (like the BSS section).
static constexpr char SEG_DATA[] = "__DATA";
static constexpr char SECT_BSS[] = "__bss";
// Segment and section names for the DWARF segment.
static constexpr char SEG_DWARF[] = "__DWARF";
static constexpr char SECT_DEBUG_LINE[] = "__debug_line";
static constexpr char SECT_DEBUG_INFO[] = "__debug_info";
static constexpr char SECT_DEBUG_ABBREV[] = "__debug_abbrev";
// Segment name for the linkedit segment. Does not contain sections but rather
// the non-header contents for other non-segment link commands like the symbol
// table and code signature.
static constexpr char SEG_LINKEDIT[] = "__LINKEDIT";
struct symtab_command {
uint32_t cmd; // LC_SYMTAB
uint32_t cmdsize;
uint32_t symoff; // The offset of the symbol table data in the object file.
uint32_t nsyms; // The number of symbols in the symbol table data.
uint32_t stroff; // The offset of the string table for the symbol table.
uint32_t strsize; // The size of the string table in bytes.
};
// The structure used for symbols in the symbol table.
struct nlist {
uint32_t n_idx; // The index of the symbol name in the string table.
uint8_t n_type; // The type of the syble (see below).
uint8_t n_sect; // For section symbols, the section that owns this symbol.
uint16_t n_desc; // Interpreted based on the type of the symbol.
// This is normally defined as a uword, but it must match the target
// architecture's bitsize, not the host.
#if defined(TARGET_ARCH_IS_32_BIT)
uint32_t n_value;
#else
uint64_t n_value;
#endif
};
// The "section" for symbols not belonging to a specific section.
static constexpr uint8_t NO_SECT = 0;
// Masks for n_type.
// If any bits in (n_type & N_STAB) are set, then the symbol is
// a symbolic debugging symbol and so n_type is a specific constant.
static constexpr uint8_t N_STAB = 0xe0;
// Otherwise, n_type is a bitfield described by the following masks:
// The private external symbol bit.
static constexpr uint8_t N_PEXT = 0x10;
// A mask for the actual type of the symbol.
static constexpr uint8_t N_TYPE = 0xe;
// The external symbol bit.
static constexpr uint8_t N_EXT = 0x1;
// Values for the N_TYPE bits when no bits in N_STAB are set.
// An undefined symbol. (n_sect == NO_SECT)
static constexpr uint8_t N_UNDEF = 0x0;
// A symbol to an absolute offset in the Mach-O file. (n_sect == NO_SECT)
static constexpr uint8_t N_ABS = 0x2;
// A symbol defined in a specific section (load command index in n_sect).
static constexpr uint8_t N_SECT = 0xe;
// Values for the N_TYPE bits that set bits in N_STAB.
// A global symbol. (n_sect == NO_SECT, value = 0).
static constexpr uint8_t N_GSYM = 0x20;
// A function defined in a specific section.
static constexpr uint8_t N_FUN = 0x24;
// A static (object) symbol defined in a specific section.
static constexpr uint8_t N_STSYM = 0x26;
// The start of a function symbol in a specific section.
static constexpr uint8_t N_BNSYM = 0x2e;
// The end of a function symbol in a specific section.
static constexpr uint8_t N_ENSYM = 0x4e;
// Values for n_desc.
// Indicates an alternate symbol definition for a symbol value that
// is already defined elsewhere.
static constexpr uint16_t N_ALT_ENTRY = 0x0200;
struct dysymtab_command {
uint32_t cmd; // LC_DYSYMTAB
uint32_t cmdsize;
// The initial fields pairs are offsets into the symbol table information
// in the linkedit segment. The first field is the symbol table index of
// the first corresponding symbol (not file offset) and the second field
// is the number of symbols starting at that index.
// The local symbols in the symbol table.
uint32_t ilocalsym;
uint32_t nlocalsym;
// The defined external symbols in the symbol table.
uint32_t iextdefsym;
uint32_t nextdefsym;
// The undefined external symbols in the symbol table.
uint32_t iundefsym;
uint32_t nundefsym;
// The remaining fields pairs are offsets into the linkedit segment.
// The first field is the file offset and the second field is the number
// of objects to read starting at that index.
//
// The Mach-O writer in the VM does not use these fields, so there's
// no need for further documentation (they are populated with 0 values).
uint32_t tocoff;
uint32_t ntoc;
uint32_t modtaboff;
uint32_t nmodtab;
uint32_t extrefsymoff;
uint32_t nextrefsyms;
uint32_t indirectsymoff;
uint32_t nindirectsyms;
uint32_t extreloff;
uint32_t nextrel;
uint32_t locreloff;
uint32_t nlocrel;
};
struct note_command {
uint32_t cmd; // LC_NOTE
uint32_t cmdsize;
// An identifier used to determine the owner of this note (e.g., to
// determine how to interpret the contents of the note.)
char data_owner[16];
// The file offset of the note contents.
uint64_t offset;
// The size of the note contents in bytes.
uint64_t size;
};
struct uuid_command {
uint32_t cmd; // LC_UUID
uint32_t cmdsize;
uint8_t uuid[16]; // The 128-bit UUID of this object file.
};
struct build_version_command {
uint32_t cmd; // LC_BUILD_VERSION
uint32_t cmdsize;
uint32_t platform; // See PLATFORM_* constants.
// minos and sdk are X.Y.Z versions encoded as a bitfield:
// From most to least significant:
// X : 16
// Y : 8
// Z : 8
uint32_t minos; // Minimum OS version.
uint32_t sdk; // Target OS version.
// The number of build_tool_version structs in the variable-length
// payload of this load command. For our purposes, always 0 and
// so there is no definition of the build_tool_version struct here.
uint32_t ntools;
};
// Values for platform.
static constexpr uint32_t PLATFORM_UNKNOWN = 0x0;
static constexpr uint32_t PLATFORM_ANY = 0xffffffff;
static constexpr uint32_t PLATFORM_MACOS = 0x1;
static constexpr uint32_t PLATFORM_IOS = 0x2;
union lc_str {
// The offset of the string in the load command contents.
uint32_t offset;
// We don't include the in-memory pointer alternative here.
};
struct dylib_info {
lc_str name;
// The timestamp the library was built and copied into user.
uint32_t timestamp;
// Version format is same as in build_version_command.
uint32_t current_version;
uint32_t compatibility_version;
};
struct dylib_command {
uint32_t cmd; // LC_LOAD_DYLIB and LC_ID_DYLIB among others
uint32_t cmdsize;
dylib_info dylib;
};
struct linkedit_data_command {
uint32_t cmd; // LC_CODE_SIGNATURE among others
uint32_t cmdsize;
// The file offset of the corresponding contents. (Note that this is
// _not_ the offset into the linkedit segment.)
uint32_t dataoff;
// The size of the contents in bytes.
uint32_t datasize;
};
// Magic numbers for code signature blobs.
static constexpr uint32_t CSMAGIC_CODEDIRECTORY = 0xfade0c02;
static constexpr uint32_t CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
// Types for code signature blobs.
static constexpr uint32_t CSSLOT_CODEDIRECTORY = 0;
// Code signature code directory flags.
static constexpr uint32_t CS_ADHOC = 0x00000002;
static constexpr uint32_t CS_LINKER_SIGNED = 0x00020000;
// Code signature hash types.
static constexpr uint8_t CS_HASHTYPE_SHA256 = 0x2;
// Code signature version numbers.
// The earliest version that can appear in a code signature.
static constexpr uint32_t CS_SUPPORTSNONE = 0x20001;
static constexpr uint32_t CS_SUPPORTSSCATTER = 0x20100;
static constexpr uint32_t CS_SUPPORTSTEAMID = 0x20200;
static constexpr uint32_t CS_SUPPORTSCODELIMIT64 = 0x20300;
static constexpr uint32_t CS_SUPPORTSEXECSEG = 0x20400;
struct cs_blob_index {
uint32_t type; // e.g., CSSLOT_CODEDIRECTORY
// the offset of the nested blob within the superblob
uint32_t offset;
};
struct cs_superblob {
uint32_t magic; // CSMAGIC_EMBEDDED_SIGNATURE
// The length of the superblob, which includes any nested blobs.
uint32_t length;
// The number of nested blobs in this blob.
uint32_t count;
// The blob indices for the nested blobs.
cs_blob_index index[];
// The variable length payload also contains the contents of the nested blobs
// after the blob indices. The blob indices are not aligned, and the data for
// each nested blob is 8-byte aligned.
};
struct cs_code_directory {
uint32_t magic; // CSMAGIC_CODEDIRECTORY
// The length of the code directory, including the identifier and hashes.
uint32_t length;
uint32_t version; // For us, CS_SUPPORTSEXECSEG above.
uint32_t flags; // For us, CS_ADHOC | CS_LINKED_SIGNED.
uint32_t hash_offset; // The file offset of the hashes.
uint32_t ident_offset; // The file offset of the identifier.
uint32_t num_special_slots; // Unused by us, so 0.
// The number of hashes (one for each page up to the code limit,
// including one for the final incomplete page if any).
uint32_t num_code_slots;
// The end of the file covered by this code directory (for us, the file
// offset of the superblob).
uint32_t code_limit;
// The size of each hash in the special and code slots.
uint8_t hash_size;
// The type of each hash in the special and code slots.
uint8_t hash_type;
uint8_t platform; // Unused by us, so 0.
uint8_t page_size; // log2(page size)
uint32_t spare2; // always 0.
uint32_t scatter_offset; // Unused by us, so 0.
uint32_t teamid_offset; // Unused by us, so 0.
uint32_t spare3; // always 0.
uint64_t code_limit_64; // Code limit if larger than 32 bits.
uint64_t exec_seg_base; // file offset of the executable segment
uint64_t exec_seg_limit; // file size of the executable segment
uint64_t exec_seg_flags; // For our purposes, always 0.
// Technically there can be more with later code signature versions,
// but the Mach-O writer doesn't output those in the ad-hoc linker
// signed signature.
// The variable length payload contains the identifier followed by
// the hashes in the special and code slots. The identifier data is
// 8-byte aligned (like blobs) and the hash data is 16-byte aligned.
};
#pragma pack(pop)
+18
View File
@@ -10,6 +10,7 @@
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID)
#include <dlfcn.h>
#include <libgen.h>
#elif defined(DART_HOST_OS_FUCHSIA)
#include <dlfcn.h>
#include <fuchsia/io/cpp/fidl.h>
@@ -398,4 +399,21 @@ void Utils::UnloadDynamicLibrary(void* library_handle, char** error) {
}
}
char* Utils::Basename(const char* path) {
#if defined(DART_HOST_OS_FUCHSIA) || defined(DART_HOST_OS_WINDOWS)
// Not handled for these operating systems.
return nullptr;
#else
if (path == nullptr) return nullptr;
char* const path_copy = Utils::StrDup(path);
char* result = basename(path_copy);
// The result may be in statically allocated memory, so copy.
result = Utils::StrDup(result);
// The result may point to a portion of the passed in string, so
// only free the copy after duplicating the result.
free(path_copy);
return result;
#endif
}
} // namespace dart
+13
View File
@@ -663,6 +663,11 @@ class Utils {
static void* LoadDynamicLibrary(const char* library_path,
bool search_dll_load_dir = false,
char** error = nullptr);
static void* LoadDynamicLibrary(const char* library_path,
char** error = nullptr) {
return LoadDynamicLibrary(library_path, /*search_dll_load_dir=*/false,
error);
}
// Resolve the given |symbol| within the library referenced by the
// given |library_handle|.
@@ -682,6 +687,14 @@ class Utils {
static void UnloadDynamicLibrary(void* library_handle,
char** error = nullptr);
// Returns the basename of the given path. The returned string is malloced
// and must be freed by the caller once no longer needed.
//
// If path is nullptr, returns nullptr.
//
// Returns nullptr if the operating system does not support this operation.
static char* Basename(const char* path);
#if defined(DART_HOST_OS_LINUX)
static bool IsWindowsSubsystemForLinux();
#endif
@@ -84,7 +84,7 @@ Future<void> testAOT(
if (useAsm) {
final assemblyPath = path.join(tempDir, 'test.S');
await run(genSnapshot, <String>[
await (disassemble ? runSilent : run)(genSnapshot, <String>[
'--snapshot-kind=app-aot-assembly',
'--assembly=$assemblyPath',
...commonSnapshotArgs,
@@ -92,7 +92,7 @@ Future<void> testAOT(
await assembleSnapshot(assemblyPath, snapshotPath);
} else {
await run(genSnapshot, <String>[
await (disassemble ? runSilent : run)(genSnapshot, <String>[
'--snapshot-kind=app-aot-elf',
'--elf=$snapshotPath',
...commonSnapshotArgs,
@@ -53,7 +53,7 @@ Future<void> main(List<String> args) async {
// Run the AOT compiler with the disassemble flags set.
final elfFile = path.join(tempDir, 'aot.snapshot');
await run(genSnapshot, <String>[
await runSilent(genSnapshot, <String>[
'--disassemble',
'--disassemble_stubs',
'--always_generate_trampolines_for_testing',
@@ -63,7 +63,7 @@ Future<void> main(List<String> args) async {
]);
// Run the AOT runtime with the disassemble flags set.
await run(dartPrecompiledRuntime, <String>[
await runSilent(dartPrecompiledRuntime, <String>[
'--disassemble',
'--disassemble_stubs',
elfFile,
@@ -71,6 +71,7 @@ main() {
"Dart_CreateAppAOTSnapshotAsAssembly",
"Dart_CreateAppAOTSnapshotAsElf",
"Dart_CreateAppAOTSnapshotAsElfs",
"Dart_CreateAppAOTSnapshotAsBinary",
"Dart_CreateAppJITSnapshotAsBlobs",
"Dart_CreateIsolateGroup",
"Dart_CreateIsolateGroupFromKernel",
@@ -57,6 +57,7 @@ Future<void> main(List<String> args) async {
]);
await checkElf(tempDir, scriptDill);
await checkMachO(tempDir, scriptDill);
await checkAssembly(tempDir, scriptDill);
});
}
@@ -67,145 +68,165 @@ const commonGenSnapshotArgs = <String>[
'--deterministic',
];
Future<void> checkElf(String tempDir, String scriptDill) async {
enum SnapshotType {
elf,
machoDylib,
assembly;
String get kindString {
switch (this) {
case elf:
return 'app-aot-elf';
case machoDylib:
return 'app-aot-macho-dylib';
case assembly:
return 'app-aot-assembly';
}
}
String get fileArgumentName {
switch (this) {
case elf:
return 'elf';
case machoDylib:
return 'macho';
case assembly:
return 'assembly';
}
}
DwarfContainer? fromFile(String filename) {
switch (this) {
case elf:
return Elf.fromFile(filename);
case machoDylib:
return MachO.fromFile(filename);
case assembly:
return Elf.fromFile(filename) ?? MachO.fromFile(filename);
}
}
@override
String toString() => name;
}
Future<void> createSnapshot(
String scriptDill,
SnapshotType snapshotType,
String finalPath, [
List<String> extraArgs = const [],
]) async {
String output = finalPath;
if (snapshotType == SnapshotType.assembly) {
output = path.withoutExtension(finalPath) + '.S';
}
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
...extraArgs,
'--snapshot-kind=${snapshotType.kindString}',
'--${snapshotType.fileArgumentName}=$output',
scriptDill,
]);
if (snapshotType == SnapshotType.assembly) {
await assembleSnapshot(output, finalPath);
}
}
Future<void> checkSnapshotType(
String tempDir,
String scriptDill,
SnapshotType snapshotType,
) 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 scriptUnobfuscatedSnapshot = path.join(
tempDir,
'unobfuscated-$snapshotType.so',
);
await createSnapshot(scriptDill, snapshotType, scriptUnobfuscatedSnapshot);
final unobfuscatedCase = TestCase(
scriptUnobfuscatedSnapshot,
Elf.fromFile(scriptUnobfuscatedSnapshot)!,
snapshotType.fromFile(scriptUnobfuscatedSnapshot)!,
);
final scriptObfuscatedOnlySnapshot = path.join(
tempDir,
'obfuscated-only-elf.so',
'obfuscated-only-$snapshotType.so',
);
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
await createSnapshot(scriptDill, snapshotType, scriptObfuscatedOnlySnapshot, [
'--obfuscate',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptObfuscatedOnlySnapshot',
scriptDill,
]);
final obfuscatedOnlyCase = TestCase(
scriptObfuscatedOnlySnapshot,
Elf.fromFile(scriptObfuscatedOnlySnapshot)!,
snapshotType.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)!,
);
// Don't compare to separate debugging information for assembled snapshots
// because the assembled code introduces a lot of local static symbols for
// relocations and so the two won't contain similar amounts of static symbols.
TestCase? obfuscatedCase;
TestCase? strippedCase;
if (snapshotType != SnapshotType.assembly) {
final scriptObfuscatedSnapshot = path.join(
tempDir,
'obfuscated-$snapshotType.so',
);
final scriptDebuggingInfo = path.join(
tempDir,
'obfuscated-debug-$snapshotType.so',
);
await createSnapshot(scriptDill, snapshotType, scriptObfuscatedSnapshot, [
'--obfuscate',
'--save-debugging-info=$scriptDebuggingInfo',
]);
obfuscatedCase = TestCase(
scriptObfuscatedSnapshot,
snapshotType.fromFile(scriptObfuscatedSnapshot)!,
snapshotType.fromFile(scriptDebuggingInfo)!,
);
final scriptStrippedSnapshot = path.join(
tempDir,
'obfuscated-stripped-elf.so',
);
final scriptSeparateDebuggingInfo = path.join(
tempDir,
'obfuscated-separate-debug-elf.so',
);
await run(genSnapshot, <String>[
...commonGenSnapshotArgs,
'--strip',
'--obfuscate',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptStrippedSnapshot',
'--save-debugging-info=$scriptSeparateDebuggingInfo',
scriptDill,
]);
final strippedCase = TestCase(
scriptStrippedSnapshot,
/*container=*/ null, // No static symbols in stripped snapshot.
Elf.fromFile(scriptSeparateDebuggingInfo)!,
);
final scriptStrippedSnapshot = path.join(
tempDir,
'obfuscated-stripped-$snapshotType.so',
);
final scriptSeparateDebuggingInfo = path.join(
tempDir,
'obfuscated-separate-debug-$snapshotType.so',
);
await createSnapshot(scriptDill, snapshotType, scriptStrippedSnapshot, [
'--strip',
'--obfuscate',
'--save-debugging-info=$scriptSeparateDebuggingInfo',
]);
strippedCase = TestCase(
scriptStrippedSnapshot,
/*container=*/ null, // No static symbols in stripped snapshot.
snapshotType.fromFile(scriptSeparateDebuggingInfo)!,
);
}
await checkCases(unobfuscatedCase, <TestCase>[
obfuscatedOnlyCase,
obfuscatedCase,
strippedCase,
if (obfuscatedCase != null) obfuscatedCase,
if (strippedCase != null) strippedCase,
]);
}
Future<void> checkElf(String tempDir, String scriptDill) async {
await checkSnapshotType(tempDir, scriptDill, SnapshotType.elf);
}
Future<void> checkMachO(String tempDir, String scriptDill) async {
await checkSnapshotType(tempDir, scriptDill, SnapshotType.machoDylib);
}
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]);
await checkSnapshotType(tempDir, scriptDill, SnapshotType.assembly);
}
class TestCase {
@@ -221,6 +242,12 @@ Future<void> checkCases(
List<TestCase> obfuscateds,
) async {
checkStaticSymbolTables(unobfuscated, obfuscateds);
if (!Platform.isMacOS && unobfuscated.container is! Elf) {
assert(unobfuscated.container is MachO);
// Don't try and run Mach-O snapshots on systems where it is not the native
// format because there is no MachOLoader in the runtime.
return;
}
await checkTraces(unobfuscated, obfuscateds);
}
@@ -35,8 +35,7 @@ Future<void> main() async {
'use_dwarf_stack_traces_flag_deferred_program.dart',
),
runNonDwarf,
runElf,
runAssembly,
[runElf, runAssembly],
);
}
@@ -104,10 +103,10 @@ typedef DwarfMap = Map<int, Dwarf>;
class DeferredElfState extends ElfState<DwarfMap> {
DeferredElfState(
super.snapshot,
super.debugInfo,
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo,
);
@override
@@ -163,19 +162,19 @@ Future<DeferredElfState> runElf(String tempDir, String scriptDill) async {
final snapshotDwarfMap = useSnapshotForDwarfPath(pathManifest).dwarfMap;
return DeferredElfState(
snapshotDwarfMap,
debugInfoDwarfMap,
output,
outputWithOppositeFlag,
snapshotDwarfMap,
debugInfoDwarfMap,
);
}
class DeferredAssemblyState extends AssemblyState<DwarfMap> {
DeferredAssemblyState(
super.snapshot,
super.debugInfo,
super.output,
super.outputWithOppositeFlag, [
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
super.singleArch,
super.multiArch,
]);
@@ -298,10 +297,10 @@ Future<DeferredAssemblyState?> runAssembly(
}
return DeferredAssemblyState(
snapshotDwarfMap,
debugInfoDwarfMap,
output,
outputWithOppositeFlag,
snapshotDwarfMap,
debugInfoDwarfMap,
singleArchSnapshotDwarfMap,
multiArchSnapshotDwarfMap,
);
@@ -46,67 +46,128 @@ class DwarfTestOutput {
DwarfTestOutput(this.trace, this.allocateObjectStart, this.allocateObjectEnd);
}
class NonDwarfState {
abstract class State {
final DwarfTestOutput output;
final DwarfTestOutput outputWithOppositeFlag;
NonDwarfState(this.output, this.outputWithOppositeFlag);
State(this.output, this.outputWithOppositeFlag);
}
class NonDwarfState extends State {
NonDwarfState(super.output, super.outputWithOppositeFlag);
void check() => expect(outputWithOppositeFlag.trace, equals(output.trace));
}
abstract class ElfState<T> {
abstract class DwarfState<T> extends State {
final T snapshot;
final T debugInfo;
final DwarfTestOutput output;
final DwarfTestOutput outputWithOppositeFlag;
ElfState(
DwarfState(
super.output,
super.outputWithOppositeFlag,
this.snapshot,
this.debugInfo,
this.output,
this.outputWithOppositeFlag,
);
String get description;
Future<void> check(Trace trace, T t);
Future<void> makeTests(Trace nonDwarfTrace) async {
test(
'Testing $description traces with separate debugging info',
() async => await check(nonDwarfTrace, debugInfo),
);
test(
'Testing $description traces with original snapshot',
() async => await check(nonDwarfTrace, snapshot),
);
}
}
abstract class AssemblyState<T> {
final T snapshot;
final T debugInfo;
final DwarfTestOutput output;
final DwarfTestOutput outputWithOppositeFlag;
abstract class ElfState<T> extends DwarfState<T> {
ElfState(
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo,
);
@override
String get description => 'ELF';
}
abstract class MultiArchDwarfState<T> extends DwarfState<T> {
final T? singleArch;
final T? multiArch;
AssemblyState(
this.snapshot,
this.debugInfo,
this.output,
this.outputWithOppositeFlag, [
MultiArchDwarfState(
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
this.singleArch,
this.multiArch,
]);
Future<void> check(Trace trace, T t);
@override
Future<void> makeTests(Trace nonDwarfTrace) async {
await super.makeTests(nonDwarfTrace);
test(
'Testing $description single-architecture universal binary',
() async {
expect(singleArch, isNotNull);
await check(nonDwarfTrace, singleArch!);
},
skip: skipUniversalBinary,
);
test(
'Testing $description multi-architecture universal binary',
() async {
expect(multiArch, isNotNull);
await check(nonDwarfTrace, multiArch!);
},
skip: skipUniversalBinary,
);
}
}
abstract class UniversalBinaryState<T> {
final T singleArch;
final T multiArch;
abstract class AssemblyState<T> extends MultiArchDwarfState<T> {
AssemblyState(
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
super.singleArch,
super.multiArch,
]);
UniversalBinaryState(this.singleArch, this.multiArch);
@override
String get description => 'assembly';
}
Future<void> checkSingleArch(Trace trace, AssemblyState assemblyState);
Future<void> checkMultiArch(Trace trace, AssemblyState assemblyState);
abstract class MachOState<T> extends MultiArchDwarfState<T> {
MachOState(
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
super.singleArch,
super.multiArch,
]);
@override
String get description => 'Mach-O';
}
Future<void> runTests<T>(
String tempPrefix,
String scriptPath,
Future<NonDwarfState> Function(String, String) runNonDwarf,
Future<ElfState<T>> Function(String, String) runElf,
Future<AssemblyState<T>?> Function(String, String) runAssembly,
Iterable<Future<DwarfState?> Function(String, String)> runDwarfs,
) async {
if (!isAOTRuntime) {
return; // Running in JIT: AOT binaries not available.
@@ -143,44 +204,17 @@ Future<void> runTests<T>(
]);
final nonDwarfState = await runNonDwarf(tempDir, scriptDill);
final elfState = await runElf(tempDir, scriptDill);
final assemblyState = await runAssembly(tempDir, scriptDill);
final dwarfStates = [
for (final f in runDwarfs) await f(tempDir, scriptDill),
];
test('Testing symbolic traces', nonDwarfState.check);
final nonDwarfTrace = nonDwarfState.output.trace;
test(
'Testing ELF traces with separate debugging info',
() async => await elfState.check(nonDwarfTrace, elfState.debugInfo),
);
test(
'Testing ELF traces with original snapshot',
() async => await elfState.check(nonDwarfTrace, elfState.snapshot),
);
test('Testing assembly traces with separate debugging info', () async {
expect(assemblyState, isNotNull);
await assemblyState!.check(nonDwarfTrace, assemblyState.debugInfo);
}, skip: skipAssembly);
test('Testing assembly traces with debug snapshot ', () async {
expect(assemblyState, isNotNull);
await assemblyState!.check(nonDwarfTrace, assemblyState.snapshot);
}, skip: skipAssembly);
test('Testing single-architecture universal binary', () async {
expect(assemblyState, isNotNull);
expect(assemblyState!.singleArch, isNotNull);
await assemblyState.check(nonDwarfTrace, assemblyState.singleArch!);
}, skip: skipUniversalBinary);
test('Testing multi-architecture universal binary', () async {
expect(assemblyState, isNotNull);
expect(assemblyState!.multiArch, isNotNull);
await assemblyState.check(nonDwarfTrace, assemblyState.multiArch!);
}, skip: skipUniversalBinary);
for (final dwarfState in dwarfStates.whereType<DwarfState>()) {
dwarfState.makeTests(nonDwarfTrace);
}
});
}
@@ -29,8 +29,13 @@ Future<void> main() async {
'use_dwarf_stack_traces_flag_program.dart',
),
runNonDwarf,
runElf,
runAssembly,
[
runElf,
// Only generate Mach-O on MacOS, since there is no MachOLoader
// to run the binary on platforms where that isn't the native format.
if (Platform.isMacOS) runMachODylib,
runAssembly,
],
);
}
@@ -64,10 +69,10 @@ Future<NonDwarfState> runNonDwarf(String tempDir, String scriptDill) async {
class DwarfElfState extends ElfState<Dwarf> {
DwarfElfState(
super.snapshot,
super.debugInfo,
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo,
);
@override
@@ -76,8 +81,9 @@ class DwarfElfState extends ElfState<Dwarf> {
}
Future<DwarfElfState> runElf(String tempDir, String scriptDill) async {
final snapshotPath = path.join(tempDir, 'dwarf.so');
final debugInfoPath = path.join(tempDir, 'debug_info.so');
print("Generating ELF snapshots");
final snapshotPath = path.join(tempDir, 'dwarf_elf.so');
final debugInfoPath = path.join(tempDir, 'debug_info_elf.so');
await run(genSnapshot, <String>[
'--dwarf-stack-traces-mode',
'--save-debugging-info=$debugInfoPath',
@@ -90,7 +96,7 @@ Future<DwarfElfState> runElf(String tempDir, String scriptDill) async {
final debugInfo = Dwarf.fromFile(debugInfoPath)!;
// Run the resulting Dwarf-AOT compiled script.
print("Generating ELF snapshot outputs");
final output = await runTestProgram(dartPrecompiledRuntime, <String>[
'--dwarf-stack-traces-mode',
snapshotPath,
@@ -101,15 +107,15 @@ Future<DwarfElfState> runElf(String tempDir, String scriptDill) async {
<String>['--no-dwarf-stack-traces-mode', snapshotPath, scriptDill],
);
return DwarfElfState(snapshot, debugInfo, output, outputWithOppositeFlag);
return DwarfElfState(output, outputWithOppositeFlag, snapshot, debugInfo);
}
class DwarfAssemblyState extends AssemblyState<Dwarf> {
DwarfAssemblyState(
super.snapshot,
super.debugInfo,
super.output,
super.outputWithOppositeFlag, [
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
super.singleArch,
super.multiArch,
]);
@@ -136,6 +142,7 @@ Future<DwarfAssemblyState?> runAssembly(
// We get a separate .dSYM bundle on MacOS.
var debugSnapshotPath = snapshotPath + (Platform.isMacOS ? '.dSYM' : '');
print("Generating assembly snapshots");
await run(genSnapshot, <String>[
// We test --dwarf-stack-traces-mode, not --dwarf-stack-traces, because
// the latter is a handler that sets the former and also may change
@@ -152,6 +159,7 @@ Future<DwarfAssemblyState?> runAssembly(
await assembleSnapshot(asmPath, snapshotPath, debug: true);
print("Generating assembly snapshot outputs");
// Run the resulting Dwarf-AOT compiled script.
final output = await runTestProgram(dartPrecompiledRuntime, <String>[
'--dwarf-stack-traces-mode',
@@ -182,6 +190,7 @@ Future<DwarfAssemblyState?> runAssembly(
emptyFiles[arch] = emptyPath;
}
print("Generating multi-arch assembly debugging information");
final singleArchSnapshotPath = path.join(tempDir, "ub-single");
await run(lipo, <String>[
debugSnapshotPath,
@@ -203,10 +212,98 @@ Future<DwarfAssemblyState?> runAssembly(
}
return DwarfAssemblyState(
snapshot,
debugInfo,
output,
outputWithOppositeFlag,
snapshot,
debugInfo,
singleArchSnapshot,
multiArchSnapshot,
);
}
class DwarfMachOState extends MachOState<Dwarf> {
DwarfMachOState(
super.output,
super.outputWithOppositeFlag,
super.snapshot,
super.debugInfo, [
super.singleArch,
super.multiArch,
]);
@override
Future<void> check(Trace trace, Dwarf dwarf) =>
compareTraces(trace, output, outputWithOppositeFlag, dwarf);
}
Future<DwarfMachOState> runMachODylib(String tempDir, String scriptDill) async {
print("Generating Mach-O snapshots");
final snapshotPath = path.join(tempDir, 'dwarf_macho_dylib.so');
final debugInfoPath = path.join(tempDir, 'debug_info_macho_dylib.so');
await run(genSnapshot, <String>[
'--dwarf-stack-traces-mode',
'--save-debugging-info=$debugInfoPath',
'--snapshot-kind=app-aot-macho-dylib',
'--macho=$snapshotPath',
scriptDill,
]);
final snapshot = Dwarf.fromFile(snapshotPath)!;
final debugInfo = Dwarf.fromFile(debugInfoPath)!;
// Run the resulting Dwarf-AOT compiled script.
print("Generating Mach-O snapshot outputs");
final output = await runTestProgram(dartPrecompiledRuntime, <String>[
'--dwarf-stack-traces-mode',
snapshotPath,
scriptDill,
]);
final outputWithOppositeFlag = await runTestProgram(
dartPrecompiledRuntime,
<String>['--no-dwarf-stack-traces-mode', snapshotPath, scriptDill],
);
Dwarf? singleArchSnapshot;
Dwarf? multiArchSnapshot;
if (skipUniversalBinary == false) {
// Create empty MachO files (just a header) for each of the possible
// architectures.
final emptyFiles = <String, String>{};
for (final arch in machOArchNames.values) {
// Don't create an empty file for the current architecture.
if (arch == dartNameForCurrentArchitecture) continue;
final contents = emptyMachOForArchitecture(arch)!;
final emptyPath = path.join(tempDir, "empty_${arch}.so");
await File(emptyPath).writeAsBytes(contents, flush: true);
emptyFiles[arch] = emptyPath;
}
print("Generating multi-arch Mach-O debugging information");
final singleArchSnapshotPath = path.join(tempDir, "ub-single");
await run(lipo, <String>[
debugInfoPath,
'-create',
'-output',
singleArchSnapshotPath,
]);
singleArchSnapshot = Dwarf.fromFile(singleArchSnapshotPath)!;
final multiArchSnapshotPath = path.join(tempDir, "ub-multiple");
await run(lipo, <String>[
...emptyFiles.values,
debugInfoPath,
'-create',
'-output',
multiArchSnapshotPath,
]);
multiArchSnapshot = Dwarf.fromFile(multiArchSnapshotPath)!;
}
return DwarfMachOState(
output,
outputWithOppositeFlag,
snapshot,
debugInfo,
singleArchSnapshot,
multiArchSnapshot,
);
@@ -248,8 +345,9 @@ Future<void> compareTraces(
checkTranslatedTrace(nonDwarfTrace, translatedDwarfTrace1);
// Since we compiled directly to ELF, there should be a DSO base address
// in the stack trace header and 'virt' markers in the stack frames.
// Since we compiled directly to a shared object, there should be a
// DSO base address in the stack trace header and 'virt' markers in
// the stack frames.
// The offsets of absolute addresses from their respective DSO base
// should be the same for both traces.
@@ -173,20 +173,23 @@ Future<void> stripSnapshot(
await run(strip, <String>['-o', strippedPath, snapshotPath]);
}
Future<ProcessResult> runHelper(String executable, List<String> args) async {
Future<ProcessResult> runHelper(
String executable,
List<String> args, {
bool printStdout = true,
bool printStderr = true,
}) async {
print('Running $executable ${args.join(' ')}');
final result = await Process.run(executable, args);
print('Subcommand terminated with exit code ${result.exitCode}.');
if (result.stdout.isNotEmpty) {
if (printStdout && result.stdout.isNotEmpty) {
print('Subcommand stdout:');
print(result.stdout);
}
if (result.exitCode != 0) {
if (result.stderr.isNotEmpty) {
print('Subcommand stderr:');
print(result.stderr);
}
if (printStderr && result.stderr.isNotEmpty) {
print('Subcommand stderr:');
print(result.stderr);
}
return result;
@@ -210,6 +213,19 @@ Future<void> run(String executable, List<String> args) async {
}
}
Future<void> runSilent(String executable, List<String> args) async {
final result = await runHelper(
executable,
args,
printStdout: false,
printStderr: false,
);
if (result.exitCode != 0) {
throw 'Command failed with unexpected exit code (was ${result.exitCode})';
}
}
Future<List<String>> runOutput(String executable, List<String> args) async {
final result = await runHelper(executable, args);
+4
View File
@@ -180,6 +180,10 @@ library_for_all_configs("libdart_vm") {
"//third_party/icu:icui18n",
"//third_party/icu:icuuc",
]
extra_precompiler_deps = [
# The Mach-O writer uses BoringSSL's SHA256 function for code signatures.
"//third_party/boringssl",
]
if (is_fuchsia) {
extra_deps += [
"$fuchsia_sdk/fidl/fuchsia.intl",
+64 -27
View File
@@ -31,6 +31,7 @@
#include "vm/isolate_reload.h"
#include "vm/kernel_isolate.h"
#include "vm/lockers.h"
#include "vm/mach_o.h"
#include "vm/message.h"
#include "vm/message_handler.h"
#include "vm/message_snapshot.h"
@@ -6429,20 +6430,16 @@ static constexpr intptr_t kAssemblyInitialSize = 512 * KB;
static constexpr intptr_t kInitialSize = 2 * MB;
static constexpr intptr_t kInitialDebugSize = 1 * MB;
enum class AOTSnapshotType {
kAssembly,
kElf,
};
static void CreateAppAOTSnapshot(
Dart_StreamingWriteCallback callback,
void* callback_data,
bool strip,
AOTSnapshotType type,
Dart_AotBinaryFormat format,
void* debug_callback_data,
GrowableArray<LoadingUnitSerializationData*>* units,
LoadingUnitSerializationData* unit,
uint32_t program_hash) {
uint32_t program_hash,
const char* identifier) {
Thread* T = Thread::Current();
NOT_IN_PRODUCT(TimelineBeginEndScope tbes2(T, Timeline::GetIsolateStream(),
@@ -6465,14 +6462,21 @@ static void CreateAppAOTSnapshot(
Dwarf* debug_dwarf = nullptr;
SharedObjectWriter* debug_so = nullptr;
if (generate_debug) {
debug_dwarf = new (Z) Dwarf(Z, deobfuscation_trie);
debug_so = new (Z) ElfWriter(
Z, &debug_stream, SharedObjectWriter::Type::DebugInfo, debug_dwarf);
debug_dwarf = new (Z) Dwarf(Z, deobfuscation_trie, identifier);
if (format == Dart_AotBinaryFormat_MachO_Dylib) {
debug_so = new (Z)
MachOWriter(Z, &debug_stream, SharedObjectWriter::Type::DebugInfo,
identifier, debug_dwarf);
} else {
debug_so = new (Z) ElfWriter(
Z, &debug_stream, SharedObjectWriter::Type::DebugInfo, debug_dwarf);
}
}
StreamingWriteStream output_stream(
type == AOTSnapshotType::kAssembly ? kAssemblyInitialSize : kInitialSize,
callback, callback_data);
StreamingWriteStream output_stream(format == Dart_AotBinaryFormat_Assembly
? kAssemblyInitialSize
: kInitialSize,
callback, callback_data);
auto const use_output_writer = [&](ImageWriter* image_writer) {
FullSnapshotWriter writer(Snapshot::kFullAOT, &vm_snapshot_data,
@@ -6487,16 +6491,21 @@ static void CreateAppAOTSnapshot(
image_writer->Finalize();
};
Dwarf* const dwarf = (type == AOTSnapshotType::kAssembly || strip) ? nullptr
: generate_debug ? debug_dwarf
: new (Z) Dwarf(Z, deobfuscation_trie);
Dwarf* const dwarf =
(format == Dart_AotBinaryFormat_Assembly || strip) ? nullptr
: generate_debug ? debug_dwarf
: new (Z) Dwarf(Z, deobfuscation_trie, identifier);
SharedObjectWriter* so = nullptr;
if (type == AOTSnapshotType::kElf) {
if (format == Dart_AotBinaryFormat_Elf) {
so = new (Z)
ElfWriter(Z, &output_stream, SharedObjectWriter::Type::Snapshot, dwarf);
} else if (format == Dart_AotBinaryFormat_MachO_Dylib) {
so = new (Z)
MachOWriter(Z, &output_stream, SharedObjectWriter::Type::Snapshot,
identifier, dwarf);
}
if (type == AOTSnapshotType::kAssembly) {
if (format == Dart_AotBinaryFormat_Assembly) {
ASSERT(so == nullptr);
AssemblyImageWriter assembly_writer(T, &output_stream, deobfuscation_trie,
strip, debug_so);
@@ -6512,7 +6521,7 @@ static void CreateAppAOTSnapshot(
static void Split(Dart_CreateLoadingUnitCallback next_callback,
void* next_callback_data,
bool strip,
AOTSnapshotType type,
Dart_AotBinaryFormat format,
Dart_StreamingWriteCallback write_callback,
Dart_StreamingCloseCallback close_callback) {
Thread* T = Thread::Current();
@@ -6544,9 +6553,9 @@ static void Split(Dart_CreateLoadingUnitCallback next_callback,
next_callback(next_callback_data, id, &write_callback_data,
&write_debug_callback_data);
}
CreateAppAOTSnapshot(write_callback, write_callback_data, strip, type,
CreateAppAOTSnapshot(write_callback, write_callback_data, strip, format,
write_debug_callback_data, &data, data[id],
program_hash);
program_hash, /*identifier=*/nullptr);
{
TransitionVMToNative transition(T);
close_callback(write_callback_data);
@@ -6579,8 +6588,8 @@ Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback,
T->isolate_group()->object_store()->set_loading_units(Object::null_array());
CreateAppAOTSnapshot(callback, callback_data, strip,
AOTSnapshotType::kAssembly, debug_callback_data, nullptr,
nullptr, 0);
Dart_AotBinaryFormat_Assembly, debug_callback_data,
nullptr, nullptr, 0, /*identifier=*/nullptr);
return Api::Success();
#endif
@@ -6606,7 +6615,7 @@ DART_EXPORT Dart_Handle Dart_CreateAppAOTSnapshotAsAssemblies(
CHECK_NULL(write_callback);
CHECK_NULL(close_callback);
Split(next_callback, next_callback_data, strip, AOTSnapshotType::kAssembly,
Split(next_callback, next_callback_data, strip, Dart_AotBinaryFormat_Assembly,
write_callback, close_callback);
return Api::Success();
@@ -6660,8 +6669,9 @@ Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback,
// Mark as not split.
T->isolate_group()->object_store()->set_loading_units(Object::null_array());
CreateAppAOTSnapshot(callback, callback_data, strip, AOTSnapshotType::kElf,
debug_callback_data, nullptr, nullptr, 0);
CreateAppAOTSnapshot(callback, callback_data, strip, Dart_AotBinaryFormat_Elf,
debug_callback_data, nullptr, nullptr, 0,
/*identifier=*/nullptr);
return Api::Success();
#endif
@@ -6685,13 +6695,40 @@ Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback,
CHECK_NULL(write_callback);
CHECK_NULL(close_callback);
Split(next_callback, next_callback_data, strip, AOTSnapshotType::kElf,
Split(next_callback, next_callback_data, strip, Dart_AotBinaryFormat_Elf,
write_callback, close_callback);
return Api::Success();
#endif
}
DART_EXPORT Dart_Handle
Dart_CreateAppAOTSnapshotAsBinary(Dart_AotBinaryFormat format,
Dart_StreamingWriteCallback callback,
void* callback_data,
bool strip,
void* debug_callback_data,
const char* identifier) {
#if defined(TARGET_ARCH_IA32)
return Api::NewError("AOT compilation is not supported on IA32.");
#elif !defined(DART_PRECOMPILER)
return Api::NewError(
"This VM was built without support for AOT compilation.");
#else
DARTSCOPE(Thread::Current());
API_TIMELINE_DURATION(T);
CHECK_NULL(callback);
// Mark as not split.
T->isolate_group()->object_store()->set_loading_units(Object::null_array());
CreateAppAOTSnapshot(callback, callback_data, strip, format,
debug_callback_data, nullptr, nullptr, 0, identifier);
return Api::Success();
#endif
}
DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id) {
#if defined(TARGET_ARCH_IA32)
return Api::NewError("AOT compilation is not supported on IA32.");
+14 -5
View File
@@ -92,8 +92,20 @@ class InliningNode : public ZoneAllocated {
InliningNode* children_next;
};
Dwarf::Dwarf(Zone* zone, const Trie<const char>* deobfuscation_trie)
static const char* GetRootLibraryName(Zone* zone) {
const auto& root_library = Library::Handle(
zone, IsolateGroup::Current()->object_store()->root_library());
const auto& root_uri = String::Handle(zone, root_library.url());
return root_uri.ToCString();
}
Dwarf::Dwarf(Zone* zone,
const Trie<const char>* deobfuscation_trie,
const char* compilation_unit_name)
: zone_(zone),
compilation_unit_name_(compilation_unit_name != nullptr
? compilation_unit_name
: GetRootLibraryName(zone)),
deobfuscation_trie_(deobfuscation_trie),
codes_(zone, 1024),
code_to_label_(zone),
@@ -274,10 +286,7 @@ void Dwarf::WriteDebugInfo(DwarfWriteStream* stream) {
// compilation unit. Note we write attributes in the same order we declared
// them in our abbreviation above in WriteAbbreviations.
stream->uleb128(kCompilationUnit);
const Library& root_library = Library::Handle(
zone_, IsolateGroup::Current()->object_store()->root_library());
const String& root_uri = String::Handle(zone_, root_library.url());
stream->string(root_uri.ToCString()); // DW_AT_name
stream->string(compilation_unit_name_); // DW_AT_name
const char* producer = zone_->PrintToString("Dart %s\n", Version::String());
stream->string(producer); // DW_AT_producer
stream->string(""); // DW_AT_comp_dir
+7 -1
View File
@@ -155,7 +155,12 @@ class DwarfWriteStream : public ValueObject {
class Dwarf : public ZoneAllocated {
public:
explicit Dwarf(Zone* zone, const Trie<const char>* deobfuscation_trie);
// The compilation unit name is used as the DW_AT_name for the
// Dart program's compilation unit. If nullptr, then the name of
// the root library is used instead.
Dwarf(Zone* zone,
const Trie<const char>* deobfuscation_trie,
const char* compilation_unit_name = nullptr);
const ZoneGrowableArray<const Code*>& codes() const { return codes_; }
@@ -245,6 +250,7 @@ class Dwarf : public ZoneAllocated {
LineNumberProgramWriter* writer);
Zone* const zone_;
const char* const compilation_unit_name_;
const Trie<const char>* const deobfuscation_trie_;
ZoneGrowableArray<const Code*> codes_;
DwarfCodeMap<intptr_t> code_to_label_;
+1 -1
View File
@@ -20,7 +20,7 @@ namespace dart {
// The max page size on all supported architectures. Used to determine
// the alignment of load segments, so that they are guaranteed page-aligned,
// and no ELF section or segment should have a larger alignment.
// and no shared object section or segment should have a larger alignment.
#if defined(DART_TARGET_OS_LINUX) && defined(TARGET_ARCH_ARM64)
// Some Linux distributions on ARM64 select 64 KB page size.
// Follow LLVM (https://reviews.llvm.org/D25079) and set maximum page size
+22 -4
View File
@@ -7,6 +7,7 @@
#include "include/dart_api.h"
#include "platform/assert.h"
#include "platform/elf.h"
#include "platform/mach_o.h"
#include "vm/bss_relocs.h"
#include "vm/class_id.h"
#include "vm/compiler/runtime_api.h"
@@ -106,6 +107,8 @@ const uint8_t* Image::build_id() const {
if (compiled_to_elf()) {
auto* const note = reinterpret_cast<const elf::Note*>(start);
return note->data + note->name_size;
} else if (compiled_to_macho()) {
return reinterpret_cast<const mach_o::uuid_command*>(start)->uuid;
}
#endif
return nullptr;
@@ -119,6 +122,8 @@ intptr_t Image::build_id_length() const {
if (compiled_to_elf()) {
auto const note = reinterpret_cast<const elf::Note*>(start);
return note->description_size;
} else if (compiled_to_macho()) {
return sizeof(mach_o::uuid_command::uuid);
}
#endif
return 0;
@@ -148,10 +153,10 @@ const uint8_t* Image::shared_object_start() const {
bool Image::compiled_to_elf() const {
#if defined(DART_PRECOMPILED_RUNTIME)
if (!compiled_to_shared_object()) return false;
constexpr intptr_t len = ARRAY_SIZE(elf::ELFMAG);
const uint8_t* so_start = shared_object_start();
for (intptr_t i = 0; i < len; ++i) {
if (so_start[i] != elf::ELFMAG[i]) return false;
auto* const ident =
reinterpret_cast<const elf::ElfHeader*>(shared_object_start())->ident;
for (size_t i = 0; i < ARRAY_SIZE(elf::ELFMAG); ++i) {
if (ident[i] != elf::ELFMAG[i]) return false;
}
return true;
#else
@@ -159,6 +164,19 @@ bool Image::compiled_to_elf() const {
#endif
}
bool Image::compiled_to_macho() const {
#if defined(DART_PRECOMPILED_RUNTIME)
if (!compiled_to_shared_object()) return false;
auto const magic =
reinterpret_cast<const mach_o::mach_header*>(shared_object_start())
->magic;
return magic == mach_o::MH_MAGIC || magic == mach_o::MH_CIGAM ||
magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64;
#else
return false;
#endif
}
uword ObjectOffsetTrait::Hash(Key key) {
ObjectPtr obj = key;
ASSERT(!obj->IsSmi());
+4
View File
@@ -91,6 +91,10 @@ class Image : ValueObject {
// Only valid for instructions images from precompiled snapshots.
bool compiled_to_elf() const;
// Returns whether this instructions section was directly compiled to MachO.
// Only valid for instructions images from precompiled snapshots.
bool compiled_to_macho() const;
private:
// For snapshots directly compiled to a shared object, returns a pointer to
// the beginning of the build id container. Otherwise returns nullptr;
+2527
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2025, 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.
#ifndef RUNTIME_VM_MACH_O_H_
#define RUNTIME_VM_MACH_O_H_
#include "platform/globals.h"
#if defined(DART_PRECOMPILER)
#include "vm/allocation.h"
#include "vm/compiler/runtime_api.h"
#include "vm/datastream.h"
#include "vm/growable_array.h"
#include "vm/so_writer.h"
#include "vm/zone.h"
namespace dart {
class MachOHeader;
class MachOSymbolTable;
class MachOWriteStream;
class MachOWriter : public SharedObjectWriter {
public:
MachOWriter(Zone* zone,
BaseWriteStream* stream,
Type type,
const char* id,
Dwarf* dwarf = nullptr);
#if defined(TARGET_ARCH_ARM64)
static constexpr intptr_t kPageSize = 16 * KB;
#else
static constexpr intptr_t kPageSize = 4 * KB;
#endif
intptr_t page_size() const override { return kPageSize; }
Output output() const override { return Output::MachO; }
const MachOHeader& header() const { return header_; }
void AddText(const char* name,
intptr_t label,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbol) override;
void AddROData(const char* name,
intptr_t label,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbols) override;
void Finalize() override;
void AssertConsistency(const SharedObjectWriter* debug) const override {
if (auto* const debug_macho = debug->AsMachOWriter()) {
AssertConsistency(this, debug_macho);
} else {
FATAL("Expected both snapshot and debug to be MachO");
}
}
const MachOWriter* AsMachOWriter() const override { return this; }
private:
static void AssertConsistency(const MachOWriter* snapshot,
const MachOWriter* debug_info);
MachOHeader& header_;
};
} // namespace dart
#endif // DART_PRECOMPILER
#endif // RUNTIME_VM_MACH_O_H_
+3 -1
View File
@@ -304,7 +304,9 @@ OS::BuildId OS::GetAppBuildId(const uint8_t* snapshot_instructions) {
const uint8_t* dso_base = GetAppDSOBase(snapshot_instructions);
const auto& macho_header =
*reinterpret_cast<const struct mach_header*>(dso_base);
// We assume host endianness in the Mach-O file.
// If the Mach-O file is not host endian, then we'd need to adjust the code
// below (and also the snapshot loading code) to load multibyte integers
// as reverse endian.
if (macho_header.magic != MH_MAGIC && macho_header.magic != MH_MAGIC_64) {
return {0, nullptr};
}
+4
View File
@@ -16,6 +16,10 @@
#include <tuple>
#include <utility>
#if defined(DART_HOST_OS_MACOS)
#include <os/signpost.h>
#endif
#include "platform/atomic.h"
#include "platform/hashmap.h"
#include "vm/isolate.h"
+9 -2
View File
@@ -29,10 +29,17 @@
#if defined(FUCHSIA_SDK) || defined(DART_HOST_OS_FUCHSIA)
#include <lib/trace-engine/context.h>
#include <lib/trace-engine/instrumentation.h>
#elif defined(DART_HOST_OS_MACOS)
#include <os/signpost.h>
#endif // defined(FUCHSIA_SDK) || defined(DART_HOST_OS_FUCHSIA)
#if defined(DART_HOST_OS_MACOS)
// Including <os/signpost.h> in this header leads to an include of
// <mach-o/loader.h>, which causes files that use this header and the
// definitions in "platform/mach_o.h" to fail to build. To avoid this,
// duplicate the typedef that <os/log.h> creates here and only include
// <os/signpost.h> in timeline.cc and timeline_macos.cc.
typedef struct os_log_s* os_log_t;
#endif
namespace dart {
#if !defined(SUPPORT_TIMELINE)
+2
View File
@@ -5,6 +5,8 @@
#include "vm/globals.h"
#if defined(DART_HOST_OS_MACOS) && defined(SUPPORT_TIMELINE)
#include <os/signpost.h>
#include "vm/log.h"
#include "vm/timeline.h"
+2
View File
@@ -166,6 +166,8 @@ vm_sources = [
"log.h",
"longjump.cc",
"longjump.h",
"mach_o.cc",
"mach_o.h",
"megamorphic_cache_table.cc",
"megamorphic_cache_table.h",
"memory_region.cc",