1 | //===- bolt/Passes/AsmDump.cpp - Dump BinaryFunction into assembly --------===// |
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 | // This file implements the AsmDumpPass class. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "bolt/Passes/AsmDump.h" |
14 | #include "llvm/CodeGen/AsmPrinter.h" |
15 | #include "llvm/MC/TargetRegistry.h" |
16 | #include "llvm/Support/FileSystem.h" |
17 | #include "llvm/Support/Path.h" |
18 | #include "llvm/Target/TargetMachine.h" |
19 | #include <unordered_set> |
20 | |
21 | #define DEBUG_TYPE "asm-dump" |
22 | |
23 | using namespace llvm; |
24 | |
25 | namespace opts { |
26 | extern bool shouldPrint(const bolt::BinaryFunction &Function); |
27 | extern cl::OptionCategory BoltCategory; |
28 | extern cl::opt<unsigned> Verbosity; |
29 | |
30 | cl::opt<std::string> AsmDump("asm-dump" , |
31 | cl::desc("dump function into assembly" ), |
32 | cl::value_desc("dump folder" ), cl::ValueOptional, |
33 | cl::Hidden, cl::cat(BoltCategory)); |
34 | } // end namespace opts |
35 | |
36 | namespace llvm { |
37 | namespace bolt { |
38 | |
39 | void dumpCFI(const BinaryFunction &BF, const MCInst &Instr, AsmPrinter &MAP) { |
40 | const MCCFIInstruction *CFIInstr = BF.getCFIFor(Instr); |
41 | switch (CFIInstr->getOperation()) { |
42 | // Skip unsupported CFI instructions. |
43 | case MCCFIInstruction::OpRememberState: |
44 | case MCCFIInstruction::OpRestoreState: |
45 | if (opts::Verbosity >= 2) |
46 | BF.getBinaryContext().errs() |
47 | << "BOLT-WARNING: AsmDump: skipping unsupported CFI instruction in " |
48 | << BF << ".\n" ; |
49 | |
50 | return; |
51 | |
52 | default: |
53 | // Emit regular CFI instructions. |
54 | MAP.emitCFIInstruction(Inst: *CFIInstr); |
55 | } |
56 | } |
57 | |
58 | void dumpTargetFunctionStub(raw_ostream &OS, const BinaryContext &BC, |
59 | const MCSymbol *CalleeSymb, |
60 | const BinarySection *&LastCS) { |
61 | const BinaryFunction *CalleeFunc = BC.getFunctionForSymbol(Symbol: CalleeSymb); |
62 | if (!CalleeFunc || CalleeFunc->isPLTFunction()) |
63 | return; |
64 | |
65 | if (CalleeFunc->getOriginSection() != LastCS) { |
66 | OS << ".section " << CalleeFunc->getOriginSectionName() << '\n'; |
67 | LastCS = CalleeFunc->getOriginSection(); |
68 | } |
69 | StringRef CalleeName = CalleeFunc->getOneName(); |
70 | OS << ".set \"" << CalleeName << "\", 0\n" ; |
71 | } |
72 | |
73 | void dumpJumpTableSymbols(raw_ostream &OS, const JumpTable *JT, AsmPrinter &MAP, |
74 | const BinarySection *&LastBS) { |
75 | if (&JT->getSection() != LastBS) { |
76 | OS << ".section " << JT->getSectionName() << '\n'; |
77 | LastBS = &JT->getSection(); |
78 | } |
79 | OS << "\"" << JT->getName() << "\":\n" ; |
80 | for (MCSymbol *JTEntry : JT->Entries) |
81 | MAP.OutStreamer->emitSymbolValue(Sym: JTEntry, Size: JT->OutputEntrySize); |
82 | OS << '\n'; |
83 | } |
84 | |
85 | void dumpBinaryDataSymbols(raw_ostream &OS, const BinaryData *BD, |
86 | const BinarySection *&LastBS) { |
87 | if (BD->isJumpTable()) |
88 | return; |
89 | if (&BD->getSection() != LastBS) { |
90 | OS << ".section " << BD->getSectionName() << '\n'; |
91 | LastBS = &BD->getSection(); |
92 | } |
93 | OS << "\"" << BD->getName() << "\": " ; |
94 | OS << '\n'; |
95 | } |
96 | |
97 | void dumpFunction(const BinaryFunction &BF) { |
98 | const BinaryContext &BC = BF.getBinaryContext(); |
99 | if (!opts::shouldPrint(Function: BF)) |
100 | return; |
101 | |
102 | // Make sure the new directory exists, creating it if necessary. |
103 | if (!opts::AsmDump.empty()) { |
104 | if (std::error_code EC = sys::fs::create_directories(path: opts::AsmDump)) { |
105 | BC.errs() << "BOLT-ERROR: could not create directory '" << opts::AsmDump |
106 | << "': " << EC.message() << '\n'; |
107 | return; |
108 | } |
109 | } |
110 | |
111 | std::string PrintName = BF.getPrintName(); |
112 | std::replace(first: PrintName.begin(), last: PrintName.end(), old_value: '/', new_value: '-'); |
113 | std::string Filename = |
114 | opts::AsmDump.empty() |
115 | ? (PrintName + ".s" ) |
116 | : (opts::AsmDump + sys::path::get_separator() + PrintName + ".s" ) |
117 | .str(); |
118 | BC.outs() << "BOLT-INFO: Dumping function assembly to " << Filename << "\n" ; |
119 | |
120 | std::error_code EC; |
121 | raw_fd_ostream OS(Filename, EC, sys::fs::OF_None); |
122 | if (EC) { |
123 | BC.errs() << "BOLT-ERROR: " << EC.message() << ", unable to open " |
124 | << Filename << " for output.\n" ; |
125 | return; |
126 | } |
127 | OS.SetUnbuffered(); |
128 | |
129 | // Create local MC context to isolate the effect of ephemeral assembly |
130 | // emission. |
131 | BinaryContext::IndependentCodeEmitter MCEInstance = |
132 | BC.createIndependentMCCodeEmitter(); |
133 | MCContext *LocalCtx = MCEInstance.LocalCtx.get(); |
134 | std::unique_ptr<MCAsmBackend> MAB( |
135 | BC.TheTarget->createMCAsmBackend(STI: *BC.STI, MRI: *BC.MRI, Options: MCTargetOptions())); |
136 | int AsmPrinterVariant = BC.AsmInfo->getAssemblerDialect(); |
137 | MCInstPrinter *InstructionPrinter(BC.TheTarget->createMCInstPrinter( |
138 | T: *BC.TheTriple, SyntaxVariant: AsmPrinterVariant, MAI: *BC.AsmInfo, MII: *BC.MII, MRI: *BC.MRI)); |
139 | auto FOut = std::make_unique<formatted_raw_ostream>(args&: OS); |
140 | FOut->SetUnbuffered(); |
141 | std::unique_ptr<MCStreamer> AsmStreamer( |
142 | createAsmStreamer(Ctx&: *LocalCtx, OS: std::move(FOut), |
143 | /*isVerboseAsm=*/true, |
144 | /*useDwarfDirectory=*/false, InstPrint: InstructionPrinter, |
145 | CE: std::move(MCEInstance.MCE), TAB: std::move(MAB), |
146 | /*ShowInst=*/false)); |
147 | AsmStreamer->initSections(NoExecStack: true, STI: *BC.STI); |
148 | std::unique_ptr<TargetMachine> TM(BC.TheTarget->createTargetMachine( |
149 | TT: BC.TripleName, CPU: "" , Features: "" , Options: TargetOptions(), RM: std::nullopt)); |
150 | std::unique_ptr<AsmPrinter> MAP( |
151 | BC.TheTarget->createAsmPrinter(TM&: *TM, Streamer: std::move(AsmStreamer))); |
152 | |
153 | StringRef FunctionName = BF.getOneName(); |
154 | OS << " .globl " << FunctionName << '\n'; |
155 | OS << " .type " << FunctionName << ", %function\n" ; |
156 | OS << FunctionName << ":\n" ; |
157 | |
158 | // FDATA for the entry point |
159 | if (uint64_t EntryExecCount = BF.getKnownExecutionCount()) |
160 | OS << "# FDATA: 0 [unknown] 0 " |
161 | << "1 " << FunctionName << " 0 " |
162 | << "0 " << EntryExecCount << '\n'; |
163 | |
164 | // Binary data references from the function. |
165 | std::unordered_set<const BinaryData *> BDReferences; |
166 | // Function references from the function (to avoid constructing call graph). |
167 | std::unordered_set<const MCSymbol *> CallReferences; |
168 | |
169 | MAP->OutStreamer->emitCFIStartProc(/*IsSimple=*/false); |
170 | for (const BinaryBasicBlock *BB : BF.getLayout().blocks()) { |
171 | OS << BB->getName() << ": \n" ; |
172 | |
173 | const std::string BranchLabel = Twine(BB->getName(), "_br" ).str(); |
174 | const MCInst *LastInst = BB->getLastNonPseudoInstr(); |
175 | |
176 | for (const MCInst &Instr : *BB) { |
177 | // Dump pseudo instructions (CFI) |
178 | if (BC.MIB->isPseudo(Inst: Instr)) { |
179 | if (BC.MIB->isCFI(Inst: Instr)) |
180 | dumpCFI(BF, Instr, MAP&: *MAP.get()); |
181 | continue; |
182 | } |
183 | |
184 | // Analyze symbol references (data, functions) from the instruction. |
185 | bool IsCall = BC.MIB->isCall(Inst: Instr); |
186 | for (const MCOperand &Operand : MCPlus::primeOperands(Inst: Instr)) { |
187 | if (Operand.isExpr() && |
188 | Operand.getExpr()->getKind() == MCExpr::SymbolRef) { |
189 | std::pair<const MCSymbol *, uint64_t> TSI = |
190 | BC.MIB->getTargetSymbolInfo(Expr: Operand.getExpr()); |
191 | const MCSymbol *Symbol = TSI.first; |
192 | if (IsCall) |
193 | CallReferences.insert(x: Symbol); |
194 | else if (const BinaryData *BD = |
195 | BC.getBinaryDataByName(Name: Symbol->getName())) |
196 | BDReferences.insert(x: BD); |
197 | } |
198 | } |
199 | |
200 | if (&Instr == LastInst && (BB->succ_size() || IsCall)) |
201 | OS << BranchLabel << ":\n" ; |
202 | |
203 | BC.InstPrinter->printInst(MI: &Instr, Address: 0, Annot: "" , STI: *BC.STI, OS); |
204 | OS << '\n'; |
205 | } |
206 | |
207 | // Dump profile data in FDATA format (as parsed by link_fdata). |
208 | for (const BinaryBasicBlock *Succ : BB->successors()) { |
209 | const BinaryBasicBlock::BinaryBranchInfo BI = BB->getBranchInfo(Succ: *Succ); |
210 | if (!BI.MispredictedCount && !BI.Count) |
211 | continue; |
212 | |
213 | OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# " |
214 | << "1 " << FunctionName << " #" << Succ->getName() << "# " |
215 | << BI.MispredictedCount << " " << BI.Count << '\n'; |
216 | } |
217 | |
218 | OS << '\n'; |
219 | } |
220 | MAP->OutStreamer->emitCFIEndProc(); |
221 | |
222 | OS << ".size " << FunctionName << ", .-" << FunctionName << '\n'; |
223 | |
224 | const BinarySection *LastSection = BF.getOriginSection(); |
225 | // Print stubs for all target functions. |
226 | for (const MCSymbol *CalleeSymb : CallReferences) |
227 | dumpTargetFunctionStub(OS, BC, CalleeSymb, LastCS&: LastSection); |
228 | |
229 | OS << "# Jump tables\n" ; |
230 | // Print all jump tables. |
231 | for (auto &JTI : BF.jumpTables()) |
232 | dumpJumpTableSymbols(OS, JT: JTI.second, MAP&: *MAP.get(), LastBS&: LastSection); |
233 | |
234 | OS << "# BinaryData\n" ; |
235 | // Print data references. |
236 | for (const BinaryData *BD : BDReferences) |
237 | dumpBinaryDataSymbols(OS, BD, LastBS&: LastSection); |
238 | } |
239 | |
240 | Error AsmDumpPass::runOnFunctions(BinaryContext &BC) { |
241 | for (const auto &BFIt : BC.getBinaryFunctions()) |
242 | dumpFunction(BF: BFIt.second); |
243 | return Error::success(); |
244 | } |
245 | |
246 | } // namespace bolt |
247 | } // namespace llvm |
248 | |