The Mach-O writer calculates the size of load commands multiple times.
First, Util::assignAddressesToSections() (in MachONormalizedFileFromAtoms.cpp)
calculates the size using headerAndLoadCommandsSize() (in
MachONormalizedFileBinaryWriter.cpp), which creates a temporary MachOFileLayout
for the NormalizedFile, only to retrieve its headerAndLoadCommandsSize. Later,
writeBinary() (in MachONormalizedFileBinaryWriter.cpp) creates a new layout and
uses the offsets from that layout to actually write out everything in the
NormalizedFile.
But the NormalizedFile changes between the first computation and the second.
When Util::assignAddressesToSections is called, file.functionStarts is always
empty because Util::addFunctionStarts has not yet been called. Yet
MachOFileLayout decides whether to include a LC_FUNCTION_STARTS command based
on whether file.functionStarts is nonempty. Therefore, the initial computation
always omits it.
Because padding for the __TEXT segment (to make its size a multiple of the page
size) is added between the load commands and the first section, LLD still
generates a valid binary as long as the amount of padding happens to be large
enough to fit LC_FUNCTION_STARTS command, which it usually is.
However, it's easy to reproduce the issue by adding a section of a precise
size. Given foo.c:
__attribute__((section("__TEXT,__foo"))) char foo[0xd78] = {0};
Run:
clang -dynamiclib -o foo.dylib foo.c -fuse-ld=lld -install_name /usr/lib/foo.dylib otool -lvv foo.dylib
This should produce:
truncated or malformed object (offset field of section 1 in LC_SEGMENT_64 command 0 not past the headers of the file)
This commit:
- Changes MachOFileLayout to always assume LC_FUNCTION_STARTS is present for the initial computation, as long as generating LC_FUNCTION_STARTS is enabled. It would be slightly better to check whether there are actually any functions, since no LC_FUNCTION_STARTS will be generated if not, but it doesn't cause a problem if the initial computation is too high.
- Adds a test.
- Adds an assert in MachOFileLayout::writeSectionContent() that we are not writing section content into the load commands region (which would happen if the offset was calculated too low due to the initial load commands size calculation being too low).
- Adds an assert in MachOFileLayout::writeLoadCommands to validate a similar situation where two size-of-load-commands computations are expected to be equivalent.