Add compact AOT patching support and related APIs
- Introduced Dart_SetObfuscationMap to restore obfuscation maps before AOT precompilation. - Added Dart_AotPatchInstallOptions structure for AOT patch installation options. - Implemented Dart_AotPatchingEnabled to check if compact AOT patching is supported. - Created Dart_SetAotPatchKeyCallback for AES key resolution during AOT patch installation. - Developed Dart_InstallAotPatch for validating and installing encrypted AOT patches. - Added Dart_FreeAotPatchPayload to free memory allocated for patch payloads. - Updated runtime_args.gni to include dart_enable_aot_patching flag. - Added tests for AOT patching functionality and ensured exported symbols include new APIs. - Refactored existing code to accommodate new AOT patching features and improve error handling.
This commit is contained in:
@@ -633,6 +633,12 @@ if (is_win) {
|
|||||||
default_warning_flags += [ "/WX" ] # Treat warnings as errors.
|
default_warning_flags += [ "/WX" ] # Treat warnings as errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure UTF-8 source encoding on Chinese Windows (codepage 936)
|
||||||
|
default_warning_flags += [
|
||||||
|
"/utf-8",
|
||||||
|
"/wd4819",
|
||||||
|
]
|
||||||
|
|
||||||
if (is_clang) {
|
if (is_clang) {
|
||||||
default_warning_flags += [
|
default_warning_flags += [
|
||||||
"-Wno-deprecated-declarations", # crashpad
|
"-Wno-deprecated-declarations", # crashpad
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import sys
|
|||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
if hasattr(sys.stdout, 'reconfigure'):
|
||||||
|
sys.stdout.reconfigure(errors='replace')
|
||||||
|
if hasattr(sys.stderr, 'reconfigure'):
|
||||||
|
sys.stderr.reconfigure(errors='replace')
|
||||||
|
|
||||||
# A regex matching an argument corresponding to the output filename passed to
|
# A regex matching an argument corresponding to the output filename passed to
|
||||||
# link.exe.
|
# link.exe.
|
||||||
_LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
|
_LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
|
||||||
@@ -142,7 +147,8 @@ class WinTool(object):
|
|||||||
env=env,
|
env=env,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
universal_newlines=True)
|
universal_newlines=True,
|
||||||
|
errors='replace')
|
||||||
# Read output one line at a time as it shows up to avoid OOM failures when
|
# Read output one line at a time as it shows up to avoid OOM failures when
|
||||||
# GBs of output is produced.
|
# GBs of output is produced.
|
||||||
for line in link.stdout:
|
for line in link.stdout:
|
||||||
|
|||||||
+20
-7
@@ -67,6 +67,7 @@ SDK_VERSION = '10.0.26100.0'
|
|||||||
# which makes a difference for the arm64 runtime.
|
# which makes a difference for the arm64 runtime.
|
||||||
# The second number is an alternate version number, only used in an error string
|
# The second number is an alternate version number, only used in an error string
|
||||||
MSVS_VERSIONS = collections.OrderedDict([
|
MSVS_VERSIONS = collections.OrderedDict([
|
||||||
|
('2026', '18.0'), # VS 2026 (user-installed) - uses folder name '18'
|
||||||
('2022', '17.0'), # The VS version in our packaged toolchain.
|
('2022', '17.0'), # The VS version in our packaged toolchain.
|
||||||
('2019', '16.0'),
|
('2019', '16.0'),
|
||||||
('2017', '15.0'),
|
('2017', '15.0'),
|
||||||
@@ -75,11 +76,20 @@ MSVS_VERSIONS = collections.OrderedDict([
|
|||||||
# List of preferred VC toolset version based on MSVS
|
# List of preferred VC toolset version based on MSVS
|
||||||
# Order is not relevant for this dictionary.
|
# Order is not relevant for this dictionary.
|
||||||
MSVC_TOOLSET_VERSION = {
|
MSVC_TOOLSET_VERSION = {
|
||||||
|
'2026': 'VC145',
|
||||||
'2022': 'VC143',
|
'2022': 'VC143',
|
||||||
'2019': 'VC142',
|
'2019': 'VC142',
|
||||||
'2017': 'VC141',
|
'2017': 'VC141',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def VisualStudioFolderName(version_as_year):
|
||||||
|
"""Return the install folder name for a supported Visual Studio version."""
|
||||||
|
if version_as_year >= '2026':
|
||||||
|
return MSVS_VERSIONS.get(version_as_year, version_as_year).split('.')[0]
|
||||||
|
return version_as_year
|
||||||
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
json_data_file = os.path.join(script_dir, 'win_toolchain.json')
|
json_data_file = os.path.join(script_dir, 'win_toolchain.json')
|
||||||
|
|
||||||
@@ -212,7 +222,8 @@ def GetVisualStudioVersion():
|
|||||||
else:
|
else:
|
||||||
program_files_path_variable = '%ProgramFiles(x86)%'
|
program_files_path_variable = '%ProgramFiles(x86)%'
|
||||||
path = os.path.expandvars(program_files_path_variable +
|
path = os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s' % version)
|
'/Microsoft Visual Studio/%s' %
|
||||||
|
VisualStudioFolderName(version))
|
||||||
if path and any(
|
if path and any(
|
||||||
os.path.exists(os.path.join(path, edition))
|
os.path.exists(os.path.join(path, edition))
|
||||||
for edition in ('Enterprise', 'Professional', 'Community', 'Preview',
|
for edition in ('Enterprise', 'Professional', 'Community', 'Preview',
|
||||||
@@ -242,24 +253,25 @@ def DetectVisualStudioPath():
|
|||||||
program_files_path_variable = '%ProgramFiles%'
|
program_files_path_variable = '%ProgramFiles%'
|
||||||
else:
|
else:
|
||||||
program_files_path_variable = '%ProgramFiles(x86)%'
|
program_files_path_variable = '%ProgramFiles(x86)%'
|
||||||
|
folder_name = VisualStudioFolderName(version_as_year)
|
||||||
for path in (os.environ.get('vs%s_install' % version_as_year),
|
for path in (os.environ.get('vs%s_install' % version_as_year),
|
||||||
os.path.expandvars(program_files_path_variable +
|
os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s/Enterprise' %
|
'/Microsoft Visual Studio/%s/Enterprise' %
|
||||||
version_as_year),
|
folder_name),
|
||||||
os.path.expandvars(program_files_path_variable +
|
os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s/Professional' %
|
'/Microsoft Visual Studio/%s/Professional' %
|
||||||
version_as_year),
|
folder_name),
|
||||||
os.path.expandvars(program_files_path_variable +
|
os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s/Community' %
|
'/Microsoft Visual Studio/%s/Community' %
|
||||||
version_as_year),
|
folder_name),
|
||||||
os.path.expandvars(program_files_path_variable +
|
os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s/Preview' %
|
'/Microsoft Visual Studio/%s/Preview' %
|
||||||
version_as_year),
|
folder_name),
|
||||||
os.path.expandvars(program_files_path_variable +
|
os.path.expandvars(program_files_path_variable +
|
||||||
'/Microsoft Visual Studio/%s/BuildTools' %
|
'/Microsoft Visual Studio/%s/BuildTools' %
|
||||||
version_as_year)):
|
folder_name)):
|
||||||
if path and os.path.exists(path):
|
if path and os.path.exists(path):
|
||||||
return path
|
return path.strip()
|
||||||
|
|
||||||
raise Exception('Visual Studio Version %s not found.' % version_as_year)
|
raise Exception('Visual Studio Version %s not found.' % version_as_year)
|
||||||
|
|
||||||
@@ -562,6 +574,7 @@ def Update(force=False, no_download=False):
|
|||||||
|
|
||||||
|
|
||||||
def NormalizePath(path):
|
def NormalizePath(path):
|
||||||
|
path = path.strip()
|
||||||
while path.endswith('\\'):
|
while path.endswith('\\'):
|
||||||
path = path[:-1]
|
path = path[:-1]
|
||||||
return path
|
return path
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ void alsoVerySecretFoo() {
|
|||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
List<MappingPair> mapping = getSnapshotMap(tmpDir, secretfilenameFile);
|
List<MappingPair> mapping = getSnapshotMap(
|
||||||
|
tmpDir,
|
||||||
|
secretfilenameFile,
|
||||||
|
outputPrefix: "base",
|
||||||
|
);
|
||||||
bool good = verify(
|
bool good = verify(
|
||||||
mapping,
|
mapping,
|
||||||
{
|
{
|
||||||
@@ -55,13 +59,51 @@ void alsoVerySecretFoo() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!good) throw "Obfuscation didn't work as expected";
|
if (!good) throw "Obfuscation didn't work as expected";
|
||||||
|
|
||||||
|
secretfilename2File.writeAsStringSync("""
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void verySecretFoo() {
|
||||||
|
print("foo!");
|
||||||
|
alsoVerySecretFoo();
|
||||||
|
newPatchOnlySecretFoo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void alsoVerySecretFoo() {
|
||||||
|
print("foo too!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void newPatchOnlySecretFoo() {
|
||||||
|
print("patched foo!");
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
final File baseObfuscationMapFile = new File.fromUri(
|
||||||
|
tmpDirUri.resolve("obfuscation-base.map"),
|
||||||
|
);
|
||||||
|
List<MappingPair> patchMapping = getSnapshotMap(
|
||||||
|
tmpDir,
|
||||||
|
secretfilenameFile,
|
||||||
|
outputPrefix: "patch",
|
||||||
|
loadObfuscationMapFile: baseObfuscationMapFile,
|
||||||
|
);
|
||||||
|
good = verifyLoadedObfuscationMap(
|
||||||
|
mapping,
|
||||||
|
patchMapping,
|
||||||
|
"newPatchOnlySecretFoo",
|
||||||
|
);
|
||||||
|
if (!good) throw "Loaded obfuscation map didn't work as expected";
|
||||||
print("Good");
|
print("Good");
|
||||||
} finally {
|
} finally {
|
||||||
tmpDir.deleteSync(recursive: true);
|
tmpDir.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MappingPair> getSnapshotMap(Directory tmpDir, File compileDartFile) {
|
List<MappingPair> getSnapshotMap(
|
||||||
|
Directory tmpDir,
|
||||||
|
File compileDartFile, {
|
||||||
|
required String outputPrefix,
|
||||||
|
File? loadObfuscationMapFile,
|
||||||
|
}) {
|
||||||
final Uri genKernel = Platform.script.resolve('../bin/gen_kernel.dart');
|
final Uri genKernel = Platform.script.resolve('../bin/gen_kernel.dart');
|
||||||
final File genKernelFile = new File.fromUri(genKernel);
|
final File genKernelFile = new File.fromUri(genKernel);
|
||||||
if (!genKernelFile.existsSync()) {
|
if (!genKernelFile.existsSync()) {
|
||||||
@@ -96,7 +138,7 @@ List<MappingPair> getSnapshotMap(Directory tmpDir, File compileDartFile) {
|
|||||||
|
|
||||||
final Uri tmpDirUri = tmpDir.uri;
|
final Uri tmpDirUri = tmpDir.uri;
|
||||||
|
|
||||||
final Uri kernelDill = tmpDirUri.resolve("kernel.dill");
|
final Uri kernelDill = tmpDirUri.resolve("kernel-$outputPrefix.dill");
|
||||||
final File kernelDillFile = new File.fromUri(kernelDill);
|
final File kernelDillFile = new File.fromUri(kernelDill);
|
||||||
|
|
||||||
print("Running gen_kernel");
|
print("Running gen_kernel");
|
||||||
@@ -120,14 +162,14 @@ List<MappingPair> getSnapshotMap(Directory tmpDir, File compileDartFile) {
|
|||||||
"stderr: ${kernelRun.stderr}";
|
"stderr: ${kernelRun.stderr}";
|
||||||
}
|
}
|
||||||
|
|
||||||
final Uri aotElf = tmpDirUri.resolve("aot.elf");
|
final Uri aotElf = tmpDirUri.resolve("aot-$outputPrefix.elf");
|
||||||
final File aotElfFile = new File.fromUri(aotElf);
|
final File aotElfFile = new File.fromUri(aotElf);
|
||||||
final Uri obfuscationMap = tmpDirUri.resolve("obfuscation.map");
|
final Uri obfuscationMap = tmpDirUri.resolve("obfuscation-$outputPrefix.map");
|
||||||
final File obfuscationMapFile = new File.fromUri(obfuscationMap);
|
final File obfuscationMapFile = new File.fromUri(obfuscationMap);
|
||||||
|
|
||||||
print("Running $genSnapshot");
|
print("Running $genSnapshot");
|
||||||
// Extracted from pkg/dart2native/lib/dart2native.dart.
|
// Extracted from pkg/dart2native/lib/dart2native.dart.
|
||||||
final ProcessResult snapshotRun = Process.runSync(genSnapshotFile.path, [
|
final List<String> genSnapshotArgs = [
|
||||||
"--snapshot-kind=app-aot-elf",
|
"--snapshot-kind=app-aot-elf",
|
||||||
"--elf=${aotElfFile.path}",
|
"--elf=${aotElfFile.path}",
|
||||||
"--dwarf-stack-traces",
|
"--dwarf-stack-traces",
|
||||||
@@ -135,7 +177,17 @@ List<MappingPair> getSnapshotMap(Directory tmpDir, File compileDartFile) {
|
|||||||
"--strip",
|
"--strip",
|
||||||
"--save-obfuscation-map=${obfuscationMapFile.path}",
|
"--save-obfuscation-map=${obfuscationMapFile.path}",
|
||||||
kernelDillFile.path,
|
kernelDillFile.path,
|
||||||
]);
|
];
|
||||||
|
if (loadObfuscationMapFile != null) {
|
||||||
|
genSnapshotArgs.insert(
|
||||||
|
genSnapshotArgs.length - 1,
|
||||||
|
"--load-obfuscation-map=${loadObfuscationMapFile.path}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final ProcessResult snapshotRun = Process.runSync(
|
||||||
|
genSnapshotFile.path,
|
||||||
|
genSnapshotArgs,
|
||||||
|
);
|
||||||
|
|
||||||
if (snapshotRun.exitCode != 0) {
|
if (snapshotRun.exitCode != 0) {
|
||||||
throw "Got exit code ${snapshotRun.exitCode}\n"
|
throw "Got exit code ${snapshotRun.exitCode}\n"
|
||||||
@@ -157,6 +209,10 @@ List<MappingPair> readJsonMapping(File file) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, String> toMap(List<MappingPair> mapping) {
|
||||||
|
return {for (MappingPair pair in mapping) pair.from: pair.to};
|
||||||
|
}
|
||||||
|
|
||||||
class MappingPair {
|
class MappingPair {
|
||||||
final String from;
|
final String from;
|
||||||
final String to;
|
final String to;
|
||||||
@@ -200,3 +256,49 @@ bool verify(
|
|||||||
}
|
}
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool verifyLoadedObfuscationMap(
|
||||||
|
List<MappingPair> baseMapping,
|
||||||
|
List<MappingPair> patchMapping,
|
||||||
|
String patchOnlyName,
|
||||||
|
) {
|
||||||
|
bool good = true;
|
||||||
|
final Map<String, String> patchMap = toMap(patchMapping);
|
||||||
|
for (MappingPair baseEntry in baseMapping) {
|
||||||
|
final String? patchValue = patchMap[baseEntry.from];
|
||||||
|
if (patchValue == null) {
|
||||||
|
print("Patch map is missing loaded entry ${baseEntry.from}");
|
||||||
|
good = false;
|
||||||
|
} else if (patchValue != baseEntry.to) {
|
||||||
|
print(
|
||||||
|
"Expected ${baseEntry.from} to keep ${baseEntry.to}, "
|
||||||
|
"but patch map used $patchValue",
|
||||||
|
);
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? patchOnlyValue = patchMap[patchOnlyName];
|
||||||
|
if (patchOnlyValue == null) {
|
||||||
|
print("Patch map is missing $patchOnlyName");
|
||||||
|
good = false;
|
||||||
|
} else {
|
||||||
|
if (patchOnlyValue == patchOnlyName) {
|
||||||
|
print("Expected $patchOnlyName to be obfuscated");
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
final Set<String> baseValues = baseMapping
|
||||||
|
.where((MappingPair pair) => pair.from != pair.to)
|
||||||
|
.map((MappingPair pair) => pair.to)
|
||||||
|
.toSet();
|
||||||
|
if (baseValues.contains(patchOnlyValue)) {
|
||||||
|
print(
|
||||||
|
"Patch-only rename $patchOnlyName->$patchOnlyValue collides with "
|
||||||
|
"the loaded map",
|
||||||
|
);
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import("runtime_args.gni")
|
|||||||
|
|
||||||
import("//build/config/sysroot.gni")
|
import("//build/config/sysroot.gni")
|
||||||
|
|
||||||
|
assert(!dart_enable_aot_patching || !dart_dynamic_modules,
|
||||||
|
"dart_enable_aot_patching must be built without dart_dynamic_modules.")
|
||||||
|
|
||||||
config("dart_public_config") {
|
config("dart_public_config") {
|
||||||
include_dirs = [
|
include_dirs = [
|
||||||
".",
|
".",
|
||||||
@@ -231,6 +234,10 @@ config("dart_config") {
|
|||||||
defines += [ "DART_DYNAMIC_MODULES" ]
|
defines += [ "DART_DYNAMIC_MODULES" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dart_enable_aot_patching) {
|
||||||
|
defines += [ "DART_ENABLE_AOT_PATCHING" ]
|
||||||
|
}
|
||||||
|
|
||||||
if (include_experimental_vm_service) {
|
if (include_experimental_vm_service) {
|
||||||
defines += [ "EXPERIMENTAL_VM_SERVICE" ]
|
defines += [ "EXPERIMENTAL_VM_SERVICE" ]
|
||||||
}
|
}
|
||||||
@@ -371,6 +378,9 @@ library_for_all_configs("libdart") {
|
|||||||
if (dart_support_perfetto) {
|
if (dart_support_perfetto) {
|
||||||
extra_deps += [ "//third_party/perfetto:libprotozero" ]
|
extra_deps += [ "//third_party/perfetto:libprotozero" ]
|
||||||
}
|
}
|
||||||
|
if (dart_enable_aot_patching) {
|
||||||
|
extra_deps += [ "//third_party/boringssl" ]
|
||||||
|
}
|
||||||
if (is_fuchsia) {
|
if (is_fuchsia) {
|
||||||
extra_deps += [
|
extra_deps += [
|
||||||
"$fuchsia_sdk/pkg/fdio",
|
"$fuchsia_sdk/pkg/fdio",
|
||||||
@@ -385,6 +395,9 @@ library_for_all_configs("libdart") {
|
|||||||
compiler_lib = "vm:libdart_compiler"
|
compiler_lib = "vm:libdart_compiler"
|
||||||
extra_configs = [ ":dart_shared_lib" ]
|
extra_configs = [ ":dart_shared_lib" ]
|
||||||
include_dirs = [ "." ]
|
include_dirs = [ "." ]
|
||||||
|
if (dart_enable_aot_patching) {
|
||||||
|
include_dirs += [ "//third_party/boringssl/src/include" ]
|
||||||
|
}
|
||||||
public_configs = [ ":dart_public_config" ]
|
public_configs = [ ":dart_public_config" ]
|
||||||
sources = [
|
sources = [
|
||||||
"$target_gen_dir/version.cc",
|
"$target_gen_dir/version.cc",
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ static const char* const kSnapshotKindNames[] = {
|
|||||||
V(loading_unit_manifest, loading_unit_manifest_filename) \
|
V(loading_unit_manifest, loading_unit_manifest_filename) \
|
||||||
V(save_debugging_info, debugging_info_filename) \
|
V(save_debugging_info, debugging_info_filename) \
|
||||||
V(save_obfuscation_map, obfuscation_map_filename) \
|
V(save_obfuscation_map, obfuscation_map_filename) \
|
||||||
|
V(load_obfuscation_map, load_obfuscation_map_filename) \
|
||||||
V(ffi_callback_stub, ffi_callback_stub_filename)
|
V(ffi_callback_stub, ffi_callback_stub_filename)
|
||||||
|
|
||||||
#define BOOL_OPTIONS_LIST(V) \
|
#define BOOL_OPTIONS_LIST(V) \
|
||||||
@@ -168,6 +169,7 @@ static void PrintUsage() {
|
|||||||
"[--obfuscate] \n"
|
"[--obfuscate] \n"
|
||||||
"[--save-debugging-info=<debug-filename>] \n"
|
"[--save-debugging-info=<debug-filename>] \n"
|
||||||
"[--save-obfuscation-map=<map-filename>] \n"
|
"[--save-obfuscation-map=<map-filename>] \n"
|
||||||
|
"[--load-obfuscation-map=<map-filename>] \n"
|
||||||
"<dart-kernel-file> \n"
|
"<dart-kernel-file> \n"
|
||||||
" \n"
|
" \n"
|
||||||
"To create an AOT application snapshot as an ELF shared library: \n"
|
"To create an AOT application snapshot as an ELF shared library: \n"
|
||||||
@@ -177,6 +179,7 @@ static void PrintUsage() {
|
|||||||
"[--obfuscate] \n"
|
"[--obfuscate] \n"
|
||||||
"[--save-debugging-info=<debug-filename>] \n"
|
"[--save-debugging-info=<debug-filename>] \n"
|
||||||
"[--save-obfuscation-map=<map-filename>] \n"
|
"[--save-obfuscation-map=<map-filename>] \n"
|
||||||
|
"[--load-obfuscation-map=<map-filename>] \n"
|
||||||
"<dart-kernel-file> \n"
|
"<dart-kernel-file> \n"
|
||||||
" \n"
|
" \n"
|
||||||
"To create an AOT application snapshot as an Mach-O dynamic library (dylib): \n"
|
"To create an AOT application snapshot as an Mach-O dynamic library (dylib): \n"
|
||||||
@@ -186,6 +189,7 @@ static void PrintUsage() {
|
|||||||
"[--obfuscate] \n"
|
"[--obfuscate] \n"
|
||||||
"[--save-debugging-info=<debug-filename>] \n"
|
"[--save-debugging-info=<debug-filename>] \n"
|
||||||
"[--save-obfuscation-map=<map-filename>] \n"
|
"[--save-obfuscation-map=<map-filename>] \n"
|
||||||
|
"[--load-obfuscation-map=<map-filename>] \n"
|
||||||
"<dart-kernel-file> \n"
|
"<dart-kernel-file> \n"
|
||||||
" \n"
|
" \n"
|
||||||
"AOT snapshots can be obfuscated: that is all identifiers will be renamed \n"
|
"AOT snapshots can be obfuscated: that is all identifiers will be renamed \n"
|
||||||
@@ -320,6 +324,12 @@ static int ParseArguments(int argc,
|
|||||||
"obfuscation is enabled by the --obfuscate flag.\n\n");
|
"obfuscation is enabled by the --obfuscate flag.\n\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (!obfuscate && load_obfuscation_map_filename != nullptr) {
|
||||||
|
Syslog::PrintErr(
|
||||||
|
"--load-obfuscation_map=<...> should only be specified when "
|
||||||
|
"obfuscation is enabled by the --obfuscate flag.\n\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsSnapshottingForPrecompilation()) {
|
if (!IsSnapshottingForPrecompilation()) {
|
||||||
if (obfuscate) {
|
if (obfuscate) {
|
||||||
@@ -335,6 +345,13 @@ static int ParseArguments(int argc,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (load_obfuscation_map_filename != nullptr) {
|
||||||
|
Syslog::PrintErr(
|
||||||
|
"--load-obfuscation-map=<...> can only be enabled when building an "
|
||||||
|
"AOT snapshot.\n\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (strip) {
|
if (strip) {
|
||||||
Syslog::PrintErr(
|
Syslog::PrintErr(
|
||||||
"Stripping can only be enabled when building an AOT snapshot.\n\n");
|
"Stripping can only be enabled when building an AOT snapshot.\n\n");
|
||||||
@@ -394,6 +411,209 @@ static void MallocFinalizer(void* isolate_callback_data, void* peer) {
|
|||||||
free(peer);
|
free(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsJsonWhitespace(uint8_t c) {
|
||||||
|
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int HexDigit(uint8_t c) {
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if (c >= 'a' && c <= 'f') {
|
||||||
|
return 10 + (c - 'a');
|
||||||
|
}
|
||||||
|
if (c >= 'A' && c <= 'F') {
|
||||||
|
return 10 + (c - 'A');
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendUtf8(char* buffer, intptr_t* length, uint32_t code_point) {
|
||||||
|
if (code_point <= 0x7F) {
|
||||||
|
buffer[(*length)++] = static_cast<char>(code_point);
|
||||||
|
} else if (code_point <= 0x7FF) {
|
||||||
|
buffer[(*length)++] = static_cast<char>(0xC0 | (code_point >> 6));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | (code_point & 0x3F));
|
||||||
|
} else if (code_point <= 0xFFFF) {
|
||||||
|
buffer[(*length)++] = static_cast<char>(0xE0 | (code_point >> 12));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | ((code_point >> 6) & 0x3F));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | (code_point & 0x3F));
|
||||||
|
} else {
|
||||||
|
buffer[(*length)++] = static_cast<char>(0xF0 | (code_point >> 18));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | ((code_point >> 12) & 0x3F));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | ((code_point >> 6) & 0x3F));
|
||||||
|
buffer[(*length)++] = static_cast<char>(0x80 | (code_point & 0x3F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseJsonStringArray(const uint8_t* buffer,
|
||||||
|
intptr_t size,
|
||||||
|
MallocGrowableArray<const char*>* strings,
|
||||||
|
const char** error) {
|
||||||
|
intptr_t cursor = 0;
|
||||||
|
auto skip_whitespace = [&]() {
|
||||||
|
while (cursor < size && IsJsonWhitespace(buffer[cursor])) {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
skip_whitespace();
|
||||||
|
if (cursor >= size || buffer[cursor++] != '[') {
|
||||||
|
*error = "expected '['";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
skip_whitespace();
|
||||||
|
if (cursor < size && buffer[cursor] == ']') {
|
||||||
|
cursor++;
|
||||||
|
skip_whitespace();
|
||||||
|
if (cursor != size) {
|
||||||
|
*error = "unexpected characters after ']'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor < size) {
|
||||||
|
if (buffer[cursor++] != '"') {
|
||||||
|
*error = "expected string";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char* value = reinterpret_cast<char*>(malloc(size + 1));
|
||||||
|
if (value == nullptr) {
|
||||||
|
*error = "out of memory";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
intptr_t length = 0;
|
||||||
|
while (cursor < size) {
|
||||||
|
uint8_t c = buffer[cursor++];
|
||||||
|
if (c == '"') {
|
||||||
|
value[length] = '\0';
|
||||||
|
strings->Add(value);
|
||||||
|
value = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c != '\\') {
|
||||||
|
value[length++] = static_cast<char>(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cursor >= size) {
|
||||||
|
free(value);
|
||||||
|
*error = "unterminated escape";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
c = buffer[cursor++];
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
case '/':
|
||||||
|
value[length++] = static_cast<char>(c);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
value[length++] = '\b';
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
value[length++] = '\f';
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
value[length++] = '\n';
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
value[length++] = '\r';
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
value[length++] = '\t';
|
||||||
|
break;
|
||||||
|
case 'u': {
|
||||||
|
if (cursor + 4 > size) {
|
||||||
|
free(value);
|
||||||
|
*error = "incomplete unicode escape";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t code_point = 0;
|
||||||
|
for (intptr_t i = 0; i < 4; i++) {
|
||||||
|
const int digit = HexDigit(buffer[cursor++]);
|
||||||
|
if (digit < 0) {
|
||||||
|
free(value);
|
||||||
|
*error = "invalid unicode escape";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
code_point = (code_point << 4) | digit;
|
||||||
|
}
|
||||||
|
AppendUtf8(value, &length, code_point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
free(value);
|
||||||
|
*error = "invalid escape";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value != nullptr) {
|
||||||
|
free(value);
|
||||||
|
*error = "unterminated string";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_whitespace();
|
||||||
|
if (cursor < size && buffer[cursor] == ',') {
|
||||||
|
cursor++;
|
||||||
|
skip_whitespace();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cursor < size && buffer[cursor] == ']') {
|
||||||
|
cursor++;
|
||||||
|
skip_whitespace();
|
||||||
|
if (cursor != size) {
|
||||||
|
*error = "unexpected characters after ']'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*error = "expected ',' or ']'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*error = "unterminated array";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MaybeLoadObfuscationMap() {
|
||||||
|
if (load_obfuscation_map_filename == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* buffer = nullptr;
|
||||||
|
intptr_t size = 0;
|
||||||
|
ReadFile(load_obfuscation_map_filename, &buffer, &size);
|
||||||
|
|
||||||
|
MallocGrowableArray<const char*> strings;
|
||||||
|
const char* parse_error = nullptr;
|
||||||
|
if (!ParseJsonStringArray(buffer, size, &strings, &parse_error)) {
|
||||||
|
free(buffer);
|
||||||
|
for (intptr_t i = 0; i < strings.length(); i++) {
|
||||||
|
free(const_cast<char*>(strings[i]));
|
||||||
|
}
|
||||||
|
PrintErrAndExit("Error: Invalid obfuscation map %s: %s\n",
|
||||||
|
load_obfuscation_map_filename, parse_error);
|
||||||
|
}
|
||||||
|
if ((strings.length() % 2) != 0) {
|
||||||
|
free(buffer);
|
||||||
|
for (intptr_t i = 0; i < strings.length(); i++) {
|
||||||
|
free(const_cast<char*>(strings[i]));
|
||||||
|
}
|
||||||
|
PrintErrAndExit(
|
||||||
|
"Error: Invalid obfuscation map %s: expected string pairs\n",
|
||||||
|
load_obfuscation_map_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dart_Handle result = Dart_SetObfuscationMap(strings.data(), strings.length());
|
||||||
|
free(buffer);
|
||||||
|
for (intptr_t i = 0; i < strings.length(); i++) {
|
||||||
|
free(const_cast<char*>(strings[i]));
|
||||||
|
}
|
||||||
|
CHECK_RESULT(result);
|
||||||
|
}
|
||||||
|
|
||||||
static void MaybeLoadExtraInputs(const CommandLineOptions& inputs) {
|
static void MaybeLoadExtraInputs(const CommandLineOptions& inputs) {
|
||||||
for (intptr_t i = 1; i < inputs.count(); i++) {
|
for (intptr_t i = 1; i < inputs.count(); i++) {
|
||||||
uint8_t* buffer = nullptr;
|
uint8_t* buffer = nullptr;
|
||||||
@@ -656,6 +876,8 @@ static void CreateAndWritePrecompiledSnapshot() {
|
|||||||
ASSERT(kind_str != nullptr);
|
ASSERT(kind_str != nullptr);
|
||||||
ASSERT(filename != nullptr);
|
ASSERT(filename != nullptr);
|
||||||
|
|
||||||
|
MaybeLoadObfuscationMap();
|
||||||
|
|
||||||
// Precompile with specified embedder entry points
|
// Precompile with specified embedder entry points
|
||||||
Dart_Handle result = Dart_Precompile();
|
Dart_Handle result = Dart_Precompile();
|
||||||
CHECK_RESULT(result);
|
CHECK_RESULT(result);
|
||||||
|
|||||||
@@ -4241,6 +4241,82 @@ Dart_CreateAppJITSnapshotAsBlobs(uint8_t** snapshot_data_buffer,
|
|||||||
DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle
|
DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle
|
||||||
Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length);
|
Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a previously saved obfuscation map before AOT precompilation.
|
||||||
|
*
|
||||||
|
* The map uses the same flat string-pair format returned by
|
||||||
|
* Dart_GetObfuscationMap: [original0, obfuscated0, original1, obfuscated1, ...].
|
||||||
|
* The VM rebuilds the precompiler obfuscation state so subsequent renames are
|
||||||
|
* stable with the release build and newly introduced identifiers receive fresh
|
||||||
|
* obfuscated names.
|
||||||
|
*/
|
||||||
|
DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle
|
||||||
|
Dart_SetObfuscationMap(const char* const* map, intptr_t map_length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ========
|
||||||
|
* AOT patching
|
||||||
|
* ========
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* app_id;
|
||||||
|
const char* app_build_id;
|
||||||
|
const char* base_flavor_id;
|
||||||
|
const char* base_license_type;
|
||||||
|
const char* flavor_id;
|
||||||
|
const char* license_type;
|
||||||
|
const char* sdk_hash;
|
||||||
|
const char* base_snapshot_hash;
|
||||||
|
const char* patch_snapshot_hash;
|
||||||
|
const char* obfuscation_map_hash;
|
||||||
|
const char* target_os;
|
||||||
|
const char* target_arch;
|
||||||
|
} Dart_AotPatchInstallOptions;
|
||||||
|
|
||||||
|
typedef bool (*Dart_AotPatchKeyCallback)(const char* key_id,
|
||||||
|
uint8_t* key_buffer,
|
||||||
|
intptr_t key_buffer_length,
|
||||||
|
intptr_t* key_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this VM was built with compact AOT patching support.
|
||||||
|
*
|
||||||
|
* This feature is intentionally independent of DART_DYNAMIC_MODULES and does
|
||||||
|
* not enable the bytecode interpreter.
|
||||||
|
*/
|
||||||
|
DART_EXPORT bool Dart_AotPatchingEnabled(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the callback used to resolve AES keys for encrypted AOT patch payloads.
|
||||||
|
* The callback is owned by the embedder and must remain valid while patches may
|
||||||
|
* be installed.
|
||||||
|
*/
|
||||||
|
DART_EXPORT void Dart_SetAotPatchKeyCallback(Dart_AotPatchKeyCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an encrypted compact AOT patch artifact before embedder install.
|
||||||
|
*
|
||||||
|
* The VM validates open artifact metadata and requests key material for the
|
||||||
|
* artifact key id, then decrypts the AES-256-GCM compact payload into an owned
|
||||||
|
* buffer. A success result means the artifact is accepted for embedder
|
||||||
|
* installation. The caller owns `patch_payload_buffer` and must release it with
|
||||||
|
* Dart_FreeAotPatchPayload. iOS-safe AOT patch loading maps patched isolate
|
||||||
|
* snapshot data/instructions before isolate startup; this API does not mutate
|
||||||
|
* live executable code.
|
||||||
|
*/
|
||||||
|
DART_EXPORT DART_API_WARN_UNUSED_RESULT Dart_Handle
|
||||||
|
Dart_InstallAotPatch(const uint8_t* patch_buffer,
|
||||||
|
intptr_t patch_buffer_length,
|
||||||
|
const Dart_AotPatchInstallOptions* options,
|
||||||
|
uint8_t** patch_payload_buffer,
|
||||||
|
intptr_t* patch_payload_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the buffer returned by Dart_InstallAotPatch.
|
||||||
|
*/
|
||||||
|
DART_EXPORT void Dart_FreeAotPatchPayload(uint8_t* patch_payload_buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the VM only supports running from precompiled snapshots and
|
* Returns whether the VM only supports running from precompiled snapshots and
|
||||||
* not from any other kind of snapshot or from source (that is, the VM was
|
* not from any other kind of snapshot or from source (that is, the VM was
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ declare_args() {
|
|||||||
|
|
||||||
# Whether to support dynamic loading and interpretation of Dart bytecode.
|
# Whether to support dynamic loading and interpretation of Dart bytecode.
|
||||||
dart_dynamic_modules = false
|
dart_dynamic_modules = false
|
||||||
|
|
||||||
|
# Whether to expose the compact AOT patch installation API. This is separate
|
||||||
|
# from dart_dynamic_modules and must not pull in the bytecode interpreter.
|
||||||
|
dart_enable_aot_patching = false
|
||||||
}
|
}
|
||||||
|
|
||||||
declare_args() {
|
declare_args() {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) 2026, 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.
|
||||||
|
|
||||||
|
// Verifies that the compact AOT patching API is exported and callable without
|
||||||
|
// enabling DART_DYNAMIC_MODULES or the bytecode interpreter.
|
||||||
|
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
|
import 'package:expect/expect.dart';
|
||||||
|
|
||||||
|
typedef DartAotPatchingEnabledNative = Bool Function();
|
||||||
|
typedef DartAotPatchingEnabled = bool Function();
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final enabled = DynamicLibrary.executable()
|
||||||
|
.lookupFunction<DartAotPatchingEnabledNative, DartAotPatchingEnabled>(
|
||||||
|
'Dart_AotPatchingEnabled',
|
||||||
|
)();
|
||||||
|
|
||||||
|
// The value depends on the build flag, but a successful call proves the API
|
||||||
|
// is present in both enabled and default builds.
|
||||||
|
Expect.isTrue(enabled == true || enabled == false);
|
||||||
|
}
|
||||||
@@ -59,6 +59,7 @@ main() {
|
|||||||
|
|
||||||
var expectedSymbols = [
|
var expectedSymbols = [
|
||||||
"Dart_AddSymbols",
|
"Dart_AddSymbols",
|
||||||
|
"Dart_AotPatchingEnabled",
|
||||||
"Dart_Allocate",
|
"Dart_Allocate",
|
||||||
"Dart_AllocateWithNativeFields",
|
"Dart_AllocateWithNativeFields",
|
||||||
"Dart_BooleanValue",
|
"Dart_BooleanValue",
|
||||||
@@ -111,6 +112,7 @@ main() {
|
|||||||
"Dart_False",
|
"Dart_False",
|
||||||
"Dart_FinalizeAllClasses",
|
"Dart_FinalizeAllClasses",
|
||||||
"Dart_FinalizeLoading",
|
"Dart_FinalizeLoading",
|
||||||
|
"Dart_FreeAotPatchPayload",
|
||||||
"Dart_FunctionIsStatic",
|
"Dart_FunctionIsStatic",
|
||||||
"Dart_FunctionName",
|
"Dart_FunctionName",
|
||||||
"Dart_FunctionOwner",
|
"Dart_FunctionOwner",
|
||||||
@@ -158,6 +160,7 @@ main() {
|
|||||||
"Dart_IdentityEquals",
|
"Dart_IdentityEquals",
|
||||||
"Dart_Initialize",
|
"Dart_Initialize",
|
||||||
"Dart_InitializeNativeAssetsResolver",
|
"Dart_InitializeNativeAssetsResolver",
|
||||||
|
"Dart_InstallAotPatch",
|
||||||
"Dart_InstanceGetType",
|
"Dart_InstanceGetType",
|
||||||
"Dart_IntegerFitsIntoInt64",
|
"Dart_IntegerFitsIntoInt64",
|
||||||
"Dart_IntegerFitsIntoUint64",
|
"Dart_IntegerFitsIntoUint64",
|
||||||
@@ -296,6 +299,7 @@ main() {
|
|||||||
"Dart_SendPortGetId",
|
"Dart_SendPortGetId",
|
||||||
"Dart_SendPortGetIdEx",
|
"Dart_SendPortGetIdEx",
|
||||||
"Dart_ServiceSendDataEvent",
|
"Dart_ServiceSendDataEvent",
|
||||||
|
"Dart_SetAotPatchKeyCallback",
|
||||||
"Dart_SetBooleanReturnValue",
|
"Dart_SetBooleanReturnValue",
|
||||||
"Dart_SetCurrentUserTag",
|
"Dart_SetCurrentUserTag",
|
||||||
"Dart_SetDartLibrarySourcesKernel",
|
"Dart_SetDartLibrarySourcesKernel",
|
||||||
@@ -314,6 +318,7 @@ main() {
|
|||||||
"Dart_SetMessageNotifyCallback",
|
"Dart_SetMessageNotifyCallback",
|
||||||
"Dart_SetNativeInstanceField",
|
"Dart_SetNativeInstanceField",
|
||||||
"Dart_SetNativeResolver",
|
"Dart_SetNativeResolver",
|
||||||
|
"Dart_SetObfuscationMap",
|
||||||
"Dart_SetPausedOnExit",
|
"Dart_SetPausedOnExit",
|
||||||
"Dart_SetPausedOnStart",
|
"Dart_SetPausedOnStart",
|
||||||
"Dart_SetPeer",
|
"Dart_SetPeer",
|
||||||
|
|||||||
@@ -3070,7 +3070,7 @@ void StubCodeCompiler::GenerateInterpretCallStub() {
|
|||||||
__ movq(CallingConventions::kArg3Reg, R11); // Negative argc.
|
__ movq(CallingConventions::kArg3Reg, R11); // Negative argc.
|
||||||
__ movq(CallingConventions::kArg4Reg, R12); // Argv.
|
__ movq(CallingConventions::kArg4Reg, R12); // Argv.
|
||||||
|
|
||||||
#if defined(TARGET_OS_WINDOWS)
|
#if defined(DART_TARGET_OS_WINDOWS)
|
||||||
__ movq(Address(RSP, 0 * target::kWordSize), THR); // Thread.
|
__ movq(Address(RSP, 0 * target::kWordSize), THR); // Thread.
|
||||||
#else
|
#else
|
||||||
__ movq(CallingConventions::kArg5Reg, THR); // Thread.
|
__ movq(CallingConventions::kArg5Reg, THR); // Thread.
|
||||||
|
|||||||
+613
-3
@@ -9,6 +9,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
#include <openssl/aead.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "lib/stacktrace.h"
|
#include "lib/stacktrace.h"
|
||||||
#include "platform/address_sanitizer.h"
|
#include "platform/address_sanitizer.h"
|
||||||
#include "platform/assert.h"
|
#include "platform/assert.h"
|
||||||
@@ -16,6 +21,7 @@
|
|||||||
#include "platform/thread_sanitizer.h"
|
#include "platform/thread_sanitizer.h"
|
||||||
#include "platform/unicode.h"
|
#include "platform/unicode.h"
|
||||||
#include "vm/app_snapshot.h"
|
#include "vm/app_snapshot.h"
|
||||||
|
#include "vm/base64.h"
|
||||||
#include "vm/bytecode_reader.h"
|
#include "vm/bytecode_reader.h"
|
||||||
#include "vm/class_finalizer.h"
|
#include "vm/class_finalizer.h"
|
||||||
#include "vm/compiler/jit/compiler.h"
|
#include "vm/compiler/jit/compiler.h"
|
||||||
@@ -6721,9 +6727,10 @@ static void CreateAppAOTSnapshotHelper(
|
|||||||
};
|
};
|
||||||
|
|
||||||
Dwarf* const dwarf =
|
Dwarf* const dwarf =
|
||||||
(format == Dart_AotBinaryFormat_Assembly || strip) ? nullptr
|
(format == Dart_AotBinaryFormat_Assembly || strip)
|
||||||
: generate_debug ? debug_dwarf
|
? nullptr
|
||||||
: new (Z) Dwarf(Z, deobfuscation_trie, identifier);
|
: generate_debug ? debug_dwarf
|
||||||
|
: new (Z) Dwarf(Z, deobfuscation_trie, identifier);
|
||||||
SharedObjectWriter* so = nullptr;
|
SharedObjectWriter* so = nullptr;
|
||||||
if (format == Dart_AotBinaryFormat_Elf) {
|
if (format == Dart_AotBinaryFormat_Elf) {
|
||||||
so = new (Z)
|
so = new (Z)
|
||||||
@@ -7122,6 +7129,126 @@ Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
|
||||||
|
static bool IsObfuscationNameChar(char c) {
|
||||||
|
return (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z'));
|
||||||
|
}
|
||||||
|
|
||||||
|
static intptr_t ObfuscationNameCharRank(char c) {
|
||||||
|
if (('a' <= c) && (c <= 'z')) {
|
||||||
|
return c - 'a';
|
||||||
|
}
|
||||||
|
ASSERT(('A' <= c) && (c <= 'Z'));
|
||||||
|
return 26 + (c - 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsObfuscationNameGreater(const char* left, const char* right) {
|
||||||
|
const intptr_t left_length = strlen(left);
|
||||||
|
const intptr_t right_length = strlen(right);
|
||||||
|
if (left_length != right_length) {
|
||||||
|
return left_length > right_length;
|
||||||
|
}
|
||||||
|
for (intptr_t i = left_length - 1; i >= 0; i--) {
|
||||||
|
const intptr_t left_rank = ObfuscationNameCharRank(left[i]);
|
||||||
|
const intptr_t right_rank = ObfuscationNameCharRank(right[i]);
|
||||||
|
if (left_rank != right_rank) {
|
||||||
|
return left_rank > right_rank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ExtractObfuscationRenameStem(const char* rename,
|
||||||
|
char* stem,
|
||||||
|
intptr_t stem_length) {
|
||||||
|
ASSERT(stem_length > 0);
|
||||||
|
const char* cursor = rename;
|
||||||
|
if (strncmp(cursor, "get:", 4) == 0 || strncmp(cursor, "set:", 4) == 0) {
|
||||||
|
cursor += 4;
|
||||||
|
}
|
||||||
|
if (*cursor == '_') {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
intptr_t written = 0;
|
||||||
|
while (*cursor != '\0' && *cursor != '@') {
|
||||||
|
if (!IsObfuscationNameChar(*cursor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (written >= stem_length - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stem[written++] = *cursor++;
|
||||||
|
}
|
||||||
|
if (written == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stem[written] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
|
||||||
|
|
||||||
|
DART_EXPORT Dart_Handle Dart_SetObfuscationMap(const char* const* map,
|
||||||
|
intptr_t map_length) {
|
||||||
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||||
|
return Api::NewError("No obfuscation map to load on an AOT runtime.");
|
||||||
|
#elif !defined(DART_PRECOMPILER)
|
||||||
|
return Api::NewError("Obfuscation is only supported for AOT compiler.");
|
||||||
|
#elif defined(TARGET_ARCH_IA32)
|
||||||
|
return Api::NewError("Obfuscation is not supported on IA32.");
|
||||||
|
#else
|
||||||
|
Thread* thread = Thread::Current();
|
||||||
|
DARTSCOPE(thread);
|
||||||
|
auto isolate_group = thread->isolate_group();
|
||||||
|
|
||||||
|
if (map == nullptr) {
|
||||||
|
RETURN_NULL_ERROR(map);
|
||||||
|
}
|
||||||
|
if (map_length < 0 || (map_length % 2) != 0) {
|
||||||
|
return Api::NewError(
|
||||||
|
"Obfuscation map must contain an even number of strings.");
|
||||||
|
}
|
||||||
|
if (!isolate_group->obfuscate()) {
|
||||||
|
return Api::NewError(
|
||||||
|
"Obfuscation map can only be loaded when --obfuscate is enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const intptr_t map_entries = map_length / 2;
|
||||||
|
const intptr_t initial_capacity =
|
||||||
|
Utils::Maximum(map_entries, static_cast<intptr_t>(16));
|
||||||
|
ObfuscationMap renames(
|
||||||
|
HashTables::New<ObfuscationMap>(initial_capacity, Heap::kOld));
|
||||||
|
String& key = String::Handle(Z);
|
||||||
|
String& value = String::Handle(Z);
|
||||||
|
char last_name[100];
|
||||||
|
last_name[0] = '\0';
|
||||||
|
|
||||||
|
for (intptr_t i = 0; i < map_length; i += 2) {
|
||||||
|
if (map[i] == nullptr || map[i + 1] == nullptr) {
|
||||||
|
renames.Release();
|
||||||
|
return Api::NewError("Obfuscation map contains a null string.");
|
||||||
|
}
|
||||||
|
key = Symbols::New(thread, map[i]);
|
||||||
|
value = Symbols::New(thread, map[i + 1]);
|
||||||
|
renames.UpdateOrInsert(key, value);
|
||||||
|
|
||||||
|
if (strcmp(map[i], map[i + 1]) != 0) {
|
||||||
|
char stem[100];
|
||||||
|
if (ExtractObfuscationRenameStem(map[i + 1], stem, sizeof(stem)) &&
|
||||||
|
(last_name[0] == '\0' || IsObfuscationNameGreater(stem, last_name))) {
|
||||||
|
strncpy(last_name, stem, sizeof(last_name));
|
||||||
|
last_name[sizeof(last_name) - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array& state = Array::Handle(Z, Array::New(2, Heap::kOld));
|
||||||
|
state.SetAt(0, String::Handle(Z, String::New(last_name, Heap::kOld)));
|
||||||
|
state.SetAt(1, renames.Release());
|
||||||
|
isolate_group->object_store()->set_obfuscation_map(state);
|
||||||
|
return Api::Success();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
DART_EXPORT Dart_Handle Dart_GetObfuscationMap(uint8_t** buffer,
|
DART_EXPORT Dart_Handle Dart_GetObfuscationMap(uint8_t** buffer,
|
||||||
intptr_t* buffer_length) {
|
intptr_t* buffer_length) {
|
||||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||||
@@ -7163,6 +7290,489 @@ DART_EXPORT Dart_Handle Dart_GetObfuscationMap(uint8_t** buffer,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dart_AotPatchKeyCallback g_aot_patch_key_callback = nullptr;
|
||||||
|
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING) && defined(DART_DYNAMIC_MODULES)
|
||||||
|
#error DART_ENABLE_AOT_PATCHING must be built without DART_DYNAMIC_MODULES.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
struct AotPatchJsonString {
|
||||||
|
const char* chars;
|
||||||
|
intptr_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool IsAotPatchJsonWhitespace(char c) {
|
||||||
|
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SkipAotPatchJsonWhitespace(const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
intptr_t* cursor) {
|
||||||
|
while (*cursor < json_length && IsAotPatchJsonWhitespace(json[*cursor])) {
|
||||||
|
(*cursor)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseAotPatchJsonString(const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
intptr_t* cursor,
|
||||||
|
AotPatchJsonString* out) {
|
||||||
|
if (*cursor >= json_length || json[*cursor] != '"') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(*cursor)++;
|
||||||
|
const intptr_t start = *cursor;
|
||||||
|
while (*cursor < json_length) {
|
||||||
|
const char c = json[*cursor];
|
||||||
|
if (c == '\\') {
|
||||||
|
// Open patch artifacts use stable ASCII metadata. Reject escaped fields
|
||||||
|
// in the VM validator instead of accepting ambiguous raw comparisons.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (c == '"') {
|
||||||
|
out->chars = json + start;
|
||||||
|
out->length = *cursor - start;
|
||||||
|
(*cursor)++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (static_cast<unsigned char>(c) < 0x20) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(*cursor)++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AotPatchJsonStringEquals(const AotPatchJsonString& value,
|
||||||
|
const char* expected) {
|
||||||
|
const intptr_t expected_length = strlen(expected);
|
||||||
|
return value.length == expected_length &&
|
||||||
|
strncmp(value.chars, expected, value.length) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool FindAotPatchJsonString(const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
const char* key,
|
||||||
|
AotPatchJsonString* out) {
|
||||||
|
intptr_t cursor = 0;
|
||||||
|
while (cursor < json_length) {
|
||||||
|
if (json[cursor] != '"') {
|
||||||
|
cursor++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AotPatchJsonString current_key;
|
||||||
|
if (!ParseAotPatchJsonString(json, json_length, &cursor, ¤t_key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SkipAotPatchJsonWhitespace(json, json_length, &cursor);
|
||||||
|
if (cursor >= json_length || json[cursor] != ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cursor++;
|
||||||
|
SkipAotPatchJsonWhitespace(json, json_length, &cursor);
|
||||||
|
|
||||||
|
if (!AotPatchJsonStringEquals(current_key, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return ParseAotPatchJsonString(json, json_length, &cursor, out);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dart_Handle ValidateAotPatchJsonField(const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
const char* field,
|
||||||
|
const char* expected) {
|
||||||
|
AotPatchJsonString actual;
|
||||||
|
if (!FindAotPatchJsonString(json, json_length, field, &actual)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing field \"%s\".", field);
|
||||||
|
}
|
||||||
|
if (!AotPatchJsonStringEquals(actual, expected)) {
|
||||||
|
return Api::NewError("AOT patch artifact field \"%s\" does not match.",
|
||||||
|
field);
|
||||||
|
}
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* CopyAotPatchJsonString(Thread* thread,
|
||||||
|
const AotPatchJsonString& value) {
|
||||||
|
char* copy = thread->zone()->Alloc<char>(value.length + 1);
|
||||||
|
memmove(copy, value.chars, value.length);
|
||||||
|
copy[value.length] = '\0';
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AotPatchOwnedBuffer {
|
||||||
|
uint8_t* data = nullptr;
|
||||||
|
intptr_t length = 0;
|
||||||
|
|
||||||
|
~AotPatchOwnedBuffer() { free(data); }
|
||||||
|
|
||||||
|
uint8_t* Release() {
|
||||||
|
uint8_t* result = data;
|
||||||
|
data = nullptr;
|
||||||
|
length = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Dart_Handle DecodeAotPatchBase64(Thread* thread,
|
||||||
|
const AotPatchJsonString& value,
|
||||||
|
const char* field,
|
||||||
|
AotPatchOwnedBuffer* out) {
|
||||||
|
if (value.length == 0) {
|
||||||
|
out->data = reinterpret_cast<uint8_t*>(malloc(1));
|
||||||
|
out->length = 0;
|
||||||
|
if (out->data == nullptr) {
|
||||||
|
return Api::NewError("Unable to allocate AOT patch field \"%s\".", field);
|
||||||
|
}
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
intptr_t decoded_length = 0;
|
||||||
|
uint8_t* decoded =
|
||||||
|
DecodeBase64(CopyAotPatchJsonString(thread, value), &decoded_length);
|
||||||
|
if (decoded == nullptr) {
|
||||||
|
return Api::NewError("AOT patch field \"%s\" is not valid base64.", field);
|
||||||
|
}
|
||||||
|
out->data = decoded;
|
||||||
|
out->length = decoded_length;
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static intptr_t AotPatchHexDigit(char c) {
|
||||||
|
if ('0' <= c && c <= '9') return c - '0';
|
||||||
|
if ('a' <= c && c <= 'f') return 10 + (c - 'a');
|
||||||
|
if ('A' <= c && c <= 'F') return 10 + (c - 'A');
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AotPatchDigestEqualsHex(const uint8_t* digest,
|
||||||
|
intptr_t digest_length,
|
||||||
|
const AotPatchJsonString& expected) {
|
||||||
|
if (expected.length != digest_length * 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (intptr_t i = 0; i < digest_length; i++) {
|
||||||
|
const intptr_t high = AotPatchHexDigit(expected.chars[i * 2]);
|
||||||
|
const intptr_t low = AotPatchHexDigit(expected.chars[(i * 2) + 1]);
|
||||||
|
if (high < 0 || low < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (digest[i] != static_cast<uint8_t>((high << 4) | low)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AppendAotPatchJsonString(ZoneTextBuffer* buffer,
|
||||||
|
const AotPatchJsonString& value) {
|
||||||
|
buffer->AddChar('"');
|
||||||
|
buffer->AddEscapedUTF8(value.chars, value.length);
|
||||||
|
buffer->AddChar('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dart_Handle AppendAotPatchMetadataField(ZoneTextBuffer* buffer,
|
||||||
|
const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
const char* field,
|
||||||
|
bool required,
|
||||||
|
bool* first) {
|
||||||
|
AotPatchJsonString value;
|
||||||
|
if (!FindAotPatchJsonString(json, json_length, field, &value)) {
|
||||||
|
if (required) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing field \"%s\".",
|
||||||
|
field);
|
||||||
|
}
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
if (!*first) {
|
||||||
|
buffer->AddChar(',');
|
||||||
|
}
|
||||||
|
*first = false;
|
||||||
|
buffer->Printf("\"%s\":", field);
|
||||||
|
AppendAotPatchJsonString(buffer, value);
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dart_Handle BuildAotPatchMetadataAad(const char* json,
|
||||||
|
intptr_t json_length,
|
||||||
|
ZoneTextBuffer* out) {
|
||||||
|
bool first = true;
|
||||||
|
out->AddChar('{');
|
||||||
|
#define APPEND_FIELD(name, required) \
|
||||||
|
do { \
|
||||||
|
Dart_Handle append_result = AppendAotPatchMetadataField( \
|
||||||
|
out, json, json_length, name, required, &first); \
|
||||||
|
if (Api::IsError(append_result)) return append_result; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
APPEND_FIELD("app_build_id", true);
|
||||||
|
APPEND_FIELD("app_id", true);
|
||||||
|
APPEND_FIELD("base_flavor_id", false);
|
||||||
|
APPEND_FIELD("base_license_type", false);
|
||||||
|
APPEND_FIELD("base_snapshot_hash", true);
|
||||||
|
APPEND_FIELD("flavor_id", true);
|
||||||
|
APPEND_FIELD("license_type", true);
|
||||||
|
APPEND_FIELD("obfuscation_map_hash", false);
|
||||||
|
APPEND_FIELD("patch_snapshot_hash", true);
|
||||||
|
APPEND_FIELD("sdk_hash", true);
|
||||||
|
APPEND_FIELD("target_arch", true);
|
||||||
|
APPEND_FIELD("target_os", true);
|
||||||
|
#undef APPEND_FIELD
|
||||||
|
out->AddChar('}');
|
||||||
|
return Api::Success();
|
||||||
|
}
|
||||||
|
#endif // defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
|
||||||
|
DART_EXPORT bool Dart_AotPatchingEnabled() {
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DART_EXPORT void Dart_SetAotPatchKeyCallback(
|
||||||
|
Dart_AotPatchKeyCallback callback) {
|
||||||
|
g_aot_patch_key_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
DART_EXPORT Dart_Handle
|
||||||
|
Dart_InstallAotPatch(const uint8_t* patch_buffer,
|
||||||
|
intptr_t patch_buffer_length,
|
||||||
|
const Dart_AotPatchInstallOptions* options,
|
||||||
|
uint8_t** patch_payload_buffer,
|
||||||
|
intptr_t* patch_payload_length) {
|
||||||
|
#if !defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
return Api::NewError(
|
||||||
|
"Compact AOT patching is not enabled in this VM. Rebuild with "
|
||||||
|
"dart_enable_aot_patching=true.");
|
||||||
|
#else
|
||||||
|
Thread* thread = Thread::Current();
|
||||||
|
DARTSCOPE(thread);
|
||||||
|
API_TIMELINE_DURATION(thread);
|
||||||
|
|
||||||
|
if (patch_buffer == nullptr) {
|
||||||
|
RETURN_NULL_ERROR(patch_buffer);
|
||||||
|
}
|
||||||
|
if (patch_buffer_length <= 0) {
|
||||||
|
return Api::NewError("Patch buffer must not be empty.");
|
||||||
|
}
|
||||||
|
if (options == nullptr) {
|
||||||
|
RETURN_NULL_ERROR(options);
|
||||||
|
}
|
||||||
|
if (patch_payload_buffer == nullptr) {
|
||||||
|
RETURN_NULL_ERROR(patch_payload_buffer);
|
||||||
|
}
|
||||||
|
if (patch_payload_length == nullptr) {
|
||||||
|
RETURN_NULL_ERROR(patch_payload_length);
|
||||||
|
}
|
||||||
|
*patch_payload_buffer = nullptr;
|
||||||
|
*patch_payload_length = 0;
|
||||||
|
if (options->app_id == nullptr || options->app_build_id == nullptr ||
|
||||||
|
options->flavor_id == nullptr || options->license_type == nullptr ||
|
||||||
|
options->sdk_hash == nullptr || options->base_snapshot_hash == nullptr ||
|
||||||
|
options->patch_snapshot_hash == nullptr ||
|
||||||
|
options->target_os == nullptr || options->target_arch == nullptr) {
|
||||||
|
return Api::NewError("AOT patch install options are incomplete.");
|
||||||
|
}
|
||||||
|
if (g_aot_patch_key_callback == nullptr) {
|
||||||
|
return Api::NewError("No AOT patch AES key callback has been configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* json = reinterpret_cast<const char*>(patch_buffer);
|
||||||
|
Dart_Handle result = ValidateAotPatchJsonField(
|
||||||
|
json, patch_buffer_length, "format", "open-aot-vmcode-encrypted-v1");
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "app_id",
|
||||||
|
options->app_id);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "app_build_id",
|
||||||
|
options->app_build_id);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
if (options->base_flavor_id != nullptr) {
|
||||||
|
result = ValidateAotPatchJsonField(
|
||||||
|
json, patch_buffer_length, "base_flavor_id", options->base_flavor_id);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
}
|
||||||
|
if (options->base_license_type != nullptr) {
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length,
|
||||||
|
"base_license_type",
|
||||||
|
options->base_license_type);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
}
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "flavor_id",
|
||||||
|
options->flavor_id);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "license_type",
|
||||||
|
options->license_type);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "sdk_hash",
|
||||||
|
options->sdk_hash);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result =
|
||||||
|
ValidateAotPatchJsonField(json, patch_buffer_length, "base_snapshot_hash",
|
||||||
|
options->base_snapshot_hash);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length,
|
||||||
|
"patch_snapshot_hash",
|
||||||
|
options->patch_snapshot_hash);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "target_os",
|
||||||
|
options->target_os);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "target_arch",
|
||||||
|
options->target_arch);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
if (options->obfuscation_map_hash != nullptr) {
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length,
|
||||||
|
"obfuscation_map_hash",
|
||||||
|
options->obfuscation_map_hash);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AotPatchJsonString key_id;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length, "key_id", &key_id)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing encryption key_id.");
|
||||||
|
}
|
||||||
|
if (key_id.length == 0) {
|
||||||
|
return Api::NewError("AOT patch artifact encryption key_id is empty.");
|
||||||
|
}
|
||||||
|
result = ValidateAotPatchJsonField(json, patch_buffer_length, "algorithm",
|
||||||
|
"AES-256-GCM");
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
AotPatchJsonString encrypted_payload;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length,
|
||||||
|
"encrypted_payload_base64", &encrypted_payload)) {
|
||||||
|
return Api::NewError(
|
||||||
|
"AOT patch artifact is missing encrypted_payload_base64.");
|
||||||
|
}
|
||||||
|
AotPatchJsonString nonce;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length, "nonce_base64",
|
||||||
|
&nonce)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing nonce_base64.");
|
||||||
|
}
|
||||||
|
AotPatchJsonString tag;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length, "tag_base64", &tag)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing tag_base64.");
|
||||||
|
}
|
||||||
|
AotPatchJsonString aad_sha256;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length, "aad_sha256",
|
||||||
|
&aad_sha256)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing aad_sha256.");
|
||||||
|
}
|
||||||
|
AotPatchJsonString payload_sha256;
|
||||||
|
if (!FindAotPatchJsonString(json, patch_buffer_length, "payload_sha256",
|
||||||
|
&payload_sha256)) {
|
||||||
|
return Api::NewError("AOT patch artifact is missing payload_sha256.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AotPatchOwnedBuffer encrypted_payload_buffer;
|
||||||
|
result = DecodeAotPatchBase64(thread, encrypted_payload,
|
||||||
|
"encrypted_payload_base64",
|
||||||
|
&encrypted_payload_buffer);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
AotPatchOwnedBuffer nonce_buffer;
|
||||||
|
result = DecodeAotPatchBase64(thread, nonce, "nonce_base64", &nonce_buffer);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
if (nonce_buffer.length != 12) {
|
||||||
|
return Api::NewError("AOT patch AES-GCM nonce must be 12 bytes.");
|
||||||
|
}
|
||||||
|
AotPatchOwnedBuffer tag_buffer;
|
||||||
|
result = DecodeAotPatchBase64(thread, tag, "tag_base64", &tag_buffer);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
if (tag_buffer.length != 16) {
|
||||||
|
return Api::NewError("AOT patch AES-GCM tag must be 16 bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ZoneTextBuffer aad(thread->zone(), 512);
|
||||||
|
result = BuildAotPatchMetadataAad(json, patch_buffer_length, &aad);
|
||||||
|
if (Api::IsError(result)) return result;
|
||||||
|
uint8_t aad_digest[SHA256_DIGEST_LENGTH];
|
||||||
|
SHA256(reinterpret_cast<const uint8_t*>(aad.buffer()), aad.length(),
|
||||||
|
aad_digest);
|
||||||
|
if (!AotPatchDigestEqualsHex(aad_digest, SHA256_DIGEST_LENGTH, aad_sha256)) {
|
||||||
|
return Api::NewError("AOT patch artifact metadata AAD hash mismatch.");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t key_buffer[32];
|
||||||
|
intptr_t key_length = 0;
|
||||||
|
const bool key_ok =
|
||||||
|
g_aot_patch_key_callback(CopyAotPatchJsonString(thread, key_id),
|
||||||
|
key_buffer, sizeof(key_buffer), &key_length);
|
||||||
|
if (!key_ok || key_length != 32) {
|
||||||
|
memset(key_buffer, 0, sizeof(key_buffer));
|
||||||
|
return Api::NewError(
|
||||||
|
"AOT patch AES key callback failed to provide a 32-byte key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AotPatchOwnedBuffer sealed_payload;
|
||||||
|
sealed_payload.length = encrypted_payload_buffer.length + tag_buffer.length;
|
||||||
|
sealed_payload.data = reinterpret_cast<uint8_t*>(
|
||||||
|
malloc(Utils::Maximum<intptr_t>(sealed_payload.length, 1)));
|
||||||
|
if (sealed_payload.data == nullptr) {
|
||||||
|
return Api::NewError("Unable to allocate AOT patch sealed payload.");
|
||||||
|
}
|
||||||
|
memmove(sealed_payload.data, encrypted_payload_buffer.data,
|
||||||
|
encrypted_payload_buffer.length);
|
||||||
|
memmove(sealed_payload.data + encrypted_payload_buffer.length,
|
||||||
|
tag_buffer.data, tag_buffer.length);
|
||||||
|
|
||||||
|
AotPatchOwnedBuffer decrypted_payload_buffer;
|
||||||
|
decrypted_payload_buffer.length = sealed_payload.length;
|
||||||
|
decrypted_payload_buffer.data = reinterpret_cast<uint8_t*>(
|
||||||
|
malloc(Utils::Maximum<intptr_t>(decrypted_payload_buffer.length, 1)));
|
||||||
|
if (decrypted_payload_buffer.data == nullptr) {
|
||||||
|
return Api::NewError("Unable to allocate AOT patch payload.");
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_AEAD_CTX ctx;
|
||||||
|
memset(&ctx, 0, sizeof(ctx));
|
||||||
|
if (EVP_AEAD_CTX_init(&ctx, EVP_aead_aes_256_gcm(), key_buffer,
|
||||||
|
sizeof(key_buffer), tag_buffer.length, nullptr) != 1) {
|
||||||
|
memset(key_buffer, 0, sizeof(key_buffer));
|
||||||
|
return Api::NewError("Failed to initialize AOT patch AES-GCM context.");
|
||||||
|
}
|
||||||
|
size_t decrypted_length = 0;
|
||||||
|
const int decrypt_ok = EVP_AEAD_CTX_open(
|
||||||
|
&ctx, decrypted_payload_buffer.data, &decrypted_length,
|
||||||
|
decrypted_payload_buffer.length, nonce_buffer.data, nonce_buffer.length,
|
||||||
|
sealed_payload.data, sealed_payload.length,
|
||||||
|
reinterpret_cast<const uint8_t*>(aad.buffer()), aad.length());
|
||||||
|
EVP_AEAD_CTX_cleanup(&ctx);
|
||||||
|
memset(key_buffer, 0, sizeof(key_buffer));
|
||||||
|
if (decrypt_ok != 1) {
|
||||||
|
return Api::NewError("AOT patch AES-GCM decryption failed.");
|
||||||
|
}
|
||||||
|
decrypted_payload_buffer.length = decrypted_length;
|
||||||
|
|
||||||
|
uint8_t payload_digest[SHA256_DIGEST_LENGTH];
|
||||||
|
SHA256(decrypted_payload_buffer.data, decrypted_payload_buffer.length,
|
||||||
|
payload_digest);
|
||||||
|
if (!AotPatchDigestEqualsHex(payload_digest, SHA256_DIGEST_LENGTH,
|
||||||
|
payload_sha256)) {
|
||||||
|
return Api::NewError("AOT patch decrypted payload hash mismatch.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This VM API validates the open artifact envelope and key contract. The
|
||||||
|
// iOS-safe AOT patch model maps patched isolate data/instructions before
|
||||||
|
// isolate startup, so embedders should install the decrypted artifact through
|
||||||
|
// their snapshot mapping path rather than mutating executable code here.
|
||||||
|
*patch_payload_buffer = decrypted_payload_buffer.Release();
|
||||||
|
*patch_payload_length = static_cast<intptr_t>(decrypted_length);
|
||||||
|
return Api::Success();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DART_EXPORT void Dart_FreeAotPatchPayload(uint8_t* patch_payload_buffer) {
|
||||||
|
free(patch_payload_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
DART_EXPORT bool Dart_IsPrecompiledRuntime() {
|
DART_EXPORT bool Dart_IsPrecompiledRuntime() {
|
||||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -297,7 +297,8 @@ TEST_CASE(DartAPI_IsolateOwnership) {
|
|||||||
EXPECT_EQ(false, Dart_GetCurrentThreadOwnsIsolate(ILLEGAL_PORT));
|
EXPECT_EQ(false, Dart_GetCurrentThreadOwnsIsolate(ILLEGAL_PORT));
|
||||||
|
|
||||||
Dart_ShutdownIsolate();
|
Dart_ShutdownIsolate();
|
||||||
}).join();
|
})
|
||||||
|
.join();
|
||||||
|
|
||||||
EXPECT_EQ(true, Dart_GetCurrentThreadOwnsIsolate(port));
|
EXPECT_EQ(true, Dart_GetCurrentThreadOwnsIsolate(port));
|
||||||
EXPECT_EQ(false, Dart_GetCurrentThreadOwnsIsolate(other_port));
|
EXPECT_EQ(false, Dart_GetCurrentThreadOwnsIsolate(other_port));
|
||||||
@@ -352,7 +353,8 @@ TEST_CASE_WITH_EXPECTATION(
|
|||||||
// Causes an assertion failure, because the isolate is already owned by
|
// Causes an assertion failure, because the isolate is already owned by
|
||||||
// another thread.
|
// another thread.
|
||||||
Dart_EnterIsolate(isolate);
|
Dart_EnterIsolate(isolate);
|
||||||
}).join();
|
})
|
||||||
|
.join();
|
||||||
|
|
||||||
Dart_EnterIsolate(isolate);
|
Dart_EnterIsolate(isolate);
|
||||||
}
|
}
|
||||||
@@ -10913,6 +10915,88 @@ TEST_CASE(Dart_SetFfiNativeResolver_DoesNotResolve) {
|
|||||||
EXPECT_ERROR(result, "Couldn't resolve function: 'DoesNotResolve'");
|
EXPECT_ERROR(result, "Couldn't resolve function: 'DoesNotResolve'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
#if defined(DART_DYNAMIC_MODULES)
|
||||||
|
#error DART_ENABLE_AOT_PATCHING must not require DART_DYNAMIC_MODULES.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool TestAotPatchKeyCallback(const char* key_id,
|
||||||
|
uint8_t* key_buffer,
|
||||||
|
intptr_t key_buffer_length,
|
||||||
|
intptr_t* key_length) {
|
||||||
|
EXPECT_STREQ(key_id, "test-key");
|
||||||
|
EXPECT(key_buffer_length >= 32);
|
||||||
|
memset(key_buffer, 0x42, 32);
|
||||||
|
*key_length = 32;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
|
||||||
|
TEST_CASE(DartAPI_AotPatchingConfiguration) {
|
||||||
|
const char patch[] = R"json({
|
||||||
|
"format": "open-aot-vmcode-encrypted-v1",
|
||||||
|
"metadata": {
|
||||||
|
"app_id": "app.test",
|
||||||
|
"app_build_id": "1",
|
||||||
|
"base_flavor_id": "free",
|
||||||
|
"base_license_type": "free",
|
||||||
|
"flavor_id": "pro",
|
||||||
|
"license_type": "pro",
|
||||||
|
"sdk_hash": "sdk",
|
||||||
|
"base_snapshot_hash": "cae662172fd450bb0cd710a769079c05bfc5d8e35efa6576edc7d0377afdd4a2",
|
||||||
|
"patch_snapshot_hash": "05d9426b9dd03e5cc3404aab6c7c45ac24e0b90e840f4bd6da83c342430533dc",
|
||||||
|
"target_os": "windows",
|
||||||
|
"target_arch": "x64"
|
||||||
|
},
|
||||||
|
"payload_kind": "full-snapshot",
|
||||||
|
"reconstructed_size": 13,
|
||||||
|
"payload_sha256": "05d9426b9dd03e5cc3404aab6c7c45ac24e0b90e840f4bd6da83c342430533dc",
|
||||||
|
"encrypted_payload_base64": "hNOLmjSh9YbgIyRuVQ==",
|
||||||
|
"encryption": {
|
||||||
|
"algorithm": "AES-256-GCM",
|
||||||
|
"key_id": "test-key",
|
||||||
|
"nonce_base64": "AAAAAAAAAAAAAAAA",
|
||||||
|
"tag_base64": "W8uOg/f+g2SS3Ahxfaeuyg==",
|
||||||
|
"aad_sha256": "1a0c6003fec49bbc26fbaaf0a6dfcd557061b4ae5f22e4b1114afe3b1a8d9796"
|
||||||
|
}
|
||||||
|
})json";
|
||||||
|
Dart_AotPatchInstallOptions options = {};
|
||||||
|
options.app_id = "app.test";
|
||||||
|
options.app_build_id = "1";
|
||||||
|
options.base_flavor_id = "free";
|
||||||
|
options.base_license_type = "free";
|
||||||
|
options.flavor_id = "pro";
|
||||||
|
options.license_type = "pro";
|
||||||
|
options.sdk_hash = "sdk";
|
||||||
|
options.base_snapshot_hash =
|
||||||
|
"cae662172fd450bb0cd710a769079c05bfc5d8e35efa6576edc7d0377afdd4a2";
|
||||||
|
options.patch_snapshot_hash =
|
||||||
|
"05d9426b9dd03e5cc3404aab6c7c45ac24e0b90e840f4bd6da83c342430533dc";
|
||||||
|
options.target_os = "windows";
|
||||||
|
options.target_arch = "x64";
|
||||||
|
|
||||||
|
uint8_t* patch_payload = nullptr;
|
||||||
|
intptr_t patch_payload_length = 0;
|
||||||
|
#if defined(DART_ENABLE_AOT_PATCHING)
|
||||||
|
EXPECT(Dart_AotPatchingEnabled());
|
||||||
|
Dart_SetAotPatchKeyCallback(TestAotPatchKeyCallback);
|
||||||
|
Dart_Handle result = Dart_InstallAotPatch(
|
||||||
|
reinterpret_cast<const uint8_t*>(patch), strlen(patch), &options,
|
||||||
|
&patch_payload, &patch_payload_length);
|
||||||
|
EXPECT_VALID(result);
|
||||||
|
EXPECT_EQ(13, patch_payload_length);
|
||||||
|
EXPECT_EQ(0, memcmp("patch-payload", patch_payload, patch_payload_length));
|
||||||
|
Dart_FreeAotPatchPayload(patch_payload);
|
||||||
|
Dart_SetAotPatchKeyCallback(nullptr);
|
||||||
|
#else
|
||||||
|
EXPECT(!Dart_AotPatchingEnabled());
|
||||||
|
Dart_Handle result = Dart_InstallAotPatch(
|
||||||
|
reinterpret_cast<const uint8_t*>(patch), strlen(patch), &options,
|
||||||
|
&patch_payload, &patch_payload_length);
|
||||||
|
EXPECT_ERROR(result, "Compact AOT patching is not enabled");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(DartAPI_UserTags) {
|
TEST_CASE(DartAPI_UserTags) {
|
||||||
Dart_Handle default_tag = Dart_GetDefaultUserTag();
|
Dart_Handle default_tag = Dart_GetDefaultUserTag();
|
||||||
EXPECT_VALID(default_tag);
|
EXPECT_VALID(default_tag);
|
||||||
|
|||||||
@@ -2078,7 +2078,7 @@ SwitchDispatch:
|
|||||||
#undef TARGET
|
#undef TARGET
|
||||||
#endif // !defined(PRODUCT)
|
#endif // !defined(PRODUCT)
|
||||||
default:
|
default:
|
||||||
FATAL1("Undefined opcode: %d\n", op);
|
FATAL("Undefined opcode: %d\n", op);
|
||||||
}
|
}
|
||||||
#if !defined(PRODUCT)
|
#if !defined(PRODUCT)
|
||||||
SwitchDispatchNoSingleStep:
|
SwitchDispatchNoSingleStep:
|
||||||
@@ -2089,7 +2089,7 @@ SwitchDispatchNoSingleStep:
|
|||||||
KERNEL_BYTECODES_LIST(TARGET)
|
KERNEL_BYTECODES_LIST(TARGET)
|
||||||
#undef TARGET
|
#undef TARGET
|
||||||
default:
|
default:
|
||||||
FATAL1("Undefined opcode: %d\n", op);
|
FATAL("Undefined opcode: %d\n", op);
|
||||||
}
|
}
|
||||||
#endif // !defined(PRODUCT)
|
#endif // !defined(PRODUCT)
|
||||||
#endif // defined(DART_HAS_COMPUTED_GOTO)
|
#endif // defined(DART_HAS_COMPUTED_GOTO)
|
||||||
|
|||||||
Reference in New Issue
Block a user