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

Investigate/implement __LINKEDIT size reduction #18332

Closed
rolfbjarne opened this issue May 25, 2023 · 1 comment · Fixed by #18408
Closed

Investigate/implement __LINKEDIT size reduction #18332

rolfbjarne opened this issue May 25, 2023 · 1 comment · Fixed by #18408
Assignees
Labels
app-size enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance
Milestone

Comments

@rolfbjarne
Copy link
Member

See dotnet/runtime#86707 for background information.

Basically we want to look into whether we can produce smaller binaries by passing the exported functions to the native linker (as opposed to stripping them away later).

The referenced issue only talks about this in the context of NativeAOT, but this might benefit us on all platforms, not just NativeAOT.

@rolfbjarne rolfbjarne added enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance app-size labels May 25, 2023
@rolfbjarne rolfbjarne added this to the .NET 8 milestone May 25, 2023
@ivanpovazan
Copy link
Contributor

ivanpovazan commented May 25, 2023

This was also noticed with Mono (to much smaller extent), where the __LINKEDIT Export Info takes up 238360 bytes of zeros with MAUI iOS template app: https://gist.github.com/ivanpovazan/97ec573836caca3f8347b8eafa2cad14

rolfbjarne added a commit that referenced this issue Jun 12, 2023
…ies (#18408)

# 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):

- https://github.com/apple-oss-distributions/cctools/blob/cctools-986/misc/strip.c
- https://github.com/apple-oss-distributions/ld64/blob/ld64-711/src/other/PruneTrie.cpp

Thanks @lambdageek for helping to track this down.

# Approach

As Xamarin build process already collects all the [required symbols][1] 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][2]).

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

### 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)

```bash
make run-device
``` 

2. Print the load commands - [load_cmds_strip.list][3]

```bash
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
```

3. Create a hex dump of the export info section - [hex_dump_strip.list][4]

``` bash
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
    
4. Verify exported symbols are correct - [dyld_info_strip.list][5]

``` bash
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)

```bash
make run-device-no-strip
``` 

2. Print the load commands - [load_cmds_nostrip.list][6]

```bash
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
```

3. Create a hex dump of the export info section - [hex_dump_nostrip.list][7]

``` bash
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
    
4. Verify exported symbols are correct - [dyld_info_nostrip.list][8]

``` bash
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)

```bash
make run-device-export-syms
``` 

2. Print the load commands - [load_cmds_export.list][9]

```bash
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
```

3. Create a hex dump of the export info section - [hex_dump_export.list][10]

``` bash
xxd -s 5942432 -l 1048 bin/Release/net7.0-ios/ios-arm64/MySingleView.app/MySingleView > hex_dump_export.list
```
    
4. Verify exported symbols are correct - [dyld_info_export.list][11]

``` bash
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:

- https://github.com/qyang-nj/llios/blob/main/exported_symbol/README.md

[1]: https://github.com/xamarin/xamarin-macios/blob/11e7883da04d80c59e4ffbbc955a3e0e0060ff90/tools/dotnet-linker/Steps/GenerateReferencesStep.cs#L38-L44
[2]: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/DynamicLibraryDesignGuidelines.html
[3]: https://gist.github.com/ivanpovazan/d53f8d10be5e4ea9f39a41ea540aa7fa
[4]: https://gist.github.com/ivanpovazan/60637422f3ff8cb5f437ddd06a21d9c1
[5]: https://gist.github.com/ivanpovazan/352595ad15c2ac02f38dcb3bd4130642
[6]: https://gist.github.com/ivanpovazan/bf700161f2f3691d1d7381c98d4fa0be
[7]: https://gist.github.com/ivanpovazan/44269e4fff5ebd58a4d181451e5c106f
[8]: https://gist.github.com/ivanpovazan/38c5afe076502d514a77420af0e10b01
[9]: https://gist.github.com/ivanpovazan/3f663c3c630005f5a578605d48ba807e
[10]: https://gist.github.com/ivanpovazan/0bb84f64281d05ab20438aeaed64f13c
[11]: https://gist.github.com/ivanpovazan/78b3ba2288f53a2316b9bc46964e7e4f

---------

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
@ghost ghost locked as resolved and limited conversation to collaborators Jul 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
app-size enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants