Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf373edc authored by Christopher Ferris's avatar Christopher Ferris
Browse files

Add caching of build id in MapInfo object.

Change the GetBuildID function to return a std::string.
Added benchmark to check how long it takes to get the build id from
a file versus an elf object.
Added a way to get an elf without passing in a valid process_memory and
added tests for this.

Test: New unit tests.
Change-Id: I3029019767e0181c758d611fe635bc1bf72d6e8e
parent b94c2e52
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -191,6 +191,7 @@ cc_test {
        "tests/LocalUnwinderTest.cpp",
        "tests/LogFake.cpp",
        "tests/MapInfoCreateMemoryTest.cpp",
        "tests/MapInfoGetBuildIDTest.cpp",
        "tests/MapInfoGetElfTest.cpp",
        "tests/MapInfoGetLoadBiasTest.cpp",
        "tests/MapInfoTest.cpp",
@@ -343,6 +344,7 @@ cc_benchmark {
    ],

    shared_libs: [
        "libbase",
        "libunwindstack",
    ],
}
+23 −2
Original line number Diff line number Diff line
@@ -140,8 +140,11 @@ bool Elf::GetGlobalVariable(const std::string& name, uint64_t* memory_address) {
  return true;
}

bool Elf::GetBuildID(std::string* build_id) {
  return valid_ && interface_->GetBuildID(build_id);
std::string Elf::GetBuildID() {
  if (!valid_) {
    return "";
  }
  return interface_->GetBuildID();
}

void Elf::GetLastError(ErrorData* data) {
@@ -384,4 +387,22 @@ bool Elf::CacheGet(MapInfo* info) {
  return false;
}

std::string Elf::GetBuildID(Memory* memory) {
  if (!IsValidElf(memory)) {
    return "";
  }

  uint8_t class_type;
  if (!memory->Read(EI_CLASS, &class_type, 1)) {
    return "";
  }

  if (class_type == ELFCLASS32) {
    return ElfInterface::ReadBuildIDFromMemory<Elf32_Ehdr, Elf32_Shdr, Elf32_Nhdr>(memory);
  } else if (class_type == ELFCLASS64) {
    return ElfInterface::ReadBuildIDFromMemory<Elf64_Ehdr, Elf64_Shdr, Elf64_Nhdr>(memory);
  }
  return "";
}

}  // namespace unwindstack
+118 −14
Original line number Diff line number Diff line
@@ -238,31 +238,31 @@ void ElfInterface::ReadProgramHeaders(const EhdrType& ehdr, uint64_t* load_bias)
}

template <typename NhdrType>
bool ElfInterface::ReadBuildID(std::string* build_id) {
std::string ElfInterface::ReadBuildID() {
  // Ensure there is no overflow in any of the calulations below.
  uint64_t tmp;
  if (__builtin_add_overflow(gnu_build_id_offset_, gnu_build_id_size_, &tmp)) {
    return false;
    return "";
  }

  uint64_t offset = 0;
  while (offset < gnu_build_id_size_) {
    if (gnu_build_id_size_ - offset < sizeof(NhdrType)) {
      return false;
      return "";
    }
    NhdrType hdr;
    if (!memory_->ReadFully(gnu_build_id_offset_ + offset, &hdr, sizeof(hdr))) {
      return false;
      return "";
    }
    offset += sizeof(hdr);

    if (gnu_build_id_size_ - offset < hdr.n_namesz) {
      return false;
      return "";
    }
    if (hdr.n_namesz > 0) {
      std::string name(hdr.n_namesz, '\0');
      if (!memory_->ReadFully(gnu_build_id_offset_ + offset, &(name[0]), hdr.n_namesz)) {
        return false;
        return "";
      }

      // Trim trailing \0 as GNU is stored as a C string in the ELF file.
@@ -273,18 +273,20 @@ bool ElfInterface::ReadBuildID(std::string* build_id) {
      offset += (hdr.n_namesz + 3) & ~3;

      if (name == "GNU" && hdr.n_type == NT_GNU_BUILD_ID) {
        if (gnu_build_id_size_ - offset < hdr.n_descsz) {
          return false;
        if (gnu_build_id_size_ - offset < hdr.n_descsz || hdr.n_descsz == 0) {
          return "";
        }
        std::string build_id(hdr.n_descsz - 1, '\0');
        if (memory_->ReadFully(gnu_build_id_offset_ + offset, &build_id[0], hdr.n_descsz)) {
          return build_id;
        }
        build_id->resize(hdr.n_descsz);
        return memory_->ReadFully(gnu_build_id_offset_ + offset, &(*build_id)[0],
                                  hdr.n_descsz);
        return "";
      }
    }
    // Align hdr.n_descsz to next power multiple of 4. See man 5 elf.
    offset += (hdr.n_descsz + 3) & ~3;
  }
  return false;
  return "";
}

template <typename EhdrType, typename ShdrType>
@@ -536,6 +538,103 @@ void ElfInterface::GetMaxSizeWithTemplate(Memory* memory, uint64_t* size) {
  *size = ehdr.e_shoff + ehdr.e_shentsize * ehdr.e_shnum;
}

template <typename EhdrType, typename ShdrType>
bool GetBuildIDInfo(Memory* memory, uint64_t* build_id_offset, uint64_t* build_id_size) {
  EhdrType ehdr;
  if (!memory->ReadFully(0, &ehdr, sizeof(ehdr))) {
    return false;
  }

  uint64_t offset = ehdr.e_shoff;
  uint64_t sec_offset;
  uint64_t sec_size;
  ShdrType shdr;
  if (ehdr.e_shstrndx >= ehdr.e_shnum) {
    return false;
  }

  uint64_t sh_offset = offset + ehdr.e_shstrndx * ehdr.e_shentsize;
  if (!memory->ReadFully(sh_offset, &shdr, sizeof(shdr))) {
    return false;
  }
  sec_offset = shdr.sh_offset;
  sec_size = shdr.sh_size;

  // Skip the first header, it's always going to be NULL.
  offset += ehdr.e_shentsize;
  for (size_t i = 1; i < ehdr.e_shnum; i++, offset += ehdr.e_shentsize) {
    if (!memory->ReadFully(offset, &shdr, sizeof(shdr))) {
      return false;
    }
    std::string name;
    if (shdr.sh_type == SHT_NOTE && shdr.sh_name < sec_size &&
        memory->ReadString(sec_offset + shdr.sh_name, &name) && name == ".note.gnu.build-id") {
      *build_id_offset = shdr.sh_offset;
      *build_id_size = shdr.sh_size;
      return true;
    }
  }

  return false;
}

template <typename EhdrType, typename ShdrType, typename NhdrType>
std::string ElfInterface::ReadBuildIDFromMemory(Memory* memory) {
  uint64_t note_offset;
  uint64_t note_size;
  if (!GetBuildIDInfo<EhdrType, ShdrType>(memory, &note_offset, &note_size)) {
    return "";
  }

  // Ensure there is no overflow in any of the calculations below.
  uint64_t tmp;
  if (__builtin_add_overflow(note_offset, note_size, &tmp)) {
    return "";
  }

  uint64_t offset = 0;
  while (offset < note_size) {
    if (note_size - offset < sizeof(NhdrType)) {
      return "";
    }
    NhdrType hdr;
    if (!memory->ReadFully(note_offset + offset, &hdr, sizeof(hdr))) {
      return "";
    }
    offset += sizeof(hdr);

    if (note_size - offset < hdr.n_namesz) {
      return "";
    }
    if (hdr.n_namesz > 0) {
      std::string name(hdr.n_namesz, '\0');
      if (!memory->ReadFully(note_offset + offset, &(name[0]), hdr.n_namesz)) {
        return "";
      }

      // Trim trailing \0 as GNU is stored as a C string in the ELF file.
      if (name.back() == '\0') name.resize(name.size() - 1);

      // Align hdr.n_namesz to next power multiple of 4. See man 5 elf.
      offset += (hdr.n_namesz + 3) & ~3;

      if (name == "GNU" && hdr.n_type == NT_GNU_BUILD_ID) {
        if (note_size - offset < hdr.n_descsz || hdr.n_descsz == 0) {
          return "";
        }
        std::string build_id(hdr.n_descsz - 1, '\0');
        if (memory->ReadFully(note_offset + offset, &build_id[0], hdr.n_descsz)) {
          return build_id;
        }
        return "";
      }
    }
    // Align hdr.n_descsz to next power multiple of 4. See man 5 elf.
    offset += (hdr.n_descsz + 3) & ~3;
  }
  return "";
}

// Instantiate all of the needed template functions.
template void ElfInterface::InitHeadersWithTemplate<uint32_t>(uint64_t);
template void ElfInterface::InitHeadersWithTemplate<uint64_t>(uint64_t);
@@ -551,8 +650,8 @@ template void ElfInterface::ReadProgramHeaders<Elf64_Ehdr, Elf64_Phdr>(const Elf
template void ElfInterface::ReadSectionHeaders<Elf32_Ehdr, Elf32_Shdr>(const Elf32_Ehdr&);
template void ElfInterface::ReadSectionHeaders<Elf64_Ehdr, Elf64_Shdr>(const Elf64_Ehdr&);

template bool ElfInterface::ReadBuildID<Elf32_Nhdr>(std::string*);
template bool ElfInterface::ReadBuildID<Elf64_Nhdr>(std::string*);
template std::string ElfInterface::ReadBuildID<Elf32_Nhdr>();
template std::string ElfInterface::ReadBuildID<Elf64_Nhdr>();

template bool ElfInterface::GetSonameWithTemplate<Elf32_Dyn>(std::string*);
template bool ElfInterface::GetSonameWithTemplate<Elf64_Dyn>(std::string*);
@@ -571,4 +670,9 @@ template void ElfInterface::GetMaxSizeWithTemplate<Elf64_Ehdr>(Memory*, uint64_t
template uint64_t ElfInterface::GetLoadBias<Elf32_Ehdr, Elf32_Phdr>(Memory*);
template uint64_t ElfInterface::GetLoadBias<Elf64_Ehdr, Elf64_Phdr>(Memory*);

template std::string ElfInterface::ReadBuildIDFromMemory<Elf32_Ehdr, Elf32_Shdr, Elf32_Nhdr>(
    Memory*);
template std::string ElfInterface::ReadBuildIDFromMemory<Elf64_Ehdr, Elf64_Shdr, Elf64_Nhdr>(
    Memory*);

}  // namespace unwindstack
+48 −0
Original line number Diff line number Diff line
@@ -146,6 +146,10 @@ Memory* MapInfo::CreateMemory(const std::shared_ptr<Memory>& process_memory) {
    }
  }

  if (process_memory == nullptr) {
    return nullptr;
  }

  // Need to verify that this elf is valid. It's possible that
  // only part of the elf file to be mapped into memory is in the executable
  // map. In this case, there will be another read-only map that includes the
@@ -263,4 +267,48 @@ uint64_t MapInfo::GetLoadBias(const std::shared_ptr<Memory>& process_memory) {
  return cur_load_bias;
}

MapInfo::~MapInfo() {
  uintptr_t id = build_id.load();
  if (id != 0) {
    delete reinterpret_cast<std::string*>(id);
  }
}

std::string MapInfo::GetBuildID() {
  uintptr_t id = build_id.load();
  if (build_id != 0) {
    return *reinterpret_cast<std::string*>(id);
  }

  // No need to lock, at worst if multiple threads do this at the same
  // time it should be detected and only one thread should win and
  // save the data.
  std::unique_ptr<std::string> cur_build_id(new std::string);

  // Now need to see if the elf object exists.
  // Make sure no other thread is trying to add the elf to this map.
  mutex_.lock();
  Elf* elf_obj = elf.get();
  mutex_.unlock();
  if (elf_obj != nullptr) {
    *cur_build_id = elf_obj->GetBuildID();
  } else {
    // This will only work if we can get the file associated with this memory.
    // If this is only available in memory, then the section name information
    // is not present and we will not be able to find the build id info.
    std::unique_ptr<Memory> memory(GetFileMemory());
    if (memory != nullptr) {
      *cur_build_id = Elf::GetBuildID(memory.get());
    }
  }

  id = reinterpret_cast<uintptr_t>(cur_build_id.get());
  uintptr_t expected_id = 0;
  if (build_id.compare_exchange_weak(expected_id, id)) {
    // Value saved, so make sure the memory is not freed.
    cur_build_id.release();
  }
  return *reinterpret_cast<std::string*>(id);
}

}  // namespace unwindstack
+62 −0
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@

#include <benchmark/benchmark.h>

#include <android-base/strings.h>

#include <unwindstack/Elf.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Memory.h>
#include <unwindstack/Regs.h>
@@ -80,4 +83,63 @@ static void BM_cached_unwind(benchmark::State& state) {
}
BENCHMARK(BM_cached_unwind);

static void Initialize(benchmark::State& state, unwindstack::Maps& maps,
                       unwindstack::MapInfo** build_id_map_info) {
  if (!maps.Parse()) {
    state.SkipWithError("Failed to parse local maps.");
    return;
  }

  // Find the libc.so share library and use that for benchmark purposes.
  *build_id_map_info = nullptr;
  for (unwindstack::MapInfo* map_info : maps) {
    if (map_info->offset == 0 && map_info->GetBuildID() != "") {
      *build_id_map_info = map_info;
      break;
    }
  }

  if (*build_id_map_info == nullptr) {
    state.SkipWithError("Failed to find a map with a BuildID.");
  }
}

static void BM_get_build_id_from_elf(benchmark::State& state) {
  unwindstack::LocalMaps maps;
  unwindstack::MapInfo* build_id_map_info;
  Initialize(state, maps, &build_id_map_info);

  unwindstack::Elf* elf = build_id_map_info->GetElf(std::shared_ptr<unwindstack::Memory>(),
                                                    unwindstack::Regs::CurrentArch());
  if (!elf->valid()) {
    state.SkipWithError("Cannot get valid elf from map.");
  }

  for (auto _ : state) {
    uintptr_t id = build_id_map_info->build_id;
    if (id != 0) {
      delete reinterpret_cast<std::string*>(id);
      build_id_map_info->build_id = 0;
    }
    benchmark::DoNotOptimize(build_id_map_info->GetBuildID());
  }
}
BENCHMARK(BM_get_build_id_from_elf);

static void BM_get_build_id_from_file(benchmark::State& state) {
  unwindstack::LocalMaps maps;
  unwindstack::MapInfo* build_id_map_info;
  Initialize(state, maps, &build_id_map_info);

  for (auto _ : state) {
    uintptr_t id = build_id_map_info->build_id;
    if (id != 0) {
      delete reinterpret_cast<std::string*>(id);
      build_id_map_info->build_id = 0;
    }
    benchmark::DoNotOptimize(build_id_map_info->GetBuildID());
  }
}
BENCHMARK(BM_get_build_id_from_file);

BENCHMARK_MAIN();
Loading