Skip to content

Commit 9f5ee4d

Browse files
committed
[dsymutil] Skip duplicates files with identical time stamps in the debug map
Static archives can contain multiple files with the same file name, in which case the timestamp is used to disambiguate. Because timestamps are expressed in seconds since epoch timestamp collisions are far from impossible. Furthermore, to facilitate reproducible builds, the static linker can be told to emit no timestamps at all. dsymutil already detects timestamp mismatches between the debug map and the object files. However, it does not handle timestamp collisions within the debug maps (STABS). Currently, we arbitrarily pick the first debug map entry and ignore the rest. This is incorrect: if a symbol exists in multiple object files, the linker might not have picked the one from the first object file. This also results in missing symbol warnings for all the symbols not defined in the first object file. Given that in this scenario, dsymutil does not have enough information to disambiguate, it should print a single informative warning and skip the ambiguous debug map objects. rdar://110374836 Differential revision: https://reviews.llvm.org/D152585 (cherry picked from commit 65f6373)
1 parent 43af265 commit 9f5ee4d

File tree

5 files changed

+119
-18
lines changed

5 files changed

+119
-18
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
$ cat f.c
2+
int f() {
3+
volatile int i;
4+
return i;
5+
}
6+
$ cat g.c
7+
int g() {
8+
volatile int i;
9+
return i;
10+
}
11+
$ cat main.c
12+
int f();
13+
int g();
14+
15+
int main() {
16+
return f() + g();
17+
}
18+
$ clang -g f.c -c -o f/foo.o
19+
$ clang -g g.c -c -o g/foo.o
20+
$ libtool -static f/foo.o g/foo.o -o foo.a
21+
$ clang main.o foo.a -o main.out
22+
23+
RUN: dsymutil -oso-prepend-path %p/../Inputs %p/../Inputs/private/tmp/collision/main.out --dump-debug-map 2>&1 | FileCheck %s
24+
CHECK: skipping debug map object with duplicate name and timestamp: 1969-12-31 16:00:00.000000000 /private/tmp/collision/foo.a(foo.o)
25+
CHECK-NOT: could not find object file symbol for symbol _g
26+
CHECK-NOT: could not find object file symbol for symbol _f
Binary file not shown.
Binary file not shown.
Binary file not shown.

llvm/tools/dsymutil/MachODebugMapParser.cpp

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
#include "BinaryHolder.h"
1010
#include "DebugMap.h"
1111
#include "MachOUtils.h"
12-
#include "llvm/ADT/Optional.h"
12+
#include "llvm/ADT/DenseSet.h"
1313
#include "llvm/ADT/SmallSet.h"
1414
#include "llvm/Object/MachO.h"
15+
#include "llvm/Support/Chrono.h"
1516
#include "llvm/Support/Path.h"
1617
#include "llvm/Support/WithColor.h"
1718
#include "llvm/Support/raw_ostream.h"
@@ -31,7 +32,7 @@ class MachODebugMapParser {
3132
: BinaryPath(std::string(BinaryPath)), Archs(Archs.begin(), Archs.end()),
3233
PathPrefix(std::string(PathPrefix)),
3334
PaperTrailWarnings(PaperTrailWarnings), BinHolder(VFS, Verbose),
34-
CurrentDebugMapObject(nullptr) {}
35+
CurrentDebugMapObject(nullptr), SkipDebugMapObject(false) {}
3536

3637
/// Parses and returns the DebugMaps of the input binary. The binary contains
3738
/// multiple maps in case it is a universal binary.
@@ -42,6 +43,8 @@ class MachODebugMapParser {
4243
/// Walk the symbol table and dump it.
4344
bool dumpStab();
4445

46+
using OSO = std::pair<llvm::StringRef, uint64_t>;
47+
4548
private:
4649
std::string BinaryPath;
4750
SmallVector<StringRef, 1> Archs;
@@ -70,12 +73,18 @@ class MachODebugMapParser {
7073
/// Element of the debug map corresponding to the current object file.
7174
DebugMapObject *CurrentDebugMapObject;
7275

76+
/// Whether we need to skip the current debug map object.
77+
bool SkipDebugMapObject;
78+
7379
/// Holds function info while function scope processing.
7480
const char *CurrentFunctionName;
7581
uint64_t CurrentFunctionAddress;
7682

7783
std::unique_ptr<DebugMap> parseOneBinary(const MachOObjectFile &MainBinary,
7884
StringRef BinaryPath);
85+
void handleStabDebugMap(
86+
const MachOObjectFile &MainBinary,
87+
std::function<void(uint32_t, uint8_t, uint8_t, uint16_t, uint64_t)> F);
7988

8089
void
8190
switchToNewDebugMapObject(StringRef Filename,
@@ -85,13 +94,21 @@ class MachODebugMapParser {
8594
std::vector<StringRef> getMainBinarySymbolNames(uint64_t Value);
8695
void loadMainBinarySymbols(const MachOObjectFile &MainBinary);
8796
void loadCurrentObjectFileSymbols(const object::MachOObjectFile &Obj);
97+
98+
void handleStabOSOEntry(uint32_t StringIndex, uint8_t Type,
99+
uint8_t SectionIndex, uint16_t Flags, uint64_t Value,
100+
llvm::DenseSet<OSO> &OSOs,
101+
llvm::SmallSet<OSO, 4> &Duplicates);
88102
void handleStabSymbolTableEntry(uint32_t StringIndex, uint8_t Type,
89103
uint8_t SectionIndex, uint16_t Flags,
90-
uint64_t Value);
104+
uint64_t Value,
105+
const llvm::SmallSet<OSO, 4> &Duplicates);
91106

92-
template <typename STEType> void handleStabDebugMapEntry(const STEType &STE) {
93-
handleStabSymbolTableEntry(STE.n_strx, STE.n_type, STE.n_sect, STE.n_desc,
94-
STE.n_value);
107+
template <typename STEType>
108+
void handleStabDebugMapEntry(
109+
const STEType &STE,
110+
std::function<void(uint32_t, uint8_t, uint8_t, uint16_t, uint64_t)> F) {
111+
F(STE.n_strx, STE.n_type, STE.n_sect, STE.n_desc, STE.n_value);
95112
}
96113

97114
void addCommonSymbols();
@@ -140,6 +157,7 @@ void MachODebugMapParser::resetParserState() {
140157
CurrentObjectAliasMap.clear();
141158
SeenAliasValues.clear();
142159
CurrentDebugMapObject = nullptr;
160+
SkipDebugMapObject = false;
143161
}
144162

145163
/// Commons symbols won't show up in the symbol map but might need to be
@@ -198,21 +216,60 @@ static std::string getArchName(const object::MachOObjectFile &Obj) {
198216
return std::string(T.getArchName());
199217
}
200218

219+
void MachODebugMapParser::handleStabDebugMap(
220+
const MachOObjectFile &MainBinary,
221+
std::function<void(uint32_t, uint8_t, uint8_t, uint16_t, uint64_t)> F) {
222+
for (const SymbolRef &Symbol : MainBinary.symbols()) {
223+
const DataRefImpl &DRI = Symbol.getRawDataRefImpl();
224+
if (MainBinary.is64Bit())
225+
handleStabDebugMapEntry(MainBinary.getSymbol64TableEntry(DRI), F);
226+
else
227+
handleStabDebugMapEntry(MainBinary.getSymbolTableEntry(DRI), F);
228+
}
229+
}
230+
201231
std::unique_ptr<DebugMap>
202232
MachODebugMapParser::parseOneBinary(const MachOObjectFile &MainBinary,
203233
StringRef BinaryPath) {
204234
Result = std::make_unique<DebugMap>(MainBinary.getArchTriple(), BinaryPath,
205235
MainBinary.getUuid());
206236
loadMainBinarySymbols(MainBinary);
207237
MainBinaryStrings = MainBinary.getStringTableData();
208-
for (const SymbolRef &Symbol : MainBinary.symbols()) {
209-
const DataRefImpl &DRI = Symbol.getRawDataRefImpl();
210-
if (MainBinary.is64Bit())
211-
handleStabDebugMapEntry(MainBinary.getSymbol64TableEntry(DRI));
212-
else
213-
handleStabDebugMapEntry(MainBinary.getSymbolTableEntry(DRI));
238+
239+
// Static archives can contain multiple object files with identical names, in
240+
// which case the timestamp is used to disambiguate. However, if both are
241+
// identical, there's no way to tell them apart. Detect this and skip
242+
// duplicate debug map objects.
243+
llvm::DenseSet<OSO> OSOs;
244+
llvm::SmallSet<OSO, 4> Duplicates;
245+
246+
// Iterate over all the STABS to find duplicate OSO entries.
247+
handleStabDebugMap(MainBinary,
248+
[&](uint32_t StringIndex, uint8_t Type,
249+
uint8_t SectionIndex, uint16_t Flags, uint64_t Value) {
250+
handleStabOSOEntry(StringIndex, Type, SectionIndex,
251+
Flags, Value, OSOs, Duplicates);
252+
});
253+
254+
// Print an informative warning with the duplicate object file name and time
255+
// stamp.
256+
for (const auto &OSO : Duplicates) {
257+
std::string Buffer;
258+
llvm::raw_string_ostream OS(Buffer);
259+
OS << sys::TimePoint<std::chrono::seconds>(sys::toTimePoint(OSO.second));
260+
Warning("skipping debug map object with duplicate name and timestamp: " +
261+
OS.str() + Twine(" ") + Twine(OSO.first));
214262
}
215263

264+
// Build the debug map by iterating over the STABS again but ignore the
265+
// duplicate debug objects.
266+
handleStabDebugMap(MainBinary, [&](uint32_t StringIndex, uint8_t Type,
267+
uint8_t SectionIndex, uint16_t Flags,
268+
uint64_t Value) {
269+
handleStabSymbolTableEntry(StringIndex, Type, SectionIndex, Flags, Value,
270+
Duplicates);
271+
});
272+
216273
resetParserState();
217274
return std::move(Result);
218275
}
@@ -407,20 +464,38 @@ ErrorOr<std::vector<std::unique_ptr<DebugMap>>> MachODebugMapParser::parse() {
407464
return std::move(Results);
408465
}
409466

467+
void MachODebugMapParser::handleStabOSOEntry(
468+
uint32_t StringIndex, uint8_t Type, uint8_t SectionIndex, uint16_t Flags,
469+
uint64_t Value, llvm::DenseSet<OSO> &OSOs,
470+
llvm::SmallSet<OSO, 4> &Duplicates) {
471+
if (Type != MachO::N_OSO)
472+
return;
473+
474+
OSO O(&MainBinaryStrings.data()[StringIndex], Value);
475+
if (!OSOs.insert(O).second)
476+
Duplicates.insert(O);
477+
}
478+
410479
/// Interpret the STAB entries to fill the DebugMap.
411-
void MachODebugMapParser::handleStabSymbolTableEntry(uint32_t StringIndex,
412-
uint8_t Type,
413-
uint8_t SectionIndex,
414-
uint16_t Flags,
415-
uint64_t Value) {
480+
void MachODebugMapParser::handleStabSymbolTableEntry(
481+
uint32_t StringIndex, uint8_t Type, uint8_t SectionIndex, uint16_t Flags,
482+
uint64_t Value, const llvm::SmallSet<OSO, 4> &Duplicates) {
416483
if (!(Type & MachO::N_STAB))
417484
return;
418485

419486
const char *Name = &MainBinaryStrings.data()[StringIndex];
420487

421488
// An N_OSO entry represents the start of a new object file description.
422-
if (Type == MachO::N_OSO)
489+
if (Type == MachO::N_OSO) {
490+
if (Duplicates.count(OSO(Name, Value))) {
491+
SkipDebugMapObject = true;
492+
return;
493+
}
423494
return switchToNewDebugMapObject(Name, sys::toTimePoint(Value));
495+
}
496+
497+
if (SkipDebugMapObject)
498+
return;
424499

425500
if (Type == MachO::N_AST) {
426501
SmallString<80> Path(PathPrefix);

0 commit comments

Comments
 (0)