Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce the size of __LINKEDIT Export Info section in stripped binaries #18408

Merged
merged 7 commits into from
Jun 12, 2023

Conversation

ivanpovazan
Copy link
Contributor

@ivanpovazan ivanpovazan commented Jun 5, 2023

Description

This PR reduces the application's SOD (size on disk) by making __LINKEDIT Export Info section smaller in the stripped Mach-O binaries.

The feature is controlled by _ExportSymbolsExplicitly MSBuild property and can be disabled by specifying: -p:_ExportSymbolsExplicitly=true

Fixes #18332

Initial problem

It has been noticed that during stripping, the strip tool does not resize the export info section after it removes the symbols. Instead it only zeroes out the entries (achieved by calling prune_trie function):

Thanks @lambdageek for helping to track this down.

Proposed approach

As Xamarin build process already collects all the required symbols needed for the application to run and preserves them during the strip phase, we can use the same file to instruct clang toolchain to export only those symbols via the command line options: -exported_symbols_list <file> (source). This will make the export info section only include what is necessary for the runtime - and at the same time eliminate the problem of the strip tool which does not resize stripped symbols.

Investigation setup

The issue is observable by building and inspecting the test application: https://github.com/xamarin/xamarin-macios/blob/main/tests/dotnet/MySingleView/MySingleView.csproj and targeting iOS platform in Release mode.

Results:

Measure MySingleView - main MySingleView - this PR Diff (%)
SOD (bytes) 13668940 13458476 -1.5%
.ipa (bytes) 4829368 4827928 -0.03%

Even though zeroes are compressed well, the SOD is still affected and unused section takes around 1.5% of the most simplistic app size.
Much bigger impact has been noted when trying out a MAUI iOS template app with NativeAOT where the __LINKEDIT Export Info zeroes take up to 20MB of the SOD, but also with the regular macOS applications: dotnet/runtime#86707

Repros (outdated - see note)

NOTE I will leave the detailed analysis as part of this PR description for reference. However it should be noted that the used make targets listed bellow are outdated and are used during investigation.

Bellow I listed 3 scenarios for reproducing the current app sizes (with and without stripping) and the app size that we can get by using changes of this PR:

  1. Repro current state of MySingleView.app with stripped binary
  2. Repro current state of MySingleView.app with unstripped binary
  3. Repro the new approach (-exported_symbols_list <file> + strip)

The repro steps assume that this PR has been checkout and that tools are invoked from the test sample directory: cd tests/dotnet/MySingleView

Repro current state of MySingleView.app with stripped binary

  1. Build the app (you can ignore the need to run the sample, I just did it to make sure the changes do not break anything)
make run-device
  1. Print the load commands - load_cmds_strip.list
otool -l bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > load_cmds_strip.list
  • We are interested in the export info section:
cmd LC_DYLD_INFO_ONLY
...
export_off 5942960
export_size 207712
  1. Create a hex dump of the export info section - hex_dump_strip.list
xxd -s 5942960 -l 207712 bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > hex_dump_strip.list
  • NOTE: Notice around ~200kb of zeroes from ~ 0x005ab490 to ~ 0x005dda00
  1. Verify exported symbols are correct - dyld_info_strip.list
dyld_info -exports bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > dyld_info_strip.list

Repro current state of MySingleView.app with unstripped binary

  1. Build the app (the make target preserves the symbols)
make run-device-no-strip
  1. Print the load commands - load_cmds_nostrip.list
otool -l bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > load_cmds_nostrip.list
  • We are interested in the export info section:
cmd LC_DYLD_INFO_ONLY
...
export_off 5942960
export_size 207712
  1. Create a hex dump of the export info section - hex_dump_nostrip.list
xxd -s 5942960 -l 207712 bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > hex_dump_nostrip.list
  • Notice that the range: ~ 0x005ab490 to ~ 0x005dda00 now includes exported symbol entries
  1. Verify exported symbols are correct - dyld_info_nostrip.list
dyld_info -exports bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > dyld_info_nostrip.list

Repro the new approach

  1. Build the app (the make target uses the new approach)
make run-device-export-syms
  1. Print the load commands - load_cmds_export.list
otool -l bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > load_cmds_export.list
  • We are interested in the export info section notice the reduced size of the section:
cmd LC_DYLD_INFO_ONLY
...
export_off 5942432
export_size 1048
  1. Create a hex dump of the export info section - hex_dump_export.list
xxd -s 5942432 -l 1048 bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > hex_dump_export.list
  1. Verify exported symbols are correct - dyld_info_export.list
dyld_info -exports bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > dyld_info_export.list

Additional benefits

  • With this approach we could also switch the way strip tool is invoked - to always strip all debug and local symbols via strip -S -x instead of passing the file with symbols to preserve. This would remove the warning that we are currently getting (which is being ignored):
/Applications/Xcode_14.3.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip: warning: removing global symbols from a final linked no longer supported.  Use -exported_symbols_list at link time when building...

Other references:

…round the strip tool not resizing the stripped export section
@ivanpovazan
Copy link
Contributor Author

