Home | History | Annotate | Line # | Download | only in XRay
      1 //===- InstrumentationMap.cpp - XRay Instrumentation Map ------------------===//
      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 // Implementation of the InstrumentationMap type for XRay sleds.
     10 //
     11 //===----------------------------------------------------------------------===//
     12 
     13 #include "llvm/XRay/InstrumentationMap.h"
     14 #include "llvm/ADT/DenseMap.h"
     15 #include "llvm/ADT/None.h"
     16 #include "llvm/ADT/STLExtras.h"
     17 #include "llvm/ADT/StringRef.h"
     18 #include "llvm/ADT/Triple.h"
     19 #include "llvm/ADT/Twine.h"
     20 #include "llvm/Object/Binary.h"
     21 #include "llvm/Object/ELFObjectFile.h"
     22 #include "llvm/Object/ObjectFile.h"
     23 #include "llvm/Object/RelocationResolver.h"
     24 #include "llvm/Support/DataExtractor.h"
     25 #include "llvm/Support/Error.h"
     26 #include "llvm/Support/FileSystem.h"
     27 #include "llvm/Support/YAMLTraits.h"
     28 #include <algorithm>
     29 #include <cstddef>
     30 #include <cstdint>
     31 #include <system_error>
     32 #include <vector>
     33 
     34 using namespace llvm;
     35 using namespace xray;
     36 
     37 Optional<int32_t> InstrumentationMap::getFunctionId(uint64_t Addr) const {
     38   auto I = FunctionIds.find(Addr);
     39   if (I != FunctionIds.end())
     40     return I->second;
     41   return None;
     42 }
     43 
     44 Optional<uint64_t> InstrumentationMap::getFunctionAddr(int32_t FuncId) const {
     45   auto I = FunctionAddresses.find(FuncId);
     46   if (I != FunctionAddresses.end())
     47     return I->second;
     48   return None;
     49 }
     50 
     51 using RelocMap = DenseMap<uint64_t, uint64_t>;
     52 
     53 static Error
     54 loadObj(StringRef Filename, object::OwningBinary<object::ObjectFile> &ObjFile,
     55         InstrumentationMap::SledContainer &Sleds,
     56         InstrumentationMap::FunctionAddressMap &FunctionAddresses,
     57         InstrumentationMap::FunctionAddressReverseMap &FunctionIds) {
     58   InstrumentationMap Map;
     59 
     60   // Find the section named "xray_instr_map".
     61   if ((!ObjFile.getBinary()->isELF() && !ObjFile.getBinary()->isMachO()) ||
     62       !(ObjFile.getBinary()->getArch() == Triple::x86_64 ||
     63         ObjFile.getBinary()->getArch() == Triple::ppc64le ||
     64         ObjFile.getBinary()->getArch() == Triple::arm ||
     65         ObjFile.getBinary()->getArch() == Triple::aarch64))
     66     return make_error<StringError>(
     67         "File format not supported (only does ELF and Mach-O little endian "
     68         "64-bit).",
     69         std::make_error_code(std::errc::not_supported));
     70 
     71   StringRef Contents = "";
     72   const auto &Sections = ObjFile.getBinary()->sections();
     73   uint64_t Address = 0;
     74   auto I = llvm::find_if(Sections, [&](object::SectionRef Section) {
     75     Expected<StringRef> NameOrErr = Section.getName();
     76     if (NameOrErr) {
     77       Address = Section.getAddress();
     78       return *NameOrErr == "xray_instr_map";
     79     }
     80     consumeError(NameOrErr.takeError());
     81     return false;
     82   });
     83 
     84   if (I == Sections.end())
     85     return make_error<StringError>(
     86         "Failed to find XRay instrumentation map.",
     87         std::make_error_code(std::errc::executable_format_error));
     88 
     89   if (Expected<StringRef> E = I->getContents())
     90     Contents = *E;
     91   else
     92     return E.takeError();
     93 
     94   RelocMap Relocs;
     95   if (ObjFile.getBinary()->isELF()) {
     96     uint32_t RelativeRelocation = [](object::ObjectFile *ObjFile) {
     97       if (const auto *ELFObj = dyn_cast<object::ELF32LEObjectFile>(ObjFile))
     98         return ELFObj->getELFFile().getRelativeRelocationType();
     99       else if (const auto *ELFObj =
    100                    dyn_cast<object::ELF32BEObjectFile>(ObjFile))
    101         return ELFObj->getELFFile().getRelativeRelocationType();
    102       else if (const auto *ELFObj =
    103                    dyn_cast<object::ELF64LEObjectFile>(ObjFile))
    104         return ELFObj->getELFFile().getRelativeRelocationType();
    105       else if (const auto *ELFObj =
    106                    dyn_cast<object::ELF64BEObjectFile>(ObjFile))
    107         return ELFObj->getELFFile().getRelativeRelocationType();
    108       else
    109         return static_cast<uint32_t>(0);
    110     }(ObjFile.getBinary());
    111 
    112     object::SupportsRelocation Supports;
    113     object::RelocationResolver Resolver;
    114     std::tie(Supports, Resolver) =
    115         object::getRelocationResolver(*ObjFile.getBinary());
    116 
    117     for (const object::SectionRef &Section : Sections) {
    118       for (const object::RelocationRef &Reloc : Section.relocations()) {
    119         if (ObjFile.getBinary()->getArch() == Triple::arm) {
    120           if (Supports && Supports(Reloc.getType())) {
    121             Expected<uint64_t> ValueOrErr = Reloc.getSymbol()->getValue();
    122             if (!ValueOrErr)
    123               return ValueOrErr.takeError();
    124             Relocs.insert(
    125                 {Reloc.getOffset(),
    126                  object::resolveRelocation(Resolver, Reloc, *ValueOrErr, 0)});
    127           }
    128         } else if (Supports && Supports(Reloc.getType())) {
    129           auto AddendOrErr = object::ELFRelocationRef(Reloc).getAddend();
    130           auto A = AddendOrErr ? *AddendOrErr : 0;
    131           Expected<uint64_t> ValueOrErr = Reloc.getSymbol()->getValue();
    132           if (!ValueOrErr)
    133             // TODO: Test this error.
    134             return ValueOrErr.takeError();
    135           Relocs.insert(
    136               {Reloc.getOffset(),
    137                object::resolveRelocation(Resolver, Reloc, *ValueOrErr, A)});
    138         } else if (Reloc.getType() == RelativeRelocation) {
    139           if (auto AddendOrErr = object::ELFRelocationRef(Reloc).getAddend())
    140             Relocs.insert({Reloc.getOffset(), *AddendOrErr});
    141         }
    142       }
    143     }
    144   }
    145 
    146   // Copy the instrumentation map data into the Sleds data structure.
    147   auto C = Contents.bytes_begin();
    148   bool Is32Bit = ObjFile.getBinary()->makeTriple().isArch32Bit();
    149   size_t ELFSledEntrySize = Is32Bit ? 16 : 32;
    150 
    151   if ((C - Contents.bytes_end()) % ELFSledEntrySize != 0)
    152     return make_error<StringError>(
    153         Twine("Instrumentation map entries not evenly divisible by size of "
    154               "an XRay sled entry."),
    155         std::make_error_code(std::errc::executable_format_error));
    156 
    157   auto RelocateOrElse = [&](uint64_t Offset, uint64_t Address) {
    158     if (!Address) {
    159       uint64_t A = I->getAddress() + C - Contents.bytes_begin() + Offset;
    160       RelocMap::const_iterator R = Relocs.find(A);
    161       if (R != Relocs.end())
    162         return R->second;
    163     }
    164     return Address;
    165   };
    166 
    167   const int WordSize = Is32Bit ? 4 : 8;
    168   int32_t FuncId = 1;
    169   uint64_t CurFn = 0;
    170   for (; C != Contents.bytes_end(); C += ELFSledEntrySize) {
    171     DataExtractor Extractor(
    172         StringRef(reinterpret_cast<const char *>(C), ELFSledEntrySize), true,
    173         8);
    174     Sleds.push_back({});
    175     auto &Entry = Sleds.back();
    176     uint64_t OffsetPtr = 0;
    177     uint64_t AddrOff = OffsetPtr;
    178     if (Is32Bit)
    179       Entry.Address = RelocateOrElse(AddrOff, Extractor.getU32(&OffsetPtr));
    180     else
    181       Entry.Address = RelocateOrElse(AddrOff, Extractor.getU64(&OffsetPtr));
    182     uint64_t FuncOff = OffsetPtr;
    183     if (Is32Bit)
    184       Entry.Function = RelocateOrElse(FuncOff, Extractor.getU32(&OffsetPtr));
    185     else
    186       Entry.Function = RelocateOrElse(FuncOff, Extractor.getU64(&OffsetPtr));
    187     auto Kind = Extractor.getU8(&OffsetPtr);
    188     static constexpr SledEntry::FunctionKinds Kinds[] = {
    189         SledEntry::FunctionKinds::ENTRY, SledEntry::FunctionKinds::EXIT,
    190         SledEntry::FunctionKinds::TAIL,
    191         SledEntry::FunctionKinds::LOG_ARGS_ENTER,
    192         SledEntry::FunctionKinds::CUSTOM_EVENT};
    193     if (Kind >= sizeof(Kinds))
    194       return errorCodeToError(
    195           std::make_error_code(std::errc::executable_format_error));
    196     Entry.Kind = Kinds[Kind];
    197     Entry.AlwaysInstrument = Extractor.getU8(&OffsetPtr) != 0;
    198     Entry.Version = Extractor.getU8(&OffsetPtr);
    199     if (Entry.Version >= 2) {
    200       Entry.Address += C - Contents.bytes_begin() + Address;
    201       Entry.Function += C - Contents.bytes_begin() + WordSize + Address;
    202     }
    203 
    204     // We do replicate the function id generation scheme implemented in the
    205     // XRay runtime.
    206     // FIXME: Figure out how to keep this consistent with the XRay runtime.
    207     if (CurFn == 0) {
    208       CurFn = Entry.Function;
    209       FunctionAddresses[FuncId] = Entry.Function;
    210       FunctionIds[Entry.Function] = FuncId;
    211     }
    212     if (Entry.Function != CurFn) {
    213       ++FuncId;
    214       CurFn = Entry.Function;
    215       FunctionAddresses[FuncId] = Entry.Function;
    216       FunctionIds[Entry.Function] = FuncId;
    217     }
    218   }
    219   return Error::success();
    220 }
    221 
    222 static Error
    223 loadYAML(sys::fs::file_t Fd, size_t FileSize, StringRef Filename,
    224          InstrumentationMap::SledContainer &Sleds,
    225          InstrumentationMap::FunctionAddressMap &FunctionAddresses,
    226          InstrumentationMap::FunctionAddressReverseMap &FunctionIds) {
    227   std::error_code EC;
    228   sys::fs::mapped_file_region MappedFile(
    229       Fd, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0, EC);
    230   sys::fs::closeFile(Fd);
    231   if (EC)
    232     return make_error<StringError>(
    233         Twine("Failed memory-mapping file '") + Filename + "'.", EC);
    234 
    235   std::vector<YAMLXRaySledEntry> YAMLSleds;
    236   yaml::Input In(StringRef(MappedFile.data(), MappedFile.size()));
    237   In >> YAMLSleds;
    238   if (In.error())
    239     return make_error<StringError>(
    240         Twine("Failed loading YAML document from '") + Filename + "'.",
    241         In.error());
    242 
    243   Sleds.reserve(YAMLSleds.size());
    244   for (const auto &Y : YAMLSleds) {
    245     FunctionAddresses[Y.FuncId] = Y.Function;
    246     FunctionIds[Y.Function] = Y.FuncId;
    247     Sleds.push_back(SledEntry{Y.Address, Y.Function, Y.Kind, Y.AlwaysInstrument,
    248                               Y.Version});
    249   }
    250   return Error::success();
    251 }
    252 
    253 // FIXME: Create error types that encapsulate a bit more information than what
    254 // StringError instances contain.
    255 Expected<InstrumentationMap>
    256 llvm::xray::loadInstrumentationMap(StringRef Filename) {
    257   // At this point we assume the file is an object file -- and if that doesn't
    258   // work, we treat it as YAML.
    259   // FIXME: Extend to support non-ELF and non-x86_64 binaries.
    260 
    261   InstrumentationMap Map;
    262   auto ObjectFileOrError = object::ObjectFile::createObjectFile(Filename);
    263   if (!ObjectFileOrError) {
    264     auto E = ObjectFileOrError.takeError();
    265     // We try to load it as YAML if the ELF load didn't work.
    266     Expected<sys::fs::file_t> FdOrErr =
    267         sys::fs::openNativeFileForRead(Filename);
    268     if (!FdOrErr) {
    269       // Report the ELF load error if YAML failed.
    270       consumeError(FdOrErr.takeError());
    271       return std::move(E);
    272     }
    273 
    274     uint64_t FileSize;
    275     if (sys::fs::file_size(Filename, FileSize))
    276       return std::move(E);
    277 
    278     // If the file is empty, we return the original error.
    279     if (FileSize == 0)
    280       return std::move(E);
    281 
    282     // From this point on the errors will be only for the YAML parts, so we
    283     // consume the errors at this point.
    284     consumeError(std::move(E));
    285     if (auto E = loadYAML(*FdOrErr, FileSize, Filename, Map.Sleds,
    286                           Map.FunctionAddresses, Map.FunctionIds))
    287       return std::move(E);
    288   } else if (auto E = loadObj(Filename, *ObjectFileOrError, Map.Sleds,
    289                               Map.FunctionAddresses, Map.FunctionIds)) {
    290     return std::move(E);
    291   }
    292   return Map;
    293 }
    294