Skip to content

Commit c53a132

Browse files
committed
[lld-macho] Implement -dependency_info (partially - more opcodes needed)
Bug: https://bugs.llvm.org/show_bug.cgi?id=49278 The flag is not well documented, so this implementation is based on observed behaviour. When specified, `-dependency_info <path>` produced a text file containing information pertaining to the current linkage, such as input files, output file, linker version, etc. This file's layout is also not documented, but it seems to be a series of null ('\0') terminated strings in the form `<op code><path>` `<op code>` could be: `0x00` : linker version `0x10` : input `0x11` : files not found(??) `0x40` : output `<path>` : is the file path, except for the linker-version case. (??) This part is a bit unclear. I think it means all the files the linker attempted to look at, but could not find. Differential Revision: https://reviews.llvm.org/D98559
1 parent 30080b0 commit c53a132

File tree

6 files changed

+190
-3
lines changed

6 files changed

+190
-3
lines changed

lld/MachO/Driver.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ using namespace llvm::sys;
5454
using namespace lld;
5555
using namespace lld::macho;
5656

57-
Configuration *lld::macho::config;
57+
Configuration *macho::config;
58+
DependencyTracker *macho::depTracker;
5859

5960
static HeaderFileType getOutputType(const InputArgList &args) {
6061
// TODO: -r, -dylinker, -preload...
@@ -84,6 +85,8 @@ findAlongPathsWithExtensions(StringRef name, ArrayRef<StringRef> extensions) {
8485
Twine location = base + ext;
8586
if (fs::exists(location))
8687
return location.str();
88+
else
89+
depTracker->logFileNotFound(location);
8790
}
8891
}
8992
return {};
@@ -815,6 +818,9 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
815818
symtab = make<SymbolTable>();
816819
target = createTargetInfo(args);
817820

821+
depTracker =
822+
make<DependencyTracker>(args.getLastArgValue(OPT_dependency_info, ""));
823+
818824
config->entry = symtab->addUndefined(args.getLastArgValue(OPT_e, "_main"),
819825
/*file=*/nullptr,
820826
/*isWeakRef=*/false);
@@ -1066,6 +1072,8 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
10661072

10671073
// Write to an output file.
10681074
writeResult();
1075+
1076+
depTracker->write(getLLDVersion(), inputFiles, config->outputFile);
10691077
}
10701078

10711079
if (config->timeTraceEnabled) {

lld/MachO/Driver.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111

1212
#include "lld/Common/LLVM.h"
1313
#include "llvm/ADT/Optional.h"
14+
#include "llvm/ADT/SetVector.h"
1415
#include "llvm/ADT/StringRef.h"
1516
#include "llvm/Option/OptTable.h"
1617
#include "llvm/Support/MemoryBuffer.h"
1718

19+
#include <set>
20+
#include <type_traits>
21+
1822
namespace llvm {
1923
namespace MachO {
2024
class InterfaceFile;
@@ -61,6 +65,45 @@ uint32_t getModTime(llvm::StringRef path);
6165

6266
void printArchiveMemberLoad(StringRef reason, const InputFile *);
6367

68+
// Helper class to export dependency info.
69+
class DependencyTracker {
70+
public:
71+
explicit DependencyTracker(llvm::StringRef path);
72+
73+
// Adds the given path to the set of not-found files.
74+
void logFileNotFound(std::string);
75+
void logFileNotFound(const llvm::Twine &path);
76+
77+
// Writes the dependencies to specified path.
78+
// The content is sorted by its Op Code, then within each section,
79+
// alphabetical order.
80+
void write(llvm::StringRef version,
81+
const llvm::SetVector<InputFile *> &inputs,
82+
llvm::StringRef output);
83+
84+
private:
85+
enum DepOpCode : char {
86+
// Denotes the linker version.
87+
Version = 0x00,
88+
// Denotes the input files.
89+
Input = 0x10,
90+
// Denotes the files that do not exist(?)
91+
NotFound = 0x11,
92+
// Denotes the output files.
93+
Output = 0x40,
94+
};
95+
96+
const llvm::StringRef path;
97+
bool active;
98+
99+
// The paths need to be alphabetically ordered.
100+
// We need to own the paths because some of them are temporarily
101+
// constructed.
102+
std::set<std::string> notFounds;
103+
};
104+
105+
extern DependencyTracker *depTracker;
106+
64107
} // namespace macho
65108
} // namespace lld
66109

lld/MachO/DriverUtils.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "llvm/Option/ArgList.h"
2424
#include "llvm/Option/Option.h"
2525
#include "llvm/Support/CommandLine.h"
26+
#include "llvm/Support/FileSystem.h"
2627
#include "llvm/Support/Path.h"
2728
#include "llvm/TextAPI/MachO/InterfaceFile.h"
2829
#include "llvm/TextAPI/MachO/TextAPIReader.h"
@@ -164,12 +165,15 @@ Optional<std::string> macho::resolveDylibPath(StringRef path) {
164165
// they are consistent.
165166
if (fs::exists(path))
166167
return std::string(path);
168+
else
169+
depTracker->logFileNotFound(path);
167170

168171
SmallString<261> location = path;
169172
path::replace_extension(location, ".tbd");
170173
if (fs::exists(location))
171174
return std::string(location);
172-
175+
else
176+
depTracker->logFileNotFound(location);
173177
return {};
174178
}
175179

@@ -240,3 +244,59 @@ void macho::printArchiveMemberLoad(StringRef reason, const InputFile *f) {
240244
if (config->printWhyLoad)
241245
message(reason + " forced load of " + toString(f));
242246
}
247+
248+
macho::DependencyTracker::DependencyTracker(StringRef path)
249+
: path(path), active(!path.empty()) {
250+
if (active && fs::exists(path) && !fs::can_write(path)) {
251+
warn("Ignoring dependency_info option since specified path is not "
252+
"writeable.");
253+
active = false;
254+
}
255+
}
256+
257+
inline void macho::DependencyTracker::logFileNotFound(std::string path) {
258+
if (active)
259+
notFounds.insert(std::move(path));
260+
}
261+
262+
inline void macho::DependencyTracker::logFileNotFound(const Twine &path) {
263+
if (active)
264+
notFounds.insert(path.str());
265+
}
266+
267+
void macho::DependencyTracker::write(llvm::StringRef version,
268+
const llvm::SetVector<InputFile *> &inputs,
269+
llvm::StringRef output) {
270+
if (!active)
271+
return;
272+
273+
std::error_code ec;
274+
llvm::raw_fd_ostream os(path, ec, llvm::sys::fs::OF_None);
275+
if (ec) {
276+
warn("Error writing dependency info to file");
277+
return;
278+
}
279+
280+
auto addDep = [&os](DepOpCode opcode, const StringRef &path) {
281+
os << opcode;
282+
os << path;
283+
os << '\0';
284+
};
285+
286+
addDep(DepOpCode::Version, version);
287+
288+
// Sort the input by its names.
289+
std::vector<StringRef> inputNames;
290+
inputNames.reserve(inputs.size());
291+
for (InputFile *f : inputs)
292+
inputNames.push_back(f->getName());
293+
llvm::sort(inputNames,
294+
[](const StringRef &a, const StringRef &b) { return a < b; });
295+
for (const StringRef &in : inputNames)
296+
addDep(DepOpCode::Input, in);
297+
298+
for (const std::string &f : notFounds)
299+
addDep(DepOpCode::NotFound, f);
300+
301+
addDep(DepOpCode::Output, output);
302+
}

lld/MachO/Options.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,6 @@ def map : Separate<["-"], "map">,
504504
def dependency_info : Separate<["-"], "dependency_info">,
505505
MetaVarName<"<path>">,
506506
HelpText<"Dump dependency info">,
507-
Flags<[HelpHidden]>,
508507
Group<grp_introspect>;
509508
def save_temps : Flag<["-"], "save-temps">,
510509
HelpText<"Save intermediate LTO compilation results">,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Dump the dependency file (produced with -dependency_info) to text
3+
# format for testing purposes.
4+
#
5+
6+
import sys
7+
8+
f = open(sys.argv[1], "rb")
9+
byte = f.read(1)
10+
while byte != b'':
11+
if byte == b'\x00':
12+
sys.stdout.write("lld-version: ")
13+
elif byte == b'\x10':
14+
sys.stdout.write("input-file: ")
15+
elif byte == b'\x11':
16+
sys.stdout.write("not-found: ")
17+
elif byte == b'\x40':
18+
sys.stdout.write("output-file: ")
19+
byte = f.read(1)
20+
while byte != b'\x00':
21+
sys.stdout.write(byte.decode("ascii"))
22+
byte = f.read(1)
23+
sys.stdout.write("\n")
24+
byte = f.read(1)
25+
26+
f.close()

lld/test/MachO/dependency-info.s

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# REQUIRES: x86
2+
## FIXME: Paths on windows have both `\` and '/', as a result, they are in a different
3+
## order when sorted. Maybe create a separate test for that?
4+
# UNSUPPORTED: system-windows
5+
#
6+
# RUN: rm -rf %t
7+
# RUN: split-file %s %t
8+
9+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/foo.o %t/foo.s
10+
# RUN: %lld -dylib -o %t/libfoo.dylib %t/foo.o
11+
12+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/bar.o %t/bar.s
13+
# RUN: llvm-ar csr %t/bar.a %t/bar.o
14+
15+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/main.o %t/main.s
16+
17+
# RUN: %lld %t/main.o %t/bar.a %t/libfoo.dylib -lSystem -dependency_info %t/deps_info.out
18+
19+
# RUN: %python %S/Inputs/DependencyDump.py %t/deps_info.out | FileCheck %s
20+
21+
# CHECK: lld-version: LLD {{.*}}
22+
23+
# CHECK-NEXT: input-file: {{.*}}/bar.a
24+
# CHECK-NEXT: input-file: {{.*}}/libfoo.dylib
25+
# CHECK-NEXT: input-file: {{.*}}/main.o
26+
# CHECK-NEXT: input-file: {{.*}}/libSystem.tbd
27+
# CHECK-NEXT: bar.o
28+
29+
# CHECK-NEXT: not-found: {{.*}}/libdyld.dylib
30+
## There could be more not-found here but we are not checking those because it's brittle.
31+
32+
# CHECK: output-file: a.out
33+
34+
35+
#--- foo.s
36+
.globl __Z3foo
37+
__Z3foo:
38+
ret
39+
40+
#--- bar.s
41+
.globl _bar
42+
_bar:
43+
callq __Z3foo
44+
ret
45+
46+
#--- main.s
47+
.globl _main
48+
_main:
49+
callq _bar
50+
callq __Z3foo
51+
ret

0 commit comments

Comments
 (0)