/cc: @rolfbjarne

@@ -1173,6 +1173,10 @@
</PropertyGroup>

<Target Name="_ComputeLinkNativeExecutableInputs" Condition="'$(IsMacEnabled)' == 'true'">
<PropertyGroup>
<_ExportedSymbolsFile Condition="'$(_ExportedSymbolsFile)' == '' and '$(_MtouchSymbolsList)' == ''">/dev/null</_ExportedSymbolsFile> <!-- nothing to export -->
Copy link
Contributor Author

@ivanpovazan ivanpovazan Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case there are no symbols to export, this workaround works just fine making the export info section empty.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2023

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

dotnet/targets/Xamarin.Shared.Sdk.targets Outdated Show resolved Hide resolved
dotnet/targets/Xamarin.Shared.Sdk.targets Outdated Show resolved Hide resolved
dotnet/targets/Xamarin.Shared.Sdk.targets Outdated Show resolved Hide resolved
@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@ivanpovazan
Copy link
Contributor Author

The CI looks 🆗 should I also try adding this:

Additional benefits
With this approach we could also switch the way strip tool is invoked - to always strip all debug and local symbols via strip -S -x instead of passing the file with symbols to preserve. This would remove the warning that we are currently getting (which is being ignored):

@rolfbjarne
Copy link
Member

The CI looks 🆗 should I also try adding this:

Additional benefits
With this approach we could also switch the way strip tool is invoked - to always strip all debug and local symbols via strip -S -x instead of passing the file with symbols to preserve. This would remove the warning that we are currently getting (which is being ignored):

That should be in a different PR.

@rolfbjarne
Copy link
Member

Actually I think we can just enable this by default, since there's an escape hatch if something goes wrong at some point.

@ivanpovazan ivanpovazan changed the title [DRAFT] Reduce the size of __LINKEDIT Export Info section in stripped binaries Reduce the size of __LINKEDIT Export Info section in stripped binaries Jun 8, 2023
@ivanpovazan ivanpovazan marked this pull request as ready for review June 8, 2023 10:04
@ivanpovazan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Commenter does not have sufficient privileges for PR 18408 in repo xamarin/xamarin-macios

@rolfbjarne
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build] Windows Integration Tests passed 💻

All Windows Integration Tests passed.

Pipeline on Agent
Hash: 3202c8b86cfb3d7f45176ad44e121cbf3eed1bb3 [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

📚 [PR Build] Artifacts 📚

Packages generated

View packages

Pipeline on Agent
Hash: [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [PR Build] Tests on macOS M1 - Mac Big Sur (11.5) passed 💻

All tests on macOS M1 - Mac Big Sur (11.5) passed.

Pipeline on Agent
Hash: [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [PR Build] Tests on macOS M1 - Mac Ventura (13.0) passed 💻

All tests on macOS M1 - Mac Ventura (13.0) passed.

Pipeline on Agent
Hash: [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ API diff for current PR / commit

Legacy Xamarin (No breaking changes)
  • iOS (no change detected)
  • tvOS (no change detected)
  • watchOS (no change detected)
  • macOS (no change detected)
NET (empty diffs)
  • iOS: (empty diff detected)
  • tvOS: (empty diff detected)
  • MacCatalyst: (empty diff detected)
  • macOS: (empty diff detected)

✅ API diff vs stable

Legacy Xamarin (No breaking changes)
.NET (No breaking changes)
Legacy Xamarin (stable) vs .NET

✅ Generator diff

Generator diff is empty

Pipeline on Agent
Hash: 3202c8b86cfb3d7f45176ad44e121cbf3eed1bb3 [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2
Copy link
Collaborator

🚀 [CI Build] Test results 🚀

Test results

✅ All tests passed on VSTS: simulator tests.

🎉 All 235 tests passed 🎉

Tests counts

✅ bcl: All 69 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests: All 1 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 7 tests passed. Html Report (VSDrops) Download
✅ framework: All 8 tests passed. Html Report (VSDrops) Download
✅ generator: All 2 tests passed. Html Report (VSDrops) Download
✅ interdependent_binding_projects: All 7 tests passed. Html Report (VSDrops) Download
✅ install_source: All 1 tests passed. Html Report (VSDrops) Download
✅ introspection: All 8 tests passed. Html Report (VSDrops) Download
✅ linker: All 65 tests passed. Html Report (VSDrops) Download
✅ mac_binding_project: All 1 tests passed. [attempt 2] Html Report (VSDrops) Download
✅ mmp: All 2 tests passed. Html Report (VSDrops) Download
✅ mononative: All 12 tests passed. Html Report (VSDrops) Download
✅ monotouch: All 35 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ mtouch: All 1 tests passed. Html Report (VSDrops) Download
✅ xammac: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 8 tests passed. Html Report (VSDrops) Download
✅ xtro: All 2 tests passed. Html Report (VSDrops) Download

Pipeline on Agent
Hash: 3202c8b86cfb3d7f45176ad44e121cbf3eed1bb3 [PR build]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Investigate/implement __LINKEDIT size reduction
4 participants