diff --git a/llvm/docs/CommandGuide/llvm-profgen.rst b/llvm/docs/CommandGuide/llvm-profgen.rst --- a/llvm/docs/CommandGuide/llvm-profgen.rst +++ b/llvm/docs/CommandGuide/llvm-profgen.rst @@ -24,6 +24,10 @@ Path of perf-script trace created by Linux perf tool with `script` command(the raw perf.data should be profiled with -b). +.. option:: --binary= + + Path of the input profiled binary files. + .. option:: --output= Path of the output profile file. @@ -32,11 +36,15 @@ ------- :program:`llvm-profgen` supports the following options: -.. option:: --binary= - - Path of the input profiled binary files. If no file path is specified, the - path of the actual profiled binaries will be used instead. - .. option:: --show-mmap-events Print mmap events. + +.. option:: --show-disassembly + + Print disassembled code. + +.. option:: --x86-asm-syntax=[att|intel] + + Specify whether to print assembly code in AT&T syntax (the default) or Intel + syntax. diff --git a/llvm/test/tools/llvm-profgen/disassemble.s b/llvm/test/tools/llvm-profgen/disassemble.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-profgen/disassemble.s @@ -0,0 +1,121 @@ +# REQUIRES: x86-registered-target +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t +# RUN: llvm-profgen --binary=%t --perfscript=%s --output=%t1 -show-disassembly -x86-asm-syntax=intel | FileCheck %s --match-full-lines + +# CHECK: Disassembly of section .text [0x0, 0x66]: +# CHECK: : +# CHECK: 0: push rbp +# CHECK: 1: mov rbp, rsp +# CHECK: 4: sub rsp, 16 +# CHECK: 8: mov dword ptr [rbp - 4], 0 +# CHECK: f: mov edi, 1 +# CHECK: 14: call 0x19 +# CHECK: 19: mov edi, 2 +# CHECK: 1e: mov dword ptr [rbp - 8], eax +# CHECK: 21: call 0x26 +# CHECK: 26: mov ecx, dword ptr [rbp - 8] +# CHECK: 29: add ecx, eax +# CHECK: 2b: mov eax, ecx +# CHECK: 2d: add rsp, 16 +# CHECK: 31: pop rbp +# CHECK: 32: ret + +# CHECK: : +# CHECK: 33: push rbp +# CHECK: 34: mov rbp, rsp +# CHECK: 37: sub rsp, 16 +# CHECK: 3b: mov dword ptr [rbp - 4], 0 +# CHECK: 42: mov edi, 1 +# CHECK: 47: call 0x4c +# CHECK: 4c: mov edi, 2 +# CHECK: 51: mov dword ptr [rbp - 8], eax +# CHECK: 54: call 0x59 +# CHECK: 59: mov ecx, dword ptr [rbp - 8] +# CHECK: 5c: add ecx, eax +# CHECK: 5e: mov eax, ecx +# CHECK: 60: add rsp, 16 +# CHECK: 64: pop rbp +# CHECK: 65: ret + + + +.section .text +foo1: + pushq %rbp + movq %rsp, %rbp + subq $16, %rsp + movl $0, -4(%rbp) + movl $1, %edi + callq _Z5funcAi + movl $2, %edi + movl %eax, -8(%rbp) + callq _Z5funcBi + movl -8(%rbp), %ecx + addl %eax, %ecx + movl %ecx, %eax + addq $16, %rsp + popq %rbp + retq + +.section .text +foo2: + pushq %rbp + movq %rsp, %rbp + subq $16, %rsp + movl $0, -4(%rbp) + movl $1, %edi + callq _Z5funcBi + movl $2, %edi + movl %eax, -8(%rbp) + callq _Z5funcAi + movl -8(%rbp), %ecx + addl %eax, %ecx + movl %ecx, %eax + addq $16, %rsp + popq %rbp + retq + +# CHECK: Disassembly of section .text.hot [0x0, 0x12]: +# CHECK: : +# CHECK: 0: push rbp +# CHECK: 1: mov rbp, rsp +# CHECK: 4: mov dword ptr [rbp - 4], edi +# CHECK: 7: mov dword ptr [rbp - 8], esi +# CHECK: a: mov eax, dword ptr [rbp - 4] +# CHECK: d: add eax, dword ptr [rbp - 8] +# CHECK: 10: pop rbp +# CHECK: 11: ret + +.section .text.hot +bar: + pushq %rbp + movq %rsp, %rbp + movl %edi, -4(%rbp) + movl %esi, -8(%rbp) + movl -4(%rbp), %eax + addl -8(%rbp), %eax + popq %rbp + retq + + +# CHECK: Disassembly of section .text.unlikely [0x0, 0x12]: +# CHECK: : +# CHECK: 0: push rbp +# CHECK: 1: mov rbp, rsp +# CHECK: 4: mov dword ptr [rbp - 4], edi +# CHECK: 7: mov dword ptr [rbp - 8], esi +# CHECK: a: mov eax, dword ptr [rbp - 4] +# CHECK: d: sub eax, dword ptr [rbp - 8] +# CHECK: 10: pop rbp +# CHECK: 11: ret + +.section .text.unlikely +baz: + pushq %rbp + movq %rsp, %rbp + movl %edi, -4(%rbp) + movl %esi, -8(%rbp) + movl -4(%rbp), %eax + subl -8(%rbp), %eax + popq %rbp + retq diff --git a/llvm/test/tools/llvm-profgen/lit.local.cfg b/llvm/test/tools/llvm-profgen/lit.local.cfg --- a/llvm/test/tools/llvm-profgen/lit.local.cfg +++ b/llvm/test/tools/llvm-profgen/lit.local.cfg @@ -3,4 +3,7 @@ config.suffixes = ['.test', '.ll', '.s', '.yaml'] if not lit.util.which("llvm-profgen", config.llvm_tools_dir): - config.unsupported = True + config.unsupported = True + +if not 'X86' in config.root.targets: + config.unsupported = True diff --git a/llvm/test/tools/llvm-profgen/mmapEvent.test b/llvm/test/tools/llvm-profgen/mmapEvent.test --- a/llvm/test/tools/llvm-profgen/mmapEvent.test +++ b/llvm/test/tools/llvm-profgen/mmapEvent.test @@ -1,4 +1,6 @@ -; RUN: llvm-profgen --perfscript=%s --output=%t --show-mmap-events | FileCheck %s +; REQUIRES: x86-registered-target +; RUN: llvm-mc -filetype=obj -triple=x86_64 %S/disassemble.s -o %t +; RUN: llvm-profgen --perfscript=%s --binary=%t --output=%t --show-mmap-events | FileCheck %s PERF_RECORD_MMAP2 2580483/2580483: [0x400000(0x1000) @ 0 103:01 539973862 1972407324]: r-xp /home/a.out PERF_RECORD_MMAP2 2580483/2580483: [0x7f2505b40000(0x224000) @ 0 08:04 19532214 4169021329]: r-xp /usr/lib64/ld-2.17.so diff --git a/llvm/tools/llvm-profgen/CMakeLists.txt b/llvm/tools/llvm-profgen/CMakeLists.txt --- a/llvm/tools/llvm-profgen/CMakeLists.txt +++ b/llvm/tools/llvm-profgen/CMakeLists.txt @@ -1,11 +1,19 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/X86 + ${LLVM_BINARY_DIR}/lib/Target/X86 + ) set(LLVM_LINK_COMPONENTS + AllTargetsDescs + AllTargetsDisassemblers Core - ProfileData + MC + MCDisassembler + Object Support - Symbolize ) add_llvm_tool(llvm-profgen llvm-profgen.cpp PerfReader.cpp + ProfiledBinary.cpp ) diff --git a/llvm/tools/llvm-profgen/ErrorHandling.h b/llvm/tools/llvm-profgen/ErrorHandling.h --- a/llvm/tools/llvm-profgen/ErrorHandling.h +++ b/llvm/tools/llvm-profgen/ErrorHandling.h @@ -38,4 +38,11 @@ LLVM_ATTRIBUTE_NORETURN inline void exitWithError(Error E, StringRef Whence) { exitWithError(errorToErrorCode(std::move(E)), Whence); } + +template +T unwrapOrError(Expected EO, Ts &&... Args) { + if (EO) + return std::move(*EO); + exitWithError(EO.takeError(), std::forward(Args)...); +} #endif diff --git a/llvm/tools/llvm-profgen/LLVMBuild.txt b/llvm/tools/llvm-profgen/LLVMBuild.txt --- a/llvm/tools/llvm-profgen/LLVMBuild.txt +++ b/llvm/tools/llvm-profgen/LLVMBuild.txt @@ -18,4 +18,4 @@ type = Tool name = llvm-profgen parent = Tools -required_libraries = Support +required_libraries = DebugInfoDWARF MC MCDisassembler MCParser Object all-targets Demangle Support diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp --- a/llvm/tools/llvm-profgen/PerfReader.cpp +++ b/llvm/tools/llvm-profgen/PerfReader.cpp @@ -127,5 +127,5 @@ parseTrace(Filename); } -} // namespace sampleprof -} // namespace llvm +} // end namespace sampleprof +} // end namespace llvm diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h --- a/llvm/tools/llvm-profgen/ProfiledBinary.h +++ b/llvm/tools/llvm-profgen/ProfiledBinary.h @@ -9,14 +9,80 @@ #ifndef LLVM_TOOLS_LLVM_PROFGEN_PROFILEDBINARY_H #define LLVM_TOOLS_LLVM_PROFGEN_PROFILEDBINARY_H #include "llvm/ADT/StringRef.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" +#include "llvm/MC/MCInst.h" +#include "llvm/MC/MCInstPrinter.h" +#include "llvm/MC/MCInstrAnalysis.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/Path.h" +#include +#include +#include +#include +#include + +using namespace llvm::object; namespace llvm { namespace sampleprof { class ProfiledBinary { + // Absolute path of the binary. std::string Path; + // The target triple. + Triple TheTriple; + // The runtime base address that the executable sections are loaded at. mutable uint64_t BaseAddress = 0; + // The preferred base address that the executable sections are loaded at. + uint64_t PreferredBaseAddress = 0; + // Mutiple MC component info + std::unique_ptr MRI; + std::unique_ptr AsmInfo; + std::unique_ptr STI; + std::unique_ptr MII; + std::unique_ptr DisAsm; + std::unique_ptr MIA; + std::unique_ptr IP; + // A list of text sections sorted by start RVA and size. Used to check + // if a given RVA is a valid code address. + std::set> TextSections; + // Function offset to name mapping. + std::unordered_map FuncStartAddrMap; + // An array of offsets of all instructions sorted in increasing order. The + // sorting is needed to fast advance to the next forward/backward instruction. + std::vector CodeAddrs; + // A set of call instruction offsets. Used by virtual unwinding. + std::unordered_set CallAddrs; + // A set of return instruction offsets. Used by virtual unwinding. + std::unordered_set RetAddrs; + + void setPreferredBaseAddress(const ELFObjectFileBase *O); + + // Set up disassembler and related components. + void setUpDisassembler(const ELFObjectFileBase *Obj); + + /// Dissassemble the text section and build various address maps. + void disassemble(const ELFObjectFileBase *O); + + /// Helper function to dissassemble the symbol and extract info for unwinding + bool dissassembleSymbol(std::size_t SI, ArrayRef Bytes, + SectionSymbolsTy &Symbols, const SectionRef &Section); + + /// Decode the interesting parts of the binary and build internal data + /// structures. On high level, the parts of interest are: + /// 1. Text sections, including the main code section and the PLT + /// entries that will be used to handle cross-module call transitions. + /// 2. The .debug_line section, used by Dwarf-based profile generation. + /// 3. Pseudo probe related sections, used by probe-based profile + /// generation. + void load(); public: ProfiledBinary(StringRef Path) : Path(Path) { load(); } @@ -25,11 +91,6 @@ const StringRef getName() const { return llvm::sys::path::filename(Path); } uint64_t getBaseAddress() const { return BaseAddress; } void setBaseAddress(uint64_t Address) { BaseAddress = Address; } - -private: - void load() { - // TODO: - } }; } // end namespace sampleprof diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp @@ -0,0 +1,263 @@ +//===-- ProfiledBinary.cpp - Binary decoder ---------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ProfiledBinary.h" +#include "ErrorHandling.h" +#include "MCTargetDesc/X86MCTargetDesc.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/X86TargetParser.h" + +#define DEBUG_TYPE "load-binary" + +using namespace llvm; + +static cl::opt ShowDisassembly("show-disassembly", cl::ReallyHidden, + cl::init(false), cl::ZeroOrMore, + cl::desc("Print disassembled code.")); + +namespace llvm { +namespace sampleprof { + +static const Target *getTarget(const ObjectFile *Obj) { + Triple TheTriple = Obj->makeTriple(); + std::string Error; + std::string ArchName; + const Target *TheTarget = + TargetRegistry::lookupTarget(ArchName, TheTriple, Error); + if (!TheTarget) + exitWithError(Error, Obj->getFileName()); + return TheTarget; +} + +template +static uint64_t getELFImageLMAForSec(const ELFFile *Obj, + const object::ELFSectionRef &Sec, + StringRef FileName) { + // Search for a PT_LOAD segment containing the requested section. Return this + // segment's p_addr as the image load address for the section. + const auto &PhdrRange = unwrapOrError(Obj->program_headers(), FileName); + for (const typename ELFT::Phdr &Phdr : PhdrRange) + if ((Phdr.p_type == ELF::PT_LOAD) && (Phdr.p_vaddr <= Sec.getAddress()) && + (Phdr.p_vaddr + Phdr.p_memsz > Sec.getAddress())) + // Segments will always be loaded at a page boundary. + return Phdr.p_paddr & ~(Phdr.p_align - 1U); + return 0; +} + +// Get the image load address for a specific section. Note that an image is +// loaded by segments (a group of sections) and segments may not be consecutive +// in memory. +static uint64_t getELFImageLMAForSec(const object::ELFSectionRef &Sec) { + if (const auto *ELFObj = dyn_cast(Sec.getObject())) + return getELFImageLMAForSec(ELFObj->getELFFile(), Sec, + ELFObj->getFileName()); + else if (const auto *ELFObj = dyn_cast(Sec.getObject())) + return getELFImageLMAForSec(ELFObj->getELFFile(), Sec, + ELFObj->getFileName()); + else if (const auto *ELFObj = dyn_cast(Sec.getObject())) + return getELFImageLMAForSec(ELFObj->getELFFile(), Sec, + ELFObj->getFileName()); + const auto *ELFObj = cast(Sec.getObject()); + return getELFImageLMAForSec(ELFObj->getELFFile(), Sec, ELFObj->getFileName()); +} + +void ProfiledBinary::load() { + // Attempt to open the binary. + OwningBinary OBinary = unwrapOrError(createBinary(Path), Path); + Binary &Binary = *OBinary.getBinary(); + + auto *Obj = dyn_cast(&Binary); + if (!Obj) + exitWithError("not a valid Elf image", Path); + + TheTriple = Obj->makeTriple(); + // Current only support X86 + if (!TheTriple.isX86()) + exitWithError("unsupported target", TheTriple.getTriple()); + LLVM_DEBUG(dbgs() << "Loading " << Path << "\n"); + + // Find the preferred base address for text sections. + setPreferredBaseAddress(Obj); + + // Disassemble the text sections. + disassemble(Obj); + + // TODO: decode other sections. + + return; +} + +void ProfiledBinary::setPreferredBaseAddress(const ELFObjectFileBase *Obj) { + for (section_iterator SI = Obj->section_begin(), SE = Obj->section_end(); + SI != SE; ++SI) { + const SectionRef &Section = *SI; + if (Section.isText()) { + PreferredBaseAddress = getELFImageLMAForSec(Section); + return; + } + } + exitWithError("no text section found", Obj->getFileName()); +} + +bool ProfiledBinary::dissassembleSymbol(std::size_t SI, ArrayRef Bytes, + SectionSymbolsTy &Symbols, + const SectionRef &Section) { + + std::size_t SE = Symbols.size(); + uint64_t SectionOffset = Section.getAddress() - PreferredBaseAddress; + uint64_t SectSize = Section.getSize(); + uint64_t StartOffset = Symbols[SI].Addr - PreferredBaseAddress; + uint64_t EndOffset = (SI + 1 < SE) + ? Symbols[SI + 1].Addr - PreferredBaseAddress + : SectionOffset + SectSize; + if (StartOffset >= EndOffset) + return true; + + std::string &&SymbolName = Symbols[SI].Name.str(); + if (ShowDisassembly) + outs() << '<' << SymbolName << ">:\n"; + + uint64_t Offset = StartOffset; + while (Offset < EndOffset) { + MCInst Inst; + uint64_t Size; + // Disassemble an instruction. + if (!DisAsm->getInstruction(Inst, Size, Bytes.slice(Offset - SectionOffset), + Offset + PreferredBaseAddress, nulls())) + return false; + + if (ShowDisassembly) { + outs() << format("%8" PRIx64 ":", Offset); + IP->printInst(&Inst, Offset + Size, "", *STI.get(), outs()); + outs() << "\n"; + } + + const MCInstrDesc &MCDesc = MII->get(Inst.getOpcode()); + + // Populate address maps. + CodeAddrs.push_back(Offset); + if (MCDesc.isCall()) + CallAddrs.insert(Offset); + else if (MCDesc.isReturn()) + RetAddrs.insert(Offset); + + Offset += Size; + } + + if (ShowDisassembly) + outs() << "\n"; + + FuncStartAddrMap[StartOffset] = Symbols[SI].Name.str(); + return true; +} + +void ProfiledBinary::setUpDisassembler(const ELFObjectFileBase *Obj) { + const Target *TheTarget = getTarget(Obj); + std::string TripleName = TheTriple.getTriple(); + StringRef FileName = Obj->getFileName(); + + MRI.reset(TheTarget->createMCRegInfo(TripleName)); + if (!MRI) + exitWithError("no register info for target " + TripleName, FileName); + + MCTargetOptions MCOptions; + AsmInfo.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); + if (!AsmInfo) + exitWithError("no assembly info for target " + TripleName, FileName); + + SubtargetFeatures Features = Obj->getFeatures(); + STI.reset( + TheTarget->createMCSubtargetInfo(TripleName, "", Features.getString())); + if (!STI) + exitWithError("no subtarget info for target " + TripleName, FileName); + + MII.reset(TheTarget->createMCInstrInfo()); + if (!MII) + exitWithError("no instruction info for target " + TripleName, FileName); + + MCObjectFileInfo MOFI; + MCContext Ctx(AsmInfo.get(), MRI.get(), &MOFI); + MOFI.InitMCObjectFileInfo(Triple(TripleName), false, Ctx); + DisAsm.reset(TheTarget->createMCDisassembler(*STI, Ctx)); + if (!DisAsm) + exitWithError("no disassembler for target " + TripleName, FileName); + + MIA.reset(TheTarget->createMCInstrAnalysis(MII.get())); + + int AsmPrinterVariant = AsmInfo->getAssemblerDialect(); + IP.reset(TheTarget->createMCInstPrinter(Triple(TripleName), AsmPrinterVariant, + *AsmInfo, *MII, *MRI)); + IP->setPrintBranchImmAsAddress(true); +} + +void ProfiledBinary::disassemble(const ELFObjectFileBase *Obj) { + // Set up disassembler and related components. + setUpDisassembler(Obj); + + // Create a mapping from virtual address to symbol name. The symbols in text + // sections are the candidates to dissassemble. + std::map AllSymbols; + StringRef FileName = Obj->getFileName(); + for (const SymbolRef &Symbol : Obj->symbols()) { + const uint64_t Addr = unwrapOrError(Symbol.getAddress(), FileName); + const StringRef Name = unwrapOrError(Symbol.getName(), FileName); + section_iterator SecI = unwrapOrError(Symbol.getSection(), FileName); + if (SecI != Obj->section_end()) + AllSymbols[*SecI].push_back(SymbolInfoTy(Addr, Name, ELF::STT_NOTYPE)); + } + + // Sort all the symbols. Use a stable sort to stabilize the output. + for (std::pair &SecSyms : AllSymbols) + stable_sort(SecSyms.second); + + if (ShowDisassembly) + outs() << "\nDisassembly of " << FileName << ":\n"; + + // Dissassemble a text section. + for (section_iterator SI = Obj->section_begin(), SE = Obj->section_end(); + SI != SE; ++SI) { + const SectionRef &Section = *SI; + if (!Section.isText()) + continue; + + uint64_t ImageLoadAddr = PreferredBaseAddress; + uint64_t SectionOffset = Section.getAddress() - ImageLoadAddr; + uint64_t SectSize = Section.getSize(); + if (!SectSize) + continue; + + // Register the text section. + TextSections.insert({SectionOffset, SectSize}); + + if (ShowDisassembly) { + StringRef SectionName = unwrapOrError(Section.getName(), FileName); + outs() << "\nDisassembly of section " << SectionName; + outs() << " [" << format("0x%" PRIx64, SectionOffset) << ", " + << format("0x%" PRIx64, SectionOffset + SectSize) << "]:\n\n"; + } + + // Get the section data. + ArrayRef Bytes = + arrayRefFromStringRef(unwrapOrError(Section.getContents(), FileName)); + + // Get the list of all the symbols in this section. + SectionSymbolsTy &Symbols = AllSymbols[Section]; + + // Disassemble symbol by symbol. + for (std::size_t SI = 0, SE = Symbols.size(); SI != SE; ++SI) { + if (!dissassembleSymbol(SI, Bytes, Symbols, Section)) + exitWithError("disassembling error", FileName); + } + } +} +} // end namespace sampleprof +} // end namespace llvm diff --git a/llvm/tools/llvm-profgen/llvm-profgen.cpp b/llvm/tools/llvm-profgen/llvm-profgen.cpp --- a/llvm/tools/llvm-profgen/llvm-profgen.cpp +++ b/llvm/tools/llvm-profgen/llvm-profgen.cpp @@ -15,6 +15,7 @@ #include "ProfiledBinary.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" +#include "llvm/Support/TargetSelect.h" static cl::list PerfTraceFilenames( "perfscript", cl::value_desc("perfscript"), cl::OneOrMore, @@ -23,7 +24,7 @@ "`script` command(the raw perf.data should be profiled with -b)")); static cl::list - BinaryFilenames("binary", cl::value_desc("binary"), cl::ZeroOrMore, + BinaryFilenames("binary", cl::value_desc("binary"), cl::OneOrMore, llvm::cl::MiscFlags::CommaSeparated, cl::desc("Path of profiled binary files")); @@ -39,6 +40,11 @@ cl::ParseCommandLineOptions(argc, argv, "llvm SPGO profile generator\n"); + // Initialize targets and assembly printers/parsers. + InitializeAllTargetInfos(); + InitializeAllTargetMCs(); + InitializeAllDisassemblers(); + // Load binaries and parse perf events and samples PerfReader Reader(BinaryFilenames); Reader.parsePerfTraces(PerfTraceFilenames);