I implemented something that's been talked about for a while -- creating "skinny corefiles", or a user process corefile that only includes dirty memory in it. This is the patchset to implement this with lldb's "process save-core" and lldb's corefile reading, for Mach-O files.
Because of how the system libraries are shared across all processes on Darwin systems, dumping all available memory for a process involves writing multiple gigabytes for even a trivial command line app. The majority of this corefile are unmodified memory pages for the system libraries; it's a very expensive and slow operation.
This patchset does a few things:
- Adds support in debugserver to find the list of pages in a memory region that are dirty. It adds a key-value pair to qHostInfo to advertise how large a VM memory page is on the target system, and it includes a list of dirty pages for a memory region in the qMemoryRegionInfo reply to lldb's queries about memory regions.
- It adds a new LC_NOTE "all image infos" in the corefile, which specifies all binary images that are present in the inferior process and where they are loaded in memory. The filepaths, UUIDs, and segment name+load addresses are required because lldb will not have access to dyld's dyld_all_image_infos structure or the ability to read these binaries out of memory. (normally the segment load addresses could be found by reading the Mach-O load commands for the binaries from the corefile)
- It adds a new LC_NOTE "executing uuids" in the corefile, which is a list of the UUIDs of the binary images that are actually executing code at the point when the corefile was written. That is, the set of binaries that appear on any backtrace on any thread at the point of the coredump. A graphical app on macOS these days can easily have 500 different binary images loaded in the process; likely only a couple dozen of those are actually executing. Knowing which binary images are executing allows us to do a more expensive search for these most-important binaries, like we do with crashlog.py.
- Changes the Mach-O corefile creation to only include dirty pages when communicating with a debugserver that provides this information.
- Read and use the "all image infos" and "executing uuids" in ProcessMachCore.
- Finally, of course, adds a test case where we have 3 binaries (one executable, two dylibs), hits a breakpoint, saves a skinny corefile. Then it moves the executable to a hidden location that lldb can only discover by calling the dsymForUUID script (to test 'executing uuids'), deletes one dylib, and leaves one at the expected location. It loads the skinny corefile into lldb and confirms that the present libraries are present, the absent library is absent (and its non-dirty memory is unreadable), and we can read variables that are on the heap/stack correctly.
Before this change, the minimum size for a corefile on macOS 10.15 was around 2GB and it would take around 5 minutes to dump. With this change, a simple test binary user process corefile is around 500KB and takes a few seconds to dump. Larger real-world applications will have larger amounts of stack & heap and will have correspondingly larger corefiles.
I'm not thrilled that I'm touching the same parts of lldb as David Spickett in https://reviews.llvm.org/D87442 - we're going to create conflicts depending on which of us lands first. Both of us need to modify the qMemoryRegionInfo packet and lldb's MemoryRegionInfo class to store our additional informations.
The majority of this patch concerns ObjectFileMachO and ProcessMachCore, as well as debugserver; I expect those to be of less interest to any reviewer/commenters. But of course I welcome any suggestions/comments to those parts as well as the other parts of lldb I touched along the way. I did need to add some API/structs in the ObjectFile base class so ObjectFileMachO could pass information up to ProcessMachCore that are very specific to what I'm doing here.