[vm,ffi] Avoid linking test DLLs to dart.exe

The `ffi_test_functions` shared library needs to access functions from
`dart_api.h` and `dart_native_api.h`, which are only available in
the Dart executable.

UNIX shared libraries can have undefined symbols, which are resolved at
runtime and can be found in the loading executable. Windows DLLs cannot
have undefined symbols, but they can be dynamically linked to an
executable (in this case `dart.exe`). This requires the DLL to be able
to find the executable at runtime.

A better solution is to include implementations for the Dart APIs in the
DLL itself, that use `GetModuleHandle(NULL)` to get a handle to the
executable and `GetProcAddress` to get the address of the function.

This is what `dart_api_win.c` does.

Fixes https://github.com/dart-lang/sdk/issues/40579
Fixes https://github.com/dart-lang/sdk/issues/59677

TEST=ci

Cq-Include-Trybots: luci.dart.try:vm-win-release-ia32-try,vm-win-debug-x64c-try,vm-win-debug-x64-try,vm-win-debug-arm64-try,vm-msvc-windows-try,vm-aot-win-debug-x64c-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-product-x64-try,vm-aot-win-release-x64-try,vm-aot-win-release-arm64-try,pkg-win-release-try,pkg-win-release-arm64-try,dart-sdk-win-try,dart-sdk-win-arm64-try

Change-Id: I7f971a8ce21e03d18ed2967e74998f925c9236b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400582
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Thomas <athom@google.com>
This commit is contained in:
Gabriel Terwesten
2025-01-07 08:15:39 -08:00
committed by Commit Queue
parent 1830689c80
commit 5c1796f4e0
9 changed files with 2861 additions and 29 deletions
+30
View File
@@ -529,6 +529,35 @@ def _CheckDevCompilerSync(input_api, output_api):
return []
def _CheckDartApiWinCSync(input_api, output_api):
"""Ensure that dart_api_win.c is up-to-date."""
GENERATOR = "runtime/tools/generate_dart_api_win_c.dart"
DART_API_H = "runtime/include/dart_api.h"
DART_NATIVe_API_H = "runtime/include/dart_native_api.h"
files = [git_file.LocalPath() for git_file in input_api.AffectedTextFiles()]
if (GENERATOR in files or DART_API_H in files or
DART_NATIVe_API_H in files):
# Run the generator with `--check-up-to-date` to see if the output is
# up-to-date.
args = [
"tools/sdks/dart-sdk/bin/dart",
GENERATOR,
"--check-up-to-date",
]
try:
subprocess.run(args, check=True)
except subprocess.CalledProcessError as e:
return [
output_api.PresubmitError(
f"Make sure to re-run {GENERATOR} when it or its inputs "
"change.")
]
return []
def _CommonChecks(input_api, output_api):
results = []
results.extend(_CheckValidHostsInDEPS(input_api, output_api))
@@ -544,6 +573,7 @@ def _CommonChecks(input_api, output_api):
results.extend(_CheckAnalyzerFiles(input_api, output_api))
results.extend(_CheckNoNewObservatoryServiceTests(input_api, output_api))
results.extend(_CheckDevCompilerSync(input_api, output_api))
results.extend(_CheckDartApiWinCSync(input_api, output_api))
return results
+8 -7
View File
@@ -1123,6 +1123,9 @@ executable("run_vm_tests") {
shared_library("entrypoints_verification_test") {
deps = [ ":dart" ]
sources = [ "entrypoints_verification_test.cc" ]
if (is_win) {
sources += [ "dart_api_win.c" ]
}
include_dirs = [ ".." ]
}
@@ -1149,19 +1152,17 @@ shared_library("ffi_test_functions") {
"ffi_test/ffi_test_functions_generated_2.cc",
"ffi_test/ffi_test_functions_vmspecific.cc",
]
if (is_win) {
sources += [ "dart_api_win.c" ]
}
if (is_win && current_cpu == "x64") {
sources += [ "ffi_test/clobber_x64_win.S" ]
} else if (!is_win) {
sources += [ "ffi_test/clobber_$current_cpu.S" ]
}
include_dirs = [ ".." ]
if (is_win) {
# TODO(dartbug.com/40579): This wrongly links in dart.exe on precompiled.
libs = [ "dart.lib" ]
abs_root_out_dir = rebase_path(root_out_dir)
ldflags = [ "/LIBPATH:$abs_root_out_dir" ]
}
}
# DartLibFuzzer only "exists" for restricted configurations.
File diff suppressed because it is too large Load Diff
@@ -33,9 +33,6 @@
#include <iostream>
#include <limits>
// TODO(dartbug.com/40579): This requires static linking to either link
// dart.exe or dartaotruntime.exe on Windows.
// The sample currently fails on Windows in AOT mode.
#include "include/dart_api.h"
#include "include/dart_native_api.h"
-3
View File
@@ -279,9 +279,6 @@ dart/kernel_determinism_test: SkipSlow
dart/regress_48196_test: SkipSlow
dart/regress_52703_test: SkipSlow
[ $compiler == dartkp && $system == windows ]
dart/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
[ $compiler == dartkp && $simulator ]
dart/awaiter_stacks/stream_methods_test/1: Pass, Slow
dart/isolates/fast_object_copy2_test*: Skip # Uses ffi which is not available on simulated architectures
+214
View File
@@ -0,0 +1,214 @@
// 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.
import 'dart:io';
import 'dart:ffi';
final repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
final runtimeRoot = '$repoRoot/runtime';
final buildtoolsRoot = '$repoRoot/buildtools';
final clangBinDir =
'$buildtoolsRoot/$currentPlatformBuildtoolsSubdir/clang/bin';
final clangFormatBin = '$clangBinDir/clang-format';
final currentPlatformBuildtoolsSubdir = switch (Abi.current()) {
Abi.macosX64 => 'mac-x64',
Abi.macosArm64 => 'mac-arm64',
Abi.linuxX64 => 'linux-x64',
Abi.linuxArm64 => 'linux-arm64',
Abi.windowsX64 => 'win-x64',
Abi.windowsArm64 => 'win-arm64',
_ => throw UnimplementedError(),
};
void main(List<String> args) {
final checkUpToDate = args.contains('--check-up-to-date');
final dartApiHFile = File('$runtimeRoot/include/dart_api.h');
final dartNativeApiHFile = File('$runtimeRoot/include/dart_native_api.h');
final dartApiWinCFile = File('$runtimeRoot/bin/dart_api_win.c');
final dartApiWinCTmpFile = File('$runtimeRoot/bin/dart_api_win_tmp.c');
final dartApiHContents = dartApiHFile.readAsStringSync();
final dartNativeApiHContents = dartNativeApiHFile.readAsStringSync();
final procedureRegexp = RegExp(
r'(DART_\w+\s+)+(?<returnType>[\w\s\*]+)\s+(?<name>\w+)\((?<arguments>[^)]*)\);',
multiLine: true,
);
final matches = [
...procedureRegexp.allMatches(dartApiHContents),
...procedureRegexp.allMatches(dartNativeApiHContents),
];
final procedures = <Procedure>[];
for (final match in matches) {
final returnType = match.namedGroup('returnType')!;
final name = match.namedGroup('name')!;
final argumentsString = match.namedGroup('arguments') ?? '';
final argumentList =
argumentsString.split(',').where((arg) => arg != 'void').map((arg) {
final parts = arg.trim().split(' ');
return (
type: parts.sublist(0, parts.length - 1).join(' '),
name: parts[parts.length - 1],
);
}).toList();
procedures.add((
name: name,
returnType: returnType,
arguments: argumentList,
));
}
final buffer = StringBuffer();
buffer.writeln('''
// 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.
// DO NOT EDIT. This file is generated by runtime/tools/generate_dart_api_win_c.dart.
#include <windows.h>
#include <include/dart_api.h>
#include <include/dart_native_api.h>
''');
// Generate typedefs for all procedures.
for (final procedure in procedures) {
buffer.write('typedef ');
buffer.write(procedure.returnType);
buffer.write(' (*');
buffer.write(procedure.typedefName);
buffer.write(')(');
for (final (i, argument) in procedure.arguments.indexed) {
buffer.write(argument.type);
if (i < procedure.arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(');');
}
buffer.writeln();
// Generate function pointers for all procedures.
for (final procedure in procedures) {
buffer.write('static ');
buffer.write(procedure.typedefName);
buffer.write(' ');
buffer.write(procedure.functionPointerName);
buffer.writeln(' = NULL;');
}
buffer.writeln();
// Generate the DllMain function that initializes all function pointers.
buffer.writeln('''
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
if (fdwReason == DLL_PROCESS_ATTACH) {
HMODULE process = GetModuleHandle(NULL);
''');
for (final procedure in procedures) {
buffer.write(' ');
buffer.write(procedure.functionPointerName);
buffer.write(' = (');
buffer.write(procedure.typedefName);
buffer.write(') GetProcAddress(process, "');
buffer.write(procedure.name);
buffer.writeln('");');
}
buffer.writeln('''
}
return TRUE;
}''');
buffer.writeln();
// Generate redirecting implementations for all procedures.
for (final procedure in procedures) {
final (:name, :returnType, :arguments) = procedure;
buffer.write(returnType);
buffer.write(' ');
buffer.write(name);
buffer.write('(');
for (final (i, (:name, :type)) in arguments.indexed) {
buffer.write(type);
buffer.write(' ');
buffer.write(name);
if (i < arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(') {');
buffer.write(' ');
if (procedure.returnType != 'void') {
buffer.write('return ');
}
buffer.write(procedure.functionPointerName);
buffer.write('(');
for (final (i, argument) in arguments.indexed) {
buffer.write(argument.name);
if (i < arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(');');
buffer.writeln('}');
buffer.writeln();
}
buffer.writeln();
dartApiWinCTmpFile.writeAsStringSync(buffer.toString());
try {
// Run clang-format on the generated file.
final clangFormatResult = Process.runSync(
clangFormatBin,
['-i', dartApiWinCTmpFile.path],
// Allows us to specify the path to the clang-format binary without the
// .exe extension on Windows.
runInShell: Platform.isWindows,
);
if (clangFormatResult.exitCode != 0) {
print(clangFormatResult.stdout);
print(clangFormatResult.stderr);
exitCode = 1;
} else {
final changed =
!dartApiWinCFile.existsSync() ||
dartApiWinCTmpFile.readAsStringSync() !=
dartApiWinCFile.readAsStringSync();
if (changed) {
if (checkUpToDate) {
exitCode = 1;
} else {
dartApiWinCTmpFile.copySync(dartApiWinCFile.path);
}
}
}
} finally {
dartApiWinCTmpFile.deleteSync();
}
}
typedef Procedure =
({
String name,
String returnType,
List<({String name, String type})> arguments,
});
extension on Procedure {
String get typedefName => '${name}Type';
String get functionPointerName => '${name}Fn';
}
-3
View File
@@ -22,9 +22,6 @@ LibTest/collection/ListMixin/ListMixin_class_A01_t03: Slow, Pass
LibTest/core/List/List_class_A01_t02: Slow, Pass
LibTest/core/List/List_class_A01_t03: Slow, Pass
[ $runtime == dart_precompiled && $system == windows ]
LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
[ $runtime == dart_precompiled && $simulator ]
LibTest/collection/ListBase/ListBase_class_A01_t01: SkipSlow # Issue 43036
LibTest/collection/ListMixin/ListMixin_class_A01_t01: SkipSlow # Issue 43036
-12
View File
@@ -79,18 +79,6 @@ native_assets/*: SkipByDesign # Only intended to run on host oses with AOT binar
[ $compiler != dart2analyzer && $compiler != fasta && $runtime != dart_precompiled && $runtime != vm ]
*: SkipByDesign # FFI is a VM-only feature. (This test suite is part of the default set.)
[ $compiler == dartkp && $system == windows ]
vmspecific_ffi_native_handles_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_ffi_native_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_function_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_handle_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_object_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_regress_37100_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_regress_37511_callbacks_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_regress_37511_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_regress_37780_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
vmspecific_regress_51794_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
# These tests trigger and catch an abort (intentionally) and terminate the VM.
# They're incompatible with ASAN because not all memory is freed when aborting and
# with AppJit because the abort the VM before it can generate a snapshot.
-1
View File
@@ -18,7 +18,6 @@ import 'dylib_utils.dart';
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
testLeafCall() {
// Note: This test currently fails on Windows AOT: https://dartbug.com/40579
// Regular calls should transition generated -> native.
final isThreadInGenerated = ffiTestFunctions
.lookupFunction<Int8 Function(), int Function()>("IsThreadInGenerated");