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

Correctly handle dyld caches on macOS 13 and above #642

Merged
merged 4 commits into from
Mar 11, 2024

Conversation

mstange
Copy link
Contributor

@mstange mstange commented Mar 9, 2024

This allows successful parsing of dyld caches on macOS 13 and above on Intel Macs.

The main dyld cache file on macOS contains an array of subcache info structs, each of which specifies the UUID (and some other information) of each subcache.
DyldCache::parse checks that the subcache UUIDs match these expected UUIDs.

In macOS 13, the format of the subcache info struct changed: the struct gained an additional field after the UUID field. This means that as soon as you had more than one subcache, our UUID check would fail, because the second subcache UUID would be read from the wrong offset.

I didn't notice this on my Apple Silicon Mac, because the arm64e dyld cache only has one subcache:
dyld_shared_cache_arm64e.01.
But on Intel Macs, there are currently four subcaches: dyld_shared_cache_x86_64.01, .02, .03, and .04.

In practice this means that my software hasn't been able to symbolicate macOS system libraries on Intel Macs since the release of macOS 13.

This commit adds the new struct definition and makes the UUID check work correctly.

This is a breaking change to the public API. I added a DyldSubCacheSlice enum, but I'm not particularly fond of it.
I'm also not a big fan of the new allocation for the Vec of UUIDs, but it seemed better than the alternatives I tried, which all had a bunch of code duplication.

dyld source code:

I've changed MIN_HEADER_SIZE_SUBCACHES_V1 to be 0x1c8 instead of 0x1c4 because we use a >= check on it. dyld uses a > check with the offset of the subCacheArrayCount field (called images_across_all_subcaches_count in our definition), so what dyld is doing is a equivalent to a > 0x1c4 check. Rather than changing our check to be a > check, I've kept it as a >= because that fits better with the MIN_HEADER_SIZE name, and instead increased the offset to point to the end of the field instead of the beginning.

I didn't touch our definition of DyldCacheHeader. It's quite incomplete because the dyld source with the new header fields hadn't been released yet at the time I wrote the object code. Now that it's out, we could flesh out our type definition a bit, but I don't have an urgent need for that at the moment.

This allows successful parsing of dyld caches on
macOS 13 and above on Intel Macs.

The main dyld cache file on macOS contains an array of
subcache info structs, each of which specifies the UUID
(and some other information) of each subcache.
`DyldCache::parse` checks that the subcache UUIDs match
these expected UUIDs.

In macOS 13, the format of the subcache info struct
changed: it gained an additional field after the UUID
field. This means that as soon as you had more than
one subcache, our UUID check would fail, because the
second subcache UUID would be read from the wrong offset.

I didn't notice this on my Apple Silicon Mac, because
the arm64e dyld cache only has one subcache:
`dyld_shared_cache_arm64e.01`.
But on Intel Macs, there are currently four subcaches:
`dyld_shared_cache_x86_64.01`, `.02`, `.03`, and `.04`.

In practice this means that my software hasn't been able to
symbolicate macOS system libraries on Intel Macs since
the release of macOS 13.

This commit adds the new struct definition and makes
the UUID check work correctly.

This is a breaking change to the public API. I added
a `DyldSubCacheSlice` enum, but I'm not particularly
fond of it.
I'm also not a big fan of the new allocation for the
Vec of UUIDs, but it seemed better than the alternatives
I tried, which all had a bunch of code duplication.
@philipc
Copy link
Contributor

philipc commented Mar 9, 2024

Are you aware of any easy way to test this without owning Apple hardware (both Apple silicon and Intel)? The best I have currently is using whatever machines CI provides.

@philipc
Copy link
Contributor

philipc commented Mar 9, 2024

Currently the examples (objdump and dyldcachedump) only load subcaches with either an integer suffix or ".symbols" suffix, and DyldCache::parse relies on the provided subcache_data matching the subcaches. Is this going to work now that DyldSubCacheEntryV2::file_suffix can be anything?

@mstange
Copy link
Contributor Author

mstange commented Mar 9, 2024

Are you aware of any easy way to test this without owning Apple hardware (both Apple silicon and Intel)? The best I have currently is using whatever machines CI provides.

The two ways I can think of involve quite a bit of effort:

  • We could try to pull the caches from the Apple update servers, similar to what the macOS symbol scraper in mozilla-central was attempting to do. That scraper is not currently working for macOS 11+ because it needs more work. Last I checked, running these scripts also requires large amounts of disk space.
  • We could try to compile dyld ourselves and create our own caches for testing, specifically we could probably use the dyld_shared_cache_builder binary. In my past attempts to build dyld I had to hack around the absence of certain header files and #defines, and additionally we'll probably want to change these subcache size constants so that our test cache isn't enormous.

Currently the examples (objdump and dyldcachedump) only load subcaches with either an integer suffix or ".symbols" suffix, and DyldCache::parse relies on the provided subcache_data matching the subcaches. Is this going to work now that DyldSubCacheEntryV2::file_suffix can be anything?

Oh, I had forgotten about these examples. You're right that they currently won't work - on macOS 13, the suffixes now have a leading zero.
And we may want a better API in this crate, so that users can list the suffixes before they read the subcache data. In samply, I just read all available files like this: https://github.com/mstange/samply/blob/8d4770d8c3e2bbf509b405b30cc54e89d27c43b5/samply-symbols/src/macho.rs#L243-L259
But this assumes that the suffix is either .1, .01, or .symbols, which is so far the only type of suffix I've observed in the caches that ship with macOS. But the dyld source suggests that there are also "development" caches with other suffixes, and anyway it would be more reliable to use the suffixes written down in the main cache.

With the current state of this PR, users of object would have to call macho::DyldCacheHeader::parse themselves for the main cache in order to list the subcaches, and then call DyldCache::parse once they have the subcache data, which parses the main cache header again.

@mstange
Copy link
Contributor Author

mstange commented Mar 10, 2024

Oh, I had forgotten about these examples. You're right that they currently won't work - on macOS 13, the suffixes now have a leading zero.

The dyldcachedump example was already taking the leading zero into account, so it was working, modulo the UUID verification issue. The objdump example was not using a leading zero so it failed to load the subcaches and said "Failed to parse file: Unsupported file format".
I'm going to add a commit which changes both to take the suffix from the header.

@mstange mstange force-pushed the fix-dyld-subcache-uuid-checks branch from 5901677 to accd7e5 Compare March 10, 2024 01:53
dyldcachedump was working correctly on macOS 13+ because it was trying
the "leading zero" suffix format as well as the "no leading zero" suffix
format. This commit changes it to read the suffix from the main cache
header.

objdump was not able to parse dyld shared cache files on macOS 13+ because
it was only using the "no leading zero" suffix format, and thus not finding
the subcaches.
@mstange mstange force-pushed the fix-dyld-subcache-uuid-checks branch from accd7e5 to a24f21c Compare March 10, 2024 01:55
Copy link
Contributor

@philipc philipc left a comment

Choose a reason for hiding this comment

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

LGTM. I assume you want a release with this soon?

src/read/macho/dyld_cache.rs Show resolved Hide resolved
src/read/macho/dyld_cache.rs Show resolved Hide resolved
src/read/macho/dyld_cache.rs Outdated Show resolved Hide resolved
@mstange
Copy link
Contributor Author

mstange commented Mar 10, 2024

LGTM. I assume you want a release with this soon?

Thank you! A release would be appreciated.

@philipc philipc merged commit 791adb0 into gimli-rs:master Mar 11, 2024
10 checks passed
mcbegamerxx954 pushed a commit to mcbegamerxx954/object that referenced this pull request Jun 15, 2024
This allows successful parsing of dyld caches on
macOS 13 and above on Intel Macs.

The main dyld cache file on macOS contains an array of
subcache info structs, each of which specifies the UUID
(and some other information) of each subcache.
`DyldCache::parse` checks that the subcache UUIDs match
these expected UUIDs.

In macOS 13, the format of the subcache info struct
changed: it gained an additional field after the UUID
field. This means that as soon as you had more than
one subcache, our UUID check would fail, because the
second subcache UUID would be read from the wrong offset.

I didn't notice this on my Apple Silicon Mac, because
the arm64e dyld cache only has one subcache:
`dyld_shared_cache_arm64e.01`.
But on Intel Macs, there are currently four subcaches:
`dyld_shared_cache_x86_64.01`, `.02`, `.03`, and `.04`.

In practice this means that my software hasn't been able to
symbolicate macOS system libraries on Intel Macs since
the release of macOS 13.

This commit adds the new struct definition and makes
the UUID check work correctly.

This is a breaking change to the public API. I added
a `DyldSubCacheSlice` enum, but I'm not particularly
fond of it.

dyldcachedump was working correctly on macOS 13+ because it was trying
the "leading zero" suffix format as well as the "no leading zero" suffix
format. This commit changes it to read the suffix from the main cache
header.

objdump was not able to parse dyld shared cache files on macOS 13+ because
it was only using the "no leading zero" suffix format, and thus not finding
the subcaches.
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.

2 participants