| //===- xray-extract.cc - XRay Instrumentation Map Extraction --------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Implementation of the xray-extract.h interface. |
| // |
| // FIXME: Support other XRay-instrumented binary formats other than ELF. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <type_traits> |
| #include <utility> |
| |
| #include "xray-extract.h" |
| |
| #include "xray-registry.h" |
| #include "xray-sleds.h" |
| #include "llvm/Object/ELF.h" |
| #include "llvm/Object/ObjectFile.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/DataExtractor.h" |
| #include "llvm/Support/ELF.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/YAMLTraits.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace llvm; |
| using namespace llvm::xray; |
| using namespace llvm::yaml; |
| |
| // llvm-xray extract |
| // ---------------------------------------------------------------------------- |
| static cl::SubCommand Extract("extract", "Extract instrumentation maps"); |
| static cl::opt<std::string> ExtractInput(cl::Positional, |
| cl::desc("<input file>"), cl::Required, |
| cl::sub(Extract)); |
| static cl::opt<std::string> |
| ExtractOutput("output", cl::value_desc("output file"), cl::init("-"), |
| cl::desc("output file; use '-' for stdout"), |
| cl::sub(Extract)); |
| static cl::alias ExtractOutput2("o", cl::aliasopt(ExtractOutput), |
| cl::desc("Alias for -output"), |
| cl::sub(Extract)); |
| |
| struct YAMLXRaySledEntry { |
| int32_t FuncId; |
| Hex64 Address; |
| Hex64 Function; |
| SledEntry::FunctionKinds Kind; |
| bool AlwaysInstrument; |
| }; |
| |
| namespace llvm { |
| namespace yaml { |
| |
| template <> struct ScalarEnumerationTraits<SledEntry::FunctionKinds> { |
| static void enumeration(IO &IO, SledEntry::FunctionKinds &Kind) { |
| IO.enumCase(Kind, "function-enter", SledEntry::FunctionKinds::ENTRY); |
| IO.enumCase(Kind, "function-exit", SledEntry::FunctionKinds::EXIT); |
| IO.enumCase(Kind, "tail-exit", SledEntry::FunctionKinds::TAIL); |
| } |
| }; |
| |
| template <> struct MappingTraits<YAMLXRaySledEntry> { |
| static void mapping(IO &IO, YAMLXRaySledEntry &Entry) { |
| IO.mapRequired("id", Entry.FuncId); |
| IO.mapRequired("address", Entry.Address); |
| IO.mapRequired("function", Entry.Function); |
| IO.mapRequired("kind", Entry.Kind); |
| IO.mapRequired("always-instrument", Entry.AlwaysInstrument); |
| } |
| |
| static constexpr bool flow = true; |
| }; |
| } |
| } |
| |
| LLVM_YAML_IS_SEQUENCE_VECTOR(YAMLXRaySledEntry) |
| |
| namespace { |
| |
| llvm::Error LoadBinaryInstrELF( |
| StringRef Filename, std::deque<SledEntry> &OutputSleds, |
| InstrumentationMapExtractor::FunctionAddressMap &InstrMap, |
| InstrumentationMapExtractor::FunctionAddressReverseMap &FunctionIds) { |
| auto ObjectFile = object::ObjectFile::createObjectFile(Filename); |
| |
| if (!ObjectFile) |
| return ObjectFile.takeError(); |
| |
| // FIXME: Maybe support other ELF formats. For now, 64-bit Little Endian only. |
| if (!ObjectFile->getBinary()->isELF()) |
| return make_error<StringError>( |
| "File format not supported (only does ELF).", |
| std::make_error_code(std::errc::not_supported)); |
| if (ObjectFile->getBinary()->getArch() != Triple::x86_64) |
| return make_error<StringError>( |
| "File format not supported (only does ELF little endian 64-bit).", |
| std::make_error_code(std::errc::not_supported)); |
| |
| // Find the section named "xray_instr_map". |
| StringRef Contents = ""; |
| const auto &Sections = ObjectFile->getBinary()->sections(); |
| auto I = find_if(Sections, [&](object::SectionRef Section) { |
| StringRef Name = ""; |
| if (Section.getName(Name)) |
| return false; |
| return Name == "xray_instr_map"; |
| }); |
| if (I == Sections.end()) |
| return make_error<StringError>( |
| "Failed to find XRay instrumentation map.", |
| std::make_error_code(std::errc::not_supported)); |
| if (I->getContents(Contents)) |
| return make_error<StringError>( |
| "Failed to get contents of 'xray_instr_map' section.", |
| std::make_error_code(std::errc::executable_format_error)); |
| |
| // Copy the instrumentation map data into the Sleds data structure. |
| auto C = Contents.bytes_begin(); |
| static constexpr size_t ELF64SledEntrySize = 32; |
| |
| if ((C - Contents.bytes_end()) % ELF64SledEntrySize != 0) |
| return make_error<StringError>( |
| "Instrumentation map entries not evenly divisible by size of an XRay " |
| "sled entry in ELF64.", |
| std::make_error_code(std::errc::executable_format_error)); |
| |
| int32_t FuncId = 1; |
| uint64_t CurFn = 0; |
| std::deque<SledEntry> Sleds; |
| for (; C != Contents.bytes_end(); C += ELF64SledEntrySize) { |
| DataExtractor Extractor( |
| StringRef(reinterpret_cast<const char *>(C), ELF64SledEntrySize), true, |
| 8); |
| Sleds.push_back({}); |
| auto &Entry = Sleds.back(); |
| uint32_t OffsetPtr = 0; |
| Entry.Address = Extractor.getU64(&OffsetPtr); |
| Entry.Function = Extractor.getU64(&OffsetPtr); |
| auto Kind = Extractor.getU8(&OffsetPtr); |
| switch (Kind) { |
| case 0: // ENTRY |
| Entry.Kind = SledEntry::FunctionKinds::ENTRY; |
| break; |
| case 1: // EXIT |
| Entry.Kind = SledEntry::FunctionKinds::EXIT; |
| break; |
| case 2: // TAIL |
| Entry.Kind = SledEntry::FunctionKinds::TAIL; |
| break; |
| default: |
| return make_error<StringError>( |
| Twine("Encountered unknown sled type ") + "'" + Twine(int32_t{Kind}) + |
| "'.", |
| std::make_error_code(std::errc::executable_format_error)); |
| } |
| Entry.AlwaysInstrument = Extractor.getU8(&OffsetPtr) != 0; |
| |
| // We replicate the function id generation scheme implemented in the runtime |
| // here. Ideally we should be able to break it out, or output this map from |
| // the runtime, but that's a design point we can discuss later on. For now, |
| // we replicate the logic and move on. |
| if (CurFn == 0) { |
| CurFn = Entry.Function; |
| InstrMap[FuncId] = Entry.Function; |
| FunctionIds[Entry.Function] = FuncId; |
| } |
| if (Entry.Function != CurFn) { |
| ++FuncId; |
| CurFn = Entry.Function; |
| InstrMap[FuncId] = Entry.Function; |
| FunctionIds[Entry.Function] = FuncId; |
| } |
| } |
| OutputSleds = std::move(Sleds); |
| return llvm::Error::success(); |
| } |
| |
| Error LoadYAMLInstrMap( |
| StringRef Filename, std::deque<SledEntry> &Sleds, |
| InstrumentationMapExtractor::FunctionAddressMap &InstrMap, |
| InstrumentationMapExtractor::FunctionAddressReverseMap &FunctionIds) { |
| int Fd; |
| if (auto EC = sys::fs::openFileForRead(Filename, Fd)) |
| return make_error<StringError>( |
| Twine("Failed opening file '") + Filename + "' for reading.", EC); |
| |
| uint64_t FileSize; |
| if (auto EC = sys::fs::file_size(Filename, FileSize)) |
| return make_error<StringError>( |
| Twine("Failed getting size of file '") + Filename + "'.", EC); |
| |
| std::error_code EC; |
| sys::fs::mapped_file_region MappedFile( |
| Fd, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0, EC); |
| if (EC) |
| return make_error<StringError>( |
| Twine("Failed memory-mapping file '") + Filename + "'.", EC); |
| |
| std::vector<YAMLXRaySledEntry> YAMLSleds; |
| Input In(StringRef(MappedFile.data(), MappedFile.size())); |
| In >> YAMLSleds; |
| if (In.error()) |
| return make_error<StringError>( |
| Twine("Failed loading YAML document from '") + Filename + "'.", |
| In.error()); |
| |
| for (const auto &Y : YAMLSleds) { |
| InstrMap[Y.FuncId] = Y.Function; |
| FunctionIds[Y.Function] = Y.FuncId; |
| Sleds.push_back( |
| SledEntry{Y.Address, Y.Function, Y.Kind, Y.AlwaysInstrument}); |
| } |
| return Error::success(); |
| } |
| |
| } // namespace |
| |
| InstrumentationMapExtractor::InstrumentationMapExtractor(std::string Filename, |
| InputFormats Format, |
| Error &EC) { |
| ErrorAsOutParameter ErrAsOutputParam(&EC); |
| if (Filename.empty()) { |
| EC = Error::success(); |
| return; |
| } |
| switch (Format) { |
| case InputFormats::ELF: { |
| EC = handleErrors( |
| LoadBinaryInstrELF(Filename, Sleds, FunctionAddresses, FunctionIds), |
| [&](std::unique_ptr<ErrorInfoBase> E) { |
| return joinErrors( |
| make_error<StringError>( |
| Twine("Cannot extract instrumentation map from '") + |
| Filename + "'.", |
| std::make_error_code(std::errc::executable_format_error)), |
| std::move(E)); |
| }); |
| break; |
| } |
| case InputFormats::YAML: { |
| EC = handleErrors( |
| LoadYAMLInstrMap(Filename, Sleds, FunctionAddresses, FunctionIds), |
| [&](std::unique_ptr<ErrorInfoBase> E) { |
| return joinErrors( |
| make_error<StringError>( |
| Twine("Cannot load YAML instrumentation map from '") + |
| Filename + "'.", |
| std::make_error_code(std::errc::executable_format_error)), |
| std::move(E)); |
| }); |
| break; |
| } |
| } |
| } |
| |
| void InstrumentationMapExtractor::exportAsYAML(raw_ostream &OS) { |
| // First we translate the sleds into the YAMLXRaySledEntry objects in a deque. |
| std::vector<YAMLXRaySledEntry> YAMLSleds; |
| YAMLSleds.reserve(Sleds.size()); |
| for (const auto &Sled : Sleds) { |
| YAMLSleds.push_back({FunctionIds[Sled.Function], Sled.Address, |
| Sled.Function, Sled.Kind, Sled.AlwaysInstrument}); |
| } |
| Output Out(OS, nullptr, 0); |
| Out << YAMLSleds; |
| } |
| |
| static CommandRegistration Unused(&Extract, []() -> Error { |
| Error Err = Error::success(); |
| xray::InstrumentationMapExtractor Extractor( |
| ExtractInput, InstrumentationMapExtractor::InputFormats::ELF, Err); |
| if (Err) |
| return Err; |
| |
| std::error_code EC; |
| raw_fd_ostream OS(ExtractOutput, EC, sys::fs::OpenFlags::F_Text); |
| if (EC) |
| return make_error<StringError>( |
| Twine("Cannot open file '") + ExtractOutput + "' for writing.", EC); |
| Extractor.exportAsYAML(OS); |
| return Error::success(); |
| }); |