| 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 | |