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 | llvm::replace(Range&: PrintName, OldValue: '/', NewValue: '-'); |
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 | std::unique_ptr<MCInstPrinter> InstructionPrinter( |
138 | BC.TheTarget->createMCInstPrinter(T: *BC.TheTriple, SyntaxVariant: AsmPrinterVariant, |
139 | MAI: *BC.AsmInfo, MII: *BC.MII, MRI: *BC.MRI)); |
140 | auto FOut = std::make_unique<formatted_raw_ostream>(args&: OS); |
141 | FOut->SetUnbuffered(); |
142 | std::unique_ptr<MCStreamer> AsmStreamer(createAsmStreamer( |
143 | Ctx&: *LocalCtx, OS: std::move(FOut), InstPrint: std::move(InstructionPrinter), |
144 | CE: std::move(MCEInstance.MCE), TAB: std::move(MAB))); |
145 | AsmStreamer->initSections(NoExecStack: true, STI: *BC.STI); |
146 | std::unique_ptr<TargetMachine> TM(BC.TheTarget->createTargetMachine( |
147 | TT: *BC.TheTriple, CPU: "" , Features: "" , Options: TargetOptions(), RM: std::nullopt)); |
148 | std::unique_ptr<AsmPrinter> MAP( |
149 | BC.TheTarget->createAsmPrinter(TM&: *TM, Streamer: std::move(AsmStreamer))); |
150 | |
151 | StringRef FunctionName = BF.getOneName(); |
152 | OS << " .globl " << FunctionName << '\n'; |
153 | OS << " .type " << FunctionName << ", %function\n" ; |
154 | OS << FunctionName << ":\n" ; |
155 | |
156 | // FDATA for the entry point |
157 | if (uint64_t EntryExecCount = BF.getKnownExecutionCount()) |
158 | OS << "# FDATA: 0 [unknown] 0 " |
159 | << "1 " << FunctionName << " 0 " |
160 | << "0 " << EntryExecCount << '\n'; |
161 | |
162 | // Binary data references from the function. |
163 | std::unordered_set<const BinaryData *> BDReferences; |
164 | // Function references from the function (to avoid constructing call graph). |
165 | std::unordered_set<const MCSymbol *> CallReferences; |
166 | |
167 | MAP->OutStreamer->emitCFIStartProc(/*IsSimple=*/false); |
168 | for (const BinaryBasicBlock *BB : BF.getLayout().blocks()) { |
169 | OS << BB->getName() << ": \n" ; |
170 | |
171 | const std::string BranchLabel = Twine(BB->getName(), "_br" ).str(); |
172 | const MCInst *LastInst = BB->getLastNonPseudoInstr(); |
173 | |
174 | for (const MCInst &Instr : *BB) { |
175 | // Dump pseudo instructions (CFI) |
176 | if (BC.MIB->isPseudo(Inst: Instr)) { |
177 | if (BC.MIB->isCFI(Inst: Instr)) |
178 | dumpCFI(BF, Instr, MAP&: *MAP); |
179 | continue; |
180 | } |
181 | |
182 | // Analyze symbol references (data, functions) from the instruction. |
183 | bool IsCall = BC.MIB->isCall(Inst: Instr); |
184 | for (const MCOperand &Operand : MCPlus::primeOperands(Inst: Instr)) { |
185 | if (Operand.isExpr() && |
186 | Operand.getExpr()->getKind() == MCExpr::SymbolRef) { |
187 | std::pair<const MCSymbol *, uint64_t> TSI = |
188 | BC.MIB->getTargetSymbolInfo(Expr: Operand.getExpr()); |
189 | const MCSymbol *Symbol = TSI.first; |
190 | if (IsCall) |
191 | CallReferences.insert(x: Symbol); |
192 | else if (const BinaryData *BD = |
193 | BC.getBinaryDataByName(Name: Symbol->getName())) |
194 | BDReferences.insert(x: BD); |
195 | } |
196 | } |
197 | |
198 | if (&Instr == LastInst && (BB->succ_size() || IsCall)) |
199 | OS << BranchLabel << ":\n" ; |
200 | |
201 | BC.InstPrinter->printInst(MI: &Instr, Address: 0, Annot: "" , STI: *BC.STI, OS); |
202 | OS << '\n'; |
203 | } |
204 | |
205 | // Dump profile data in FDATA format (as parsed by link_fdata). |
206 | for (const BinaryBasicBlock *Succ : BB->successors()) { |
207 | const BinaryBasicBlock::BinaryBranchInfo BI = BB->getBranchInfo(Succ: *Succ); |
208 | if (!BI.MispredictedCount && !BI.Count) |
209 | continue; |
210 | |
211 | OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# " |
212 | << "1 " << FunctionName << " #" << Succ->getName() << "# " |
213 | << BI.MispredictedCount << " " << BI.Count << '\n'; |
214 | } |
215 | |
216 | OS << '\n'; |
217 | } |
218 | MAP->OutStreamer->emitCFIEndProc(); |
219 | |
220 | OS << ".size " << FunctionName << ", .-" << FunctionName << '\n'; |
221 | |
222 | const BinarySection *LastSection = BF.getOriginSection(); |
223 | // Print stubs for all target functions. |
224 | for (const MCSymbol *CalleeSymb : CallReferences) |
225 | dumpTargetFunctionStub(OS, BC, CalleeSymb, LastCS&: LastSection); |
226 | |
227 | OS << "# Jump tables\n" ; |
228 | // Print all jump tables. |
229 | for (auto &JTI : BF.jumpTables()) |
230 | dumpJumpTableSymbols(OS, JT: JTI.second, MAP&: *MAP, LastBS&: LastSection); |
231 | |
232 | OS << "# BinaryData\n" ; |
233 | // Print data references. |
234 | for (const BinaryData *BD : BDReferences) |
235 | dumpBinaryDataSymbols(OS, BD, LastBS&: LastSection); |
236 | } |
237 | |
238 | Error AsmDumpPass::runOnFunctions(BinaryContext &BC) { |
239 | for (const auto &BFIt : BC.getBinaryFunctions()) |
240 | dumpFunction(BF: BFIt.second); |
241 | return Error::success(); |
242 | } |
243 | |
244 | } // namespace bolt |
245 | } // namespace llvm |
246 | |