diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index 30e6d1e13..f23a38722 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -1,5 +1,8 @@ ## 0.6.1-wip +- For Android, produce dylibs with page-size set to 16kb by default. + https://github.com/dart-lang/native/issues/1611 + ## 0.6.0 - Address analyzer info diagnostic about multi-line if requiring a block body. diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart index 4bbbe69a3..3b3327059 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart @@ -276,6 +276,9 @@ class RunCBuilder { cppLinkStdLib ?? defaultCppLinkStdLib[config.targetOS]! ], ...linkerOptions?.preSourcesFlags(toolInstance.tool, sourceFiles) ?? [], + // Support Android 15 page size by default, can be overridden by + // passing [flags]. + if (config.targetOS == OS.android) '-Wl,-z,max-page-size=16384', ...flags, for (final MapEntry(key: name, :value) in defines.entries) if (value == null) '-D$name' else '-D$name=$value', diff --git a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart index 2225a8bd9..e44305e44 100644 --- a/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart +++ b/pkgs/native_toolchain_c/test/cbuilder/cbuilder_cross_android_test.dart @@ -67,6 +67,9 @@ void main() { .firstWhere((e) => e.contains('file format')); expect(machine, contains(objdumpFileFormat[target])); } + if (linkMode == DynamicLoadingBundled()) { + await expectPageSize(libUri, 16 * 1024); + } }); } } @@ -95,14 +98,37 @@ void main() { // Identical API levels should lead to an identical binary. expect(bytes2, bytes3); }); + + test('page size override', () async { + const target = Architecture.arm64; + final linkMode = DynamicLoadingBundled(); + const apiLevel1 = flutterAndroidNdkVersionLowestSupported; + final tempUri = await tempDirForTest(); + final outUri = tempUri.resolve('out1/'); + await Directory.fromUri(outUri).create(); + const pageSize = 4 * 1024; + final libUri = await buildLib( + outUri, + target, + apiLevel1, + linkMode, + flags: ['-Wl,-z,max-page-size=$pageSize'], + ); + if (Platform.isMacOS || Platform.isLinux) { + final address = await textSectionAddress(libUri); + expect(address, greaterThanOrEqualTo(pageSize)); + expect(address, isNot(greaterThanOrEqualTo(pageSize * 4))); + } + }); } Future buildLib( Uri tempUri, Architecture targetArchitecture, int androidNdkApi, - LinkMode linkMode, -) async { + LinkMode linkMode, { + List flags = const [], +}) async { final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c'); const name = 'add'; @@ -140,6 +166,7 @@ Future buildLib( name: name, assetName: name, sources: [addCUri.toFilePath()], + flags: flags, ); await cbuilder.run( config: buildConfig, diff --git a/pkgs/native_toolchain_c/test/helpers.dart b/pkgs/native_toolchain_c/test/helpers.dart index 60b758e6f..ac5a51034 100644 --- a/pkgs/native_toolchain_c/test/helpers.dart +++ b/pkgs/native_toolchain_c/test/helpers.dart @@ -255,3 +255,48 @@ Future expectSymbols({ throw UnimplementedError(); } } + +Future textSectionAddress(Uri dylib) async { + if (Platform.isMacOS) { + // Find the line in the objdump output that looks like: + // 11 .text 00000046 00000000000045a0 TEXT + final result = await runProcess( + executable: Uri.file('objdump'), + arguments: ['--headers', dylib.toFilePath()], + logger: logger, + ); + expect(result.exitCode, 0); + final textSection = + result.stdout.split('\n').firstWhere((e) => e.contains('.text')); + final parsed = textSection.split(' ').where((e) => e.isNotEmpty).toList(); + expect(parsed[1], '.text'); + expect(parsed[4], 'TEXT'); + final vma = int.parse(parsed[3], radix: 16); + return vma; + } + if (Platform.isLinux) { + // Find the line in the readelf output that looks like: + // [11] .text PROGBITS 00004328 000328 000064 00 AX 0 0 4 + final result = await readelf(dylib.toFilePath(), 'S'); + final textSection = + result.split('\n').firstWhere((e) => e.contains('.text')); + final parsed = textSection.split(' ').where((e) => e.isNotEmpty).toList(); + expect(parsed[1], '.text'); + expect(parsed[2], 'PROGBITS'); + final addr = int.parse(parsed[3], radix: 16); + return addr; + } + throw UnimplementedError(); +} + +Future expectPageSize( + Uri dylib, + int pageSize, +) async { + if (Platform.isMacOS || Platform.isLinux) { + // If page size is 16kb, the `.text` section address should be + // above 0x4000. With smaller page sizes it's above 0x1000. + final vma = await textSectionAddress(dylib); + expect(vma, greaterThanOrEqualTo(pageSize)); + } +}