Skip to content

Commit 65f6373

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
1 parent 08da9ce commit 65f6373

File tree

5 files changed

+119
-17
lines changed

5 files changed

+119
-17
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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#include "BinaryHolder.h"
1010
#include "DebugMap.h"
1111
#include "MachOUtils.h"
12+
#include "llvm/ADT/DenseSet.h"
1213
#include "llvm/ADT/SmallSet.h"
1314
#include "llvm/Object/MachO.h"
15+
#include "llvm/Support/Chrono.h"
1416
#include "llvm/Support/Path.h"
1517
#include "llvm/Support/WithColor.h"
1618
#include "llvm/Support/raw_ostream.h"
@@ -31,7 +33,7 @@ class MachODebugMapParser {
3133
: BinaryPath(std::string(BinaryPath)), Archs(Archs.begin(), Archs.end()),
3234
PathPrefix(std::string(PathPrefix)),
3335
PaperTrailWarnings(PaperTrailWarnings), BinHolder(VFS, Verbose),
34-
CurrentDebugMapObject(nullptr) {}
36+
CurrentDebugMapObject(nullptr), SkipDebugMapObject(false) {}
3537

3638
/// Parses and returns the DebugMaps of the input binary. The binary contains
3739
/// multiple maps in case it is a universal binary.
@@ -42,6 +44,8 @@ class MachODebugMapParser {
4244
/// Walk the symbol table and dump it.
4345
bool dumpStab();
4446

47+
using OSO = std::pair<llvm::StringRef, uint64_t>;
48+
4549
private:
4650
std::string BinaryPath;
4751
SmallVector<StringRef, 1> Archs;
@@ -70,12 +74,18 @@ class MachODebugMapParser {
7074
/// Element of the debug map corresponding to the current object file.
7175
DebugMapObject *CurrentDebugMapObject;
7276

77+
/// Whether we need to skip the current debug map object.
78+
bool SkipDebugMapObject;
79+
7380
/// Holds function info while function scope processing.
7481
const char *CurrentFunctionName;
7582
uint64_t CurrentFunctionAddress;
7683

7784
std::unique_ptr<DebugMap> parseOneBinary(const MachOObjectFile &MainBinary,
7885
StringRef BinaryPath);
86+
void handleStabDebugMap(
87+
const MachOObjectFile &MainBinary,
88+
std::function<void(uint32_t, uint8_t, uint8_t, uint16_t, uint64_t)> F);
7989

8090
void
8191
switchToNewDebugMapObject(StringRef Filename,
@@ -85,13 +95,21 @@ class MachODebugMapParser {
8595
std::vector<StringRef> getMainBinarySymbolNames(uint64_t Value);
8696
void loadMainBinarySymbols(const MachOObjectFile &MainBinary);
8797
void loadCurrentObjectFileSymbols(const object::MachOObjectFile &Obj);
98+
99+
void handleStabOSOEntry(uint32_t StringIndex, uint8_t Type,
100+
uint8_t SectionIndex, uint16_t Flags, uint64_t Value,
101+
llvm::DenseSet<OSO> &OSOs,
102+
llvm::SmallSet<OSO, 4> &Duplicates);
88103
void handleStabSymbolTableEntry(uint32_t StringIndex, uint8_t Type,
89104
uint8_t SectionIndex, uint16_t Flags,
90-
uint64_t Value);
105+
uint64_t Value,
106+
const llvm::SmallSet<OSO, 4> &Duplicates);
91107

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);
108+
template <typename STEType>
109+
void handleStabDebugMapEntry(
110+
const STEType &STE,
111+
std::function<void(uint32_t, uint8_t, uint8_t, uint16_t, uint64_t)> F) {
112+
F(STE.n_strx, STE.n_type, STE.n_sect, STE.n_desc, STE.n_value);
95113
}
96114

97115
void addCommonSymbols();
@@ -140,6 +158,7 @@ void MachODebugMapParser::resetParserState() {
140158
CurrentObjectAliasMap.clear();
141159
SeenAliasValues.clear();
142160
CurrentDebugMapObject = nullptr;
161+
SkipDebugMapObject = false;
143162
}
144163

145164
/// Commons symbols won't show up in the symbol map but might need to be
@@ -199,21 +218,60 @@ static std::string getArchName(const object::MachOObjectFile &Obj) {
199218
return std::string(T.getArchName());
200219
}
201220

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

266+
// Build the debug map by iterating over the STABS again but ignore the
267+
// duplicate debug objects.
268+
handleStabDebugMap(MainBinary, [&](uint32_t StringIndex, uint8_t Type,
269+
uint8_t SectionIndex, uint16_t Flags,
270+
uint64_t Value) {
271+
handleStabSymbolTableEntry(StringIndex, Type, SectionIndex, Flags, Value,
272+
Duplicates);
273+
});
274+
217275
resetParserState();
218276
return std::move(Result);
219277
}
@@ -408,20 +466,38 @@ ErrorOr<std::vector<std::unique_ptr<DebugMap>>> MachODebugMapParser::parse() {
408466
return std::move(Results);
409467
}
410468

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

420488
const char *Name = &MainBinaryStrings.data()[StringIndex];
421489

422490
// An N_OSO entry represents the start of a new object file description.
423-
if (Type == MachO::N_OSO)
491+
if (Type == MachO::N_OSO) {
492+
if (Duplicates.count(OSO(Name, Value))) {
493+
SkipDebugMapObject = true;
494+
return;
495+
}
424496
return switchToNewDebugMapObject(Name, sys::toTimePoint(Value));
497+
}
498+
499+
if (SkipDebugMapObject)
500+
return;
425501

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

0 commit comments

Comments
 (0)