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 @@ -22,6 +22,10 @@ Path of input Linux perf script data files (should be profiled with -b). +.. option:: --binary= + + Path of the input profiled binary files. + .. option:: --output= Path of the output profile file. @@ -30,11 +34,15 @@ ------- :program:`llvm-profgen` supports the following options: -.. option:: --binary= +.. option:: --show-mmap-events - Path of the input profiled binary files. If no file path is specified, the - path of the actual profiled binaries will be used instead. + Print mmap events. -.. option:: --show-mmap +.. option:: --show-disassembly - Print mmap events. + 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,39 @@ +# 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, 0x33]: +# 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 + +.text +foo: + 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 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 | 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,10 +1,18 @@ +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 + 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/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-profgen/ProfiledBinary.h @@ -0,0 +1,78 @@ +//===-- ProfiledBinary.h - 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVM_PROGEN_PROFILEDBINARY_H +#define LLVM_TOOLS_LLVM_PROGEN_PROFILEDBINARY_H + +#include "llvm/ADT/StringRef.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. + uint64_t BaseAddress; + // The preferred base address that the executable sections are loaded at. + uint64_t PreferredBaseAddress; + // 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 RVA to name mapping. + std::unordered_map FuncStartAddrMap; + // An array of RVAs 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 RVAs. Used by virtual unwinding. + std::unordered_set CallAddrs; + // A set of return instruction RVAs. Used by virtual unwinding. + std::unordered_set RetAddrs; + + bool IsLoaded; + + void setPreferredBaseAddress(const ELFObjectFileBase *O); + + /// Dissassemble the text section and build various address maps. + void disassemble(const ELFObjectFileBase *O); + +public: + ProfiledBinary() {} + ProfiledBinary(StringRef Path) + : Path(Path), BaseAddress(0), IsLoaded(false) {} + + const StringRef getPath() const { return Path; } + const StringRef getName() const { return llvm::sys::path::filename(Path); } + uint64_t getBaseAddress() const { return BaseAddress; } + void setBaseAddress(uint64_t Address) { BaseAddress = Address; } + + /// 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(); +}; + +} // namespace sampleprof +} // end namespace llvm + +#endif 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,302 @@ +//===-- 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/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/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::Hidden, + 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; +} + +static bool InstIsCall(const MCInst &Inst, Triple TheTriple) { + if (TheTriple.isX86()) { + switch (Inst.getOpcode()) { + case X86::CALL16m: + case X86::CALL16r: + case X86::CALL32m: + case X86::CALL32r: + case X86::CALL64m: + case X86::CALL64r: + case X86::CALLpcrel32: + case X86::CALL64pcrel32: + return true; + default: + return false; + } + } else { + llvm_unreachable("not yet implemented"); + } +} + +static bool InstIsReturn(const MCInst &Inst, Triple TheTriple) { + if (TheTriple.isX86()) { + switch (Inst.getOpcode()) { + case X86::RETW: + case X86::RETL: + case X86::RETQ: + case X86::RETIL: + case X86::RETIQ: + case X86::RETIW: + return true; + default: + return false; + } + } else { + llvm_unreachable("not yet implemented"); + } +} + +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() { + if (IsLoaded) + return; + + // Attempt to open the binary. + OwningBinary OBinary = unwrapOrError(createBinary(Path), Path); + Binary &Binary = *OBinary.getBinary(); + if (auto Obj = dyn_cast(&Binary)) { + 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. + + IsLoaded = true; + return; + } + + exitWithError("not a valid Elf image", Path); +} + +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()); +} + +void ProfiledBinary::disassemble(const ELFObjectFileBase *Obj) { + const Target *TheTarget = getTarget(Obj); + std::string TripleName = TheTriple.getTriple(); + StringRef FileName = Obj->getFileName(); + + // Set up disassembler and related components. + std::unique_ptr MRI( + TheTarget->createMCRegInfo(TripleName)); + if (!MRI) + exitWithError("no register info for target " + TripleName, FileName); + + MCTargetOptions MCOptions; + std::unique_ptr AsmInfo( + TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); + if (!AsmInfo) + exitWithError("no assembly info for target " + TripleName, FileName); + + SubtargetFeatures Features = Obj->getFeatures(); + std::unique_ptr STI( + TheTarget->createMCSubtargetInfo(TripleName, "", Features.getString())); + if (!STI) + exitWithError("no subtarget info for target " + TripleName, FileName); + + std::unique_ptr MII(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); + + std::unique_ptr DisAsm( + TheTarget->createMCDisassembler(*STI, Ctx)); + if (!DisAsm) + exitWithError("no disassembler for target " + TripleName, FileName); + + std::unique_ptr MIA( + TheTarget->createMCInstrAnalysis(MII.get())); + + int AsmPrinterVariant = AsmInfo->getAssemblerDialect(); + std::unique_ptr IP(TheTarget->createMCInstPrinter( + Triple(TripleName), AsmPrinterVariant, *AsmInfo, *MII, *MRI)); + IP->setPrintBranchImmAsAddress(true); + + // Create a mapping from virtual address to symbol name. The symbols in text + // sections are the candidates to dissassemble. + std::map AllSymbols; + 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 SectionRVA = Section.getAddress() - ImageLoadAddr; + uint64_t SectSize = Section.getSize(); + if (!SectSize) + continue; + + // Register the text section. + TextSections.insert({SectionRVA, SectSize}); + + if (ShowDisassembly) { + StringRef SectionName = unwrapOrError(Section.getName(), FileName); + outs() << "\nDisassembly of section " << SectionName; + outs() << " [" << format("0x%" PRIx64, SectionRVA) << ", " + << format("0x%" PRIx64, SectionRVA + 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 (unsigned SI = 0, SE = Symbols.size(); SI != SE; ++SI) { + uint64_t StartRVA = Symbols[SI].Addr - ImageLoadAddr; + uint64_t EndRVA = (SI + 1 < SE) ? Symbols[SI + 1].Addr - ImageLoadAddr + : SectionRVA + SectSize; + if (StartRVA >= EndRVA) + continue; + + std::string SymbolName = Symbols[SI].Name.str(); + if (ShowDisassembly) + outs() << '<' << SymbolName << ">:\n"; + + uint64_t RVA = StartRVA; + while (RVA < EndRVA) { + MCInst Inst; + uint64_t Size; + // Disassemble an instruction. + bool Disassembled = + DisAsm->getInstruction(Inst, Size, Bytes.slice(RVA - SectionRVA), + RVA + ImageLoadAddr, nulls()); + if (Disassembled) { + if (ShowDisassembly) { + outs() << format("%8" PRIx64 ":", RVA); + IP->printInst(&Inst, RVA + Size, "", *STI.get(), outs()); + outs() << "\n"; + } + // Populate address maps. + CodeAddrs.push_back(RVA); + if (InstIsCall(Inst, TheTriple)) + CallAddrs.insert(RVA); + else if (InstIsReturn(Inst, TheTriple)) + RetAddrs.insert(RVA); + } else { + exitWithError("disassembling error", FileName); + } + + RVA += Size; + } + + if (ShowDisassembly) + outs() << "\n"; + + FuncStartAddrMap[StartRVA] = Symbols[SI].Name.str(); + } + } +} +} // 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 @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// #include "ErrorHandling.h" +#include "ProfiledBinary.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" +#include "llvm/Support/TargetSelect.h" #include #include #include @@ -29,7 +31,7 @@ cl::desc("Input Linux perf script output (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("Input profiled binary files")); @@ -37,36 +39,13 @@ cl::Required, cl::desc("Output profile file")); -static cl::opt ShowMmap("show-mmap", cl::Hidden, cl::init(false), - cl::ZeroOrMore, - cl::desc("Print binary load events.")); +static cl::opt ShowMmapEvents("show-mmap-events", cl::Hidden, + cl::init(false), cl::ZeroOrMore, + cl::desc("Print binary load events.")); namespace llvm { namespace sampleprof { -class ProfiledBinary { - std::string Path; - uint64_t BaseAddress; - bool IsLoaded; - -public: - ProfiledBinary() {} - ProfiledBinary(StringRef Path) - : Path(Path), BaseAddress(0), IsLoaded(false) {} - - const StringRef getPath() const { return Path; } - const StringRef getName() const { return llvm::sys::path::filename(Path); } - uint64_t getBaseAddress() const { return BaseAddress; } - void setBaseAddress(uint64_t Address) { BaseAddress = Address; } - - void load() { - if (IsLoaded) - return; - // TODO: - IsLoaded = true; - } -}; - using BinaryMap = StringMap; using BinaryAddressMap = std::map; @@ -177,7 +156,7 @@ Fields[4].getAsInteger(0, Event.Offset); Event.BinaryPath = Fields[5]; updateBinaryAddress(Event); - if (ShowMmap) { + if (ShowMmapEvents) { outs() << "Mmap: Binary " << Event.BinaryPath << " loaded at " << format("0x%" PRIx64 ":", Event.BaseAddress) << " \n"; } @@ -224,6 +203,11 @@ cl::ParseCommandLineOptions(argc, argv, "llvm SPGO profile generator\n"); + // Initialize targets and assembly printers/parsers. + InitializeAllTargetInfos(); + InitializeAllTargetMCs(); + InitializeAllDisassemblers(); + PerfReader Reader; Reader.run();