Skip to content

Commit 7f8ca6e

Browse files
committed
lld: Fix initial Mach-O load commands size calculation omitting LC_FUNCTION_STARTS
Patch by Nicholas Allegra. 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. llvm-svn: 358545
1 parent e3576b0 commit 7f8ca6e

File tree

4 files changed

+326
-12
lines changed

4 files changed

+326
-12
lines changed

lld/lib/ReaderWriter/MachO/MachONormalizedFile.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ readBinary(std::unique_ptr<MemoryBuffer> &mb,
287287
/// Takes in-memory normalized view and writes a mach-o object file.
288288
llvm::Error writeBinary(const NormalizedFile &file, StringRef path);
289289

290-
size_t headerAndLoadCommandsSize(const NormalizedFile &file);
290+
size_t headerAndLoadCommandsSize(const NormalizedFile &file,
291+
bool includeFunctionStarts);
291292

292293

293294
/// Parses a yaml encoded mach-o file to produce an in-memory normalized view.

lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryWriter.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ struct TrieNode {
108108
class MachOFileLayout {
109109
public:
110110
/// All layout computation is done in the constructor.
111-
MachOFileLayout(const NormalizedFile &file);
111+
MachOFileLayout(const NormalizedFile &file, bool alwaysIncludeFunctionStarts);
112112

113113
/// Returns the final file size as computed in the constructor.
114114
size_t size() const;
@@ -122,7 +122,8 @@ class MachOFileLayout {
122122
llvm::Error writeBinary(StringRef path);
123123

124124
private:
125-
uint32_t loadCommandsSize(uint32_t &count);
125+
uint32_t loadCommandsSize(uint32_t &count,
126+
bool alwaysIncludeFunctionStarts);
126127
void buildFileOffsets();
127128
void writeMachHeader();
128129
llvm::Error writeLoadCommands();
@@ -231,8 +232,9 @@ class MachOFileLayout {
231232
ByteBuffer _exportTrie;
232233
};
233234

234-
size_t headerAndLoadCommandsSize(const NormalizedFile &file) {
235-
MachOFileLayout layout(file);
235+
size_t headerAndLoadCommandsSize(const NormalizedFile &file,
236+
bool includeFunctionStarts) {
237+
MachOFileLayout layout(file, includeFunctionStarts);
236238
return layout.headerAndLoadCommandsSize();
237239
}
238240

@@ -249,7 +251,8 @@ size_t MachOFileLayout::headerAndLoadCommandsSize() const {
249251
return _endOfLoadCommands;
250252
}
251253

252-
MachOFileLayout::MachOFileLayout(const NormalizedFile &file)
254+
MachOFileLayout::MachOFileLayout(const NormalizedFile &file,
255+
bool alwaysIncludeFunctionStarts)
253256
: _file(file),
254257
_is64(MachOLinkingContext::is64Bit(file.arch)),
255258
_swap(!MachOLinkingContext::isHostEndian(file.arch)),
@@ -270,7 +273,7 @@ MachOFileLayout::MachOFileLayout(const NormalizedFile &file)
270273
_endOfLoadCommands += sizeof(version_min_command);
271274
_countOfLoadCommands++;
272275
}
273-
if (!_file.functionStarts.empty()) {
276+
if (!_file.functionStarts.empty() || alwaysIncludeFunctionStarts) {
274277
_endOfLoadCommands += sizeof(linkedit_data_command);
275278
_countOfLoadCommands++;
276279
}
@@ -325,7 +328,8 @@ MachOFileLayout::MachOFileLayout(const NormalizedFile &file)
325328
} else {
326329
// Final linked images have one load command per segment.
327330
_endOfLoadCommands = _startOfLoadCommands
328-
+ loadCommandsSize(_countOfLoadCommands);
331+
+ loadCommandsSize(_countOfLoadCommands,
332+
alwaysIncludeFunctionStarts);
329333

330334
// Assign section file offsets.
331335
buildFileOffsets();
@@ -374,7 +378,8 @@ MachOFileLayout::MachOFileLayout(const NormalizedFile &file)
374378
}
375379
}
376380

377-
uint32_t MachOFileLayout::loadCommandsSize(uint32_t &count) {
381+
uint32_t MachOFileLayout::loadCommandsSize(uint32_t &count,
382+
bool alwaysIncludeFunctionStarts) {
378383
uint32_t size = 0;
379384
count = 0;
380385

@@ -444,7 +449,7 @@ uint32_t MachOFileLayout::loadCommandsSize(uint32_t &count) {
444449
}
445450

446451
// Add LC_FUNCTION_STARTS if needed
447-
if (!_file.functionStarts.empty()) {
452+
if (!_file.functionStarts.empty() || alwaysIncludeFunctionStarts) {
448453
size += sizeof(linkedit_data_command);
449454
++count;
450455
}
@@ -1006,6 +1011,7 @@ llvm::Error MachOFileLayout::writeLoadCommands() {
10061011
lc += sizeof(linkedit_data_command);
10071012
}
10081013
}
1014+
assert(lc == &_buffer[_endOfLoadCommands]);
10091015
return llvm::Error::success();
10101016
}
10111017

@@ -1017,6 +1023,7 @@ void MachOFileLayout::writeSectionContent() {
10171023
if (s.content.empty())
10181024
continue;
10191025
uint32_t offset = _sectInfo[&s].fileOffset;
1026+
assert(offset >= _endOfLoadCommands);
10201027
uint8_t *p = &_buffer[offset];
10211028
memcpy(p, &s.content[0], s.content.size());
10221029
p += s.content.size();
@@ -1542,7 +1549,7 @@ llvm::Error MachOFileLayout::writeBinary(StringRef path) {
15421549

15431550
/// Takes in-memory normalized view and writes a mach-o object file.
15441551
llvm::Error writeBinary(const NormalizedFile &file, StringRef path) {
1545-
MachOFileLayout layout(file);
1552+
MachOFileLayout layout(file, false);
15461553
return layout.writeBinary(path);
15471554
}
15481555

lld/lib/ReaderWriter/MachO/MachONormalizedFileFromAtoms.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,8 @@ void Util::layoutSectionsInTextSegment(size_t hlcSize, SegmentInfo *seg,
587587

588588
void Util::assignAddressesToSections(const NormalizedFile &file) {
589589
// NOTE!: Keep this in sync with organizeSections.
590-
size_t hlcSize = headerAndLoadCommandsSize(file);
590+
size_t hlcSize = headerAndLoadCommandsSize(file,
591+
_ctx.generateFunctionStartsLoadCommand());
591592
uint64_t address = 0;
592593
for (SegmentInfo *seg : _segmentInfos) {
593594
if (seg->name.equals("__PAGEZERO")) {

0 commit comments

Comments
 (0)