1//===- DXContainerPDB.cpp - DirectX PDB writer pass -----------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "DirectX.h"
10#include "MCTargetDesc/DirectXContainerObjectWriter.h"
11#include "llvm/ADT/ScopeExit.h"
12#include "llvm/ADT/StringSet.h"
13#include "llvm/BinaryFormat/DXContainer.h"
14#include "llvm/DebugInfo/CodeView/GUID.h"
15#include "llvm/DebugInfo/MSF/MSFBuilder.h"
16#include "llvm/DebugInfo/PDB/Native/InfoStreamBuilder.h"
17#include "llvm/DebugInfo/PDB/Native/PDBFileBuilder.h"
18#include "llvm/IR/Constants.h"
19#include "llvm/IR/Module.h"
20#include "llvm/MC/MCDXContainerWriter.h"
21#include "llvm/Pass.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/FileSystem.h"
24#include "llvm/Support/IOSandbox.h"
25#include "llvm/Support/VirtualFileSystem.h"
26#include "llvm/Transforms/Utils/ModuleUtils.h"
27
28using namespace llvm;
29
30namespace {
31
32class DXContainerPDB : public ModulePass, MCDXContainerBaseWriter {
33 Module *M = nullptr;
34 SmallVector<MCDXContainerPart> Parts;
35
36 void reset() {
37 M = nullptr;
38 Parts.clear();
39 }
40
41public:
42 static char ID;
43 DXContainerPDB() : ModulePass(ID) {}
44
45 StringRef getPassName() const override { return "DirectX PDB Emitter"; }
46
47 bool runOnModule(Module &M) override;
48
49 void getAnalysisUsage(AnalysisUsage &AU) const override {
50 AU.setPreservesAll();
51 }
52
53 bool shouldSkipSection(StringRef SectionName, size_t SectionSize) override;
54 ArrayRef<MCDXContainerPart> collectParts() override;
55};
56
57} // namespace
58
59bool DXContainerPDB::shouldSkipSection(StringRef SectionName,
60 size_t SectionSize) {
61 if (MCDXContainerBaseWriter::shouldSkipSection(SectionName, SectionSize))
62 return true;
63 if (dxil::SlimDebug && SectionName == "ILDB")
64 return true;
65
66 // Skip sections that are irrelevant for debug info.
67 static const StringSet<> DebugSections{"ILDB", "ILDN", "HASH", "PDBI",
68 "SRCI", "STAT", "RDAT", "VERS"};
69 return !DebugSections.contains(key: SectionName);
70}
71
72static StringRef getGlobalData(const GlobalVariable &GV) {
73 if (GV.hasInitializer())
74 if (const auto *Data =
75 dyn_cast<ConstantDataSequential>(Val: GV.getInitializer()))
76 return Data->getRawDataValues();
77 return {};
78}
79
80ArrayRef<MCDXContainerPart> DXContainerPDB::collectParts() {
81 Parts.clear();
82 for (const GlobalVariable &GV : M->globals()) {
83 StringRef Name = GV.getSection();
84 StringRef Data = getGlobalData(GV);
85
86 if (Data.empty())
87 continue;
88 if (shouldSkipSection(SectionName: Name, SectionSize: Data.size()))
89 continue;
90
91 Parts.push_back(Elt: {.Name: Name, .Data: Data});
92 }
93 return Parts;
94}
95
96static GlobalVariable *createPrivateDataGlobal(Module &M, StringRef Data) {
97 Constant *Content =
98 ConstantDataArray::getString(Context&: M.getContext(), Initializer: Data, /*AddNull*/ false);
99 auto *GV =
100 new GlobalVariable(M, Content->getType(), true,
101 GlobalValue::PrivateLinkage, Content, "dx.priv");
102 GV->setSection("PRIV");
103 GV->setAlignment(Align(1));
104 return GV;
105}
106
107bool DXContainerPDB::runOnModule(Module &M) {
108 llvm::scope_exit Cleanup([&]() { reset(); });
109 this->M = &M;
110
111 SmallString<128> DebugFileName;
112 ArrayRef<char> ModuleHash;
113 for (const GlobalVariable &GV : M.globals()) {
114 if (GV.getSection() == PdbFileNameSectionName) {
115 assert(DebugFileName.empty() && "Duplicate PDBNAME section");
116 DebugFileName = getGlobalData(GV);
117 } else if (GV.getSection() == ModuleHashSectionName) {
118 assert(ModuleHash.empty() && "Duplicate PBDHASH section");
119 StringRef Data = getGlobalData(GV);
120 ModuleHash = ArrayRef(Data.data(), Data.size());
121 }
122 }
123
124 // PDB emission was not requested.
125 if (DebugFileName.empty() && !dxil::PdbInPrivate)
126 return false;
127 if (ModuleHash.empty())
128 report_fatal_error(reason: "Module hash for PDB not found");
129
130 bool DeleteAfterRead = false;
131 if (DebugFileName.empty()) {
132 if (std::error_code EC =
133 sys::fs::createTemporaryFile(Prefix: "dxil", Suffix: "pdb", ResultPath&: DebugFileName))
134 reportFatalInternalError(reason: "Failed to create temporary PDB file");
135 DeleteAfterRead = true;
136 }
137 llvm::scope_exit FileCleanup([&]() {
138 if (DeleteAfterRead)
139 sys::fs::remove(path: DebugFileName);
140 });
141
142 BumpPtrAllocator Allocator;
143 pdb::PDBFileBuilder Builder(Allocator);
144
145 // DirectXShaderCompiler uses block size 512.
146 if (Error Err = Builder.initialize(BlockSize: 512))
147 reportFatalInternalError(Err: std::move(Err));
148
149 // Reserved streams that should be empty.
150 static_assert(pdb::kSpecialStreamCount == 5 &&
151 "First 5 streams should be empty in DirectX PDB file");
152 for (uint32_t I = 0; I < pdb::kSpecialStreamCount; ++I) {
153 if (auto Err = Builder.getMsfBuilder().addStream(Size: 0).takeError())
154 reportFatalInternalError(Err: std::move(Err));
155 }
156
157 // Add DXContainer stream.
158 if (auto Err = Builder.getMsfBuilder().addStream(Size: 0).takeError())
159 reportFatalInternalError(Err: std::move(Err));
160
161 // InfoStream must be filled. Bitcode hash from HASH part is used for PDB
162 // GUID.
163 codeview::GUID PdbGuid;
164 assert(ModuleHash.size() == std::size(PdbGuid.Guid) &&
165 "Module hash length must be match GUID length");
166 std::copy_n(first: ModuleHash.begin(), n: std::size(PdbGuid.Guid), result: PdbGuid.Guid);
167
168 auto &InfoBuilder = Builder.getInfoBuilder();
169 InfoBuilder.setAge(1);
170 InfoBuilder.setGuid(PdbGuid);
171 InfoBuilder.setSignature(0);
172 InfoBuilder.setVersion(pdb::PdbRaw_ImplVer::PdbImplVC70);
173
174 // Write DXContainer.
175 raw_svector_ostream OS(*Builder.getDXContainerData());
176 write(OS, TT: M.getTargetTriple());
177
178 // Write PDB file.
179 codeview::GUID IgnoredOutGuid;
180 {
181 // FIXME(sandboxing): Remove this by routing output through OutputBackend.
182 auto BypassSandbox = sys::sandbox::scopedDisable();
183 if (Error Err = Builder.commit(Filename: DebugFileName, Guid: &IgnoredOutGuid))
184 reportFatalUsageError(reason: "Couldn't write to PDB file: " +
185 Twine(toString(E: std::move(Err))));
186 }
187
188 if (!dxil::PdbInPrivate)
189 return false;
190
191 ErrorOr<std::unique_ptr<MemoryBuffer>> Buf =
192 M.getContext().getVirtualFileSystem().getBufferForFile(
193 Name: DebugFileName, /*FileSize=*/-1, /*RequiresNullTerminator=*/false,
194 /*IsVolatile=*/false, /*IsText=*/false);
195 if (!Buf)
196 reportFatalInternalError(reason: "Failed to read PDB for PRIV embedding");
197
198 appendToCompilerUsed(M, Values: createPrivateDataGlobal(M, Data: (*Buf)->getBuffer()));
199
200 return true;
201}
202
203char DXContainerPDB::ID = 0;
204INITIALIZE_PASS(DXContainerPDB, "dxil-pdb", "DirectX PDB Emitter", false, true)
205
206ModulePass *llvm::createDXContainerPDBPass() { return new DXContainerPDB(); }
207