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

Commit 568dc01f authored by Christopher Ferris's avatar Christopher Ferris Committed by Gerrit Code Review
Browse files

Merge "Add Symbols parsing."

parents 7887dd85 e7ba4cc2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ cc_defaults {
        "Log.cpp",
        "Regs.cpp",
        "Memory.cpp",
        "Symbols.cpp",
    ],

    shared_libs: [
@@ -102,6 +103,7 @@ cc_defaults {
        "tests/MemoryRangeTest.cpp",
        "tests/MemoryRemoteTest.cpp",
        "tests/RegsTest.cpp",
        "tests/SymbolsTest.cpp",
    ],

    cflags: [
+110 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include <elf.h>
#include <stdint.h>

#include <string>

#include "Memory.h"
#include "Symbols.h"

Symbols::Symbols(uint64_t offset, uint64_t size, uint64_t entry_size, uint64_t str_offset,
                 uint64_t str_size)
    : cur_offset_(offset),
      offset_(offset),
      end_(offset + size),
      entry_size_(entry_size),
      str_offset_(str_offset),
      str_end_(str_offset_ + str_size) {}

const Symbols::Info* Symbols::GetInfoFromCache(uint64_t addr) {
  // Binary search the table.
  size_t first = 0;
  size_t last = symbols_.size();
  while (first < last) {
    size_t current = first + (last - first) / 2;
    const Info* info = &symbols_[current];
    if (addr < info->start_offset) {
      last = current;
    } else if (addr < info->end_offset) {
      return info;
    } else {
      first = current + 1;
    }
  }
  return nullptr;
}

template <typename SymType>
bool Symbols::GetName(uint64_t addr, uint64_t load_bias, Memory* elf_memory, std::string* name,
                      uint64_t* func_offset) {
  addr += load_bias;

  if (symbols_.size() != 0) {
    const Info* info = GetInfoFromCache(addr);
    if (info) {
      assert(addr >= info->start_offset && addr <= info->end_offset);
      *func_offset = addr - info->start_offset;
      return elf_memory->ReadString(info->str_offset, name, str_end_ - info->str_offset);
    }
  }

  bool symbol_added = false;
  bool return_value = false;
  while (cur_offset_ + entry_size_ <= end_) {
    SymType entry;
    if (!elf_memory->Read(cur_offset_, &entry, sizeof(entry))) {
      // Stop all processing, something looks like it is corrupted.
      cur_offset_ = UINT64_MAX;
      return false;
    }
    cur_offset_ += entry_size_;

    if (entry.st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry.st_info) == STT_FUNC) {
      // Treat st_value as virtual address.
      uint64_t start_offset = entry.st_value;
      if (entry.st_shndx != SHN_ABS) {
        start_offset += load_bias;
      }
      uint64_t end_offset = start_offset + entry.st_size;

      // Cache the value.
      symbols_.emplace_back(start_offset, end_offset, str_offset_ + entry.st_name);
      symbol_added = true;

      if (addr >= start_offset && addr < end_offset) {
        *func_offset = addr - start_offset;
        uint64_t offset = str_offset_ + entry.st_name;
        if (offset < str_end_) {
          return_value = elf_memory->ReadString(offset, name, str_end_ - offset);
        }
        break;
      }
    }
  }

  if (symbol_added) {
    std::sort(symbols_.begin(), symbols_.end(),
              [](const Info& a, const Info& b) { return a.start_offset < b.start_offset; });
  }
  return return_value;
}

// Instantiate all of the needed template functions.
template bool Symbols::GetName<Elf32_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
template bool Symbols::GetName<Elf64_Sym>(uint64_t, uint64_t, Memory*, std::string*, uint64_t*);
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _LIBUNWINDSTACK_SYMBOLS_H
#define _LIBUNWINDSTACK_SYMBOLS_H

#include <stdint.h>

#include <string>
#include <vector>

// Forward declaration.
class Memory;

class Symbols {
  struct Info {
    Info(uint64_t start_offset, uint64_t end_offset, uint64_t str_offset)
        : start_offset(start_offset), end_offset(end_offset), str_offset(str_offset) {}
    uint64_t start_offset;
    uint64_t end_offset;
    uint64_t str_offset;
  };

 public:
  Symbols(uint64_t offset, uint64_t size, uint64_t entry_size, uint64_t str_offset,
          uint64_t str_size);
  virtual ~Symbols() = default;

  const Info* GetInfoFromCache(uint64_t addr);

  template <typename SymType>
  bool GetName(uint64_t addr, uint64_t load_bias, Memory* elf_memory, std::string* name,
               uint64_t* func_offset);

  void ClearCache() {
    symbols_.clear();
    cur_offset_ = offset_;
  }

 private:
  uint64_t cur_offset_;
  uint64_t offset_;
  uint64_t end_;
  uint64_t entry_size_;
  uint64_t str_offset_;
  uint64_t str_end_;

  std::vector<Info> symbols_;
};

#endif  // _LIBUNWINDSTACK_SYMBOLS_H
+335 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <elf.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <vector>

#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <gtest/gtest.h>

#include "Memory.h"
#include "MemoryFake.h"
#include "Symbols.h"

template <typename TypeParam>
class SymbolsTest : public ::testing::Test {
 protected:
  void SetUp() override { memory_.Clear(); }

  void InitSym(TypeParam* sym, uint32_t st_value, uint32_t st_size, uint32_t st_name) {
    memset(sym, 0, sizeof(*sym));
    sym->st_info = STT_FUNC;
    sym->st_value = st_value;
    sym->st_size = st_size;
    sym->st_name = st_name;
    sym->st_shndx = SHN_COMMON;
  }

  MemoryFake memory_;
};
TYPED_TEST_CASE_P(SymbolsTest);

TYPED_TEST_P(SymbolsTest, function_bounds_check) {
  Symbols symbols(0x1000, sizeof(TypeParam), sizeof(TypeParam), 0x2000, 0x100);

  TypeParam sym;
  this->InitSym(&sym, 0x5000, 0x10, 0x40);
  uint64_t offset = 0x1000;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));

  std::string fake_name("fake_function");
  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);

  std::string name;
  uint64_t func_offset;
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("fake_function", name);
  ASSERT_EQ(0U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x500f, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("fake_function", name);
  ASSERT_EQ(0xfU, func_offset);

  // Check one before and one after the function.
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x4fff, 0, &this->memory_, &name, &func_offset));
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5010, 0, &this->memory_, &name, &func_offset));
}

TYPED_TEST_P(SymbolsTest, no_symbol) {
  Symbols symbols(0x1000, sizeof(TypeParam), sizeof(TypeParam), 0x2000, 0x100);

  TypeParam sym;
  this->InitSym(&sym, 0x5000, 0x10, 0x40);
  uint64_t offset = 0x1000;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));

  std::string fake_name("fake_function");
  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);

  // First verify that we can get the name.
  std::string name;
  uint64_t func_offset;
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("fake_function", name);
  ASSERT_EQ(0U, func_offset);

  // Now modify the info field so it's no longer a function.
  sym.st_info = 0;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  // Clear the cache to force the symbol data to be re-read.
  symbols.ClearCache();
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));

  // Set the function back, and set the shndx to UNDEF.
  sym.st_info = STT_FUNC;
  sym.st_shndx = SHN_UNDEF;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  // Clear the cache to force the symbol data to be re-read.
  symbols.ClearCache();
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
}

TYPED_TEST_P(SymbolsTest, multiple_entries) {
  Symbols symbols(0x1000, sizeof(TypeParam) * 3, sizeof(TypeParam), 0x2000, 0x500);

  TypeParam sym;
  uint64_t offset = 0x1000;
  std::string fake_name;

  this->InitSym(&sym, 0x5000, 0x10, 0x40);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_one";
  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);
  offset += sizeof(sym);

  this->InitSym(&sym, 0x3004, 0x200, 0x100);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_two";
  this->memory_.SetMemory(0x2100, fake_name.c_str(), fake_name.size() + 1);
  offset += sizeof(sym);

  this->InitSym(&sym, 0xa010, 0x20, 0x230);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_three";
  this->memory_.SetMemory(0x2230, fake_name.c_str(), fake_name.size() + 1);

  std::string name;
  uint64_t func_offset;
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_two", name);
  ASSERT_EQ(1U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_one", name);
  ASSERT_EQ(4U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_three", name);
  ASSERT_EQ(1U, func_offset);

  // Reget some of the others to verify getting one function name doesn't
  // affect any of the next calls.
  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5008, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_one", name);
  ASSERT_EQ(8U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3008, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_two", name);
  ASSERT_EQ(4U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa01a, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_three", name);
  ASSERT_EQ(0xaU, func_offset);
}

TYPED_TEST_P(SymbolsTest, multiple_entries_nonstandard_size) {
  uint64_t entry_size = sizeof(TypeParam) + 5;
  Symbols symbols(0x1000, entry_size * 3, entry_size, 0x2000, 0x500);

  TypeParam sym;
  uint64_t offset = 0x1000;
  std::string fake_name;

  this->InitSym(&sym, 0x5000, 0x10, 0x40);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_one";
  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);
  offset += entry_size;

  this->InitSym(&sym, 0x3004, 0x200, 0x100);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_two";
  this->memory_.SetMemory(0x2100, fake_name.c_str(), fake_name.size() + 1);
  offset += entry_size;

  this->InitSym(&sym, 0xa010, 0x20, 0x230);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  fake_name = "function_three";
  this->memory_.SetMemory(0x2230, fake_name.c_str(), fake_name.size() + 1);

  std::string name;
  uint64_t func_offset;
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x3005, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_two", name);
  ASSERT_EQ(1U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_one", name);
  ASSERT_EQ(4U, func_offset);

  name.clear();
  ASSERT_TRUE(symbols.GetName<TypeParam>(0xa011, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function_three", name);
  ASSERT_EQ(1U, func_offset);
}

TYPED_TEST_P(SymbolsTest, load_bias) {
  Symbols symbols(0x1000, sizeof(TypeParam), sizeof(TypeParam), 0x2000, 0x100);

  TypeParam sym;
  this->InitSym(&sym, 0x5000, 0x10, 0x40);
  uint64_t offset = 0x1000;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));

  std::string fake_name("fake_function");
  this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);

  // Set a non-zero load_bias that should be a valid function offset.
  std::string name;
  uint64_t func_offset;
  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5004, 0x1000, &this->memory_, &name, &func_offset));
  ASSERT_EQ("fake_function", name);
  ASSERT_EQ(4U, func_offset);

  // Set a flag that should cause the load_bias to be ignored.
  sym.st_shndx = SHN_ABS;
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  // Clear the cache to force the symbol data to be re-read.
  symbols.ClearCache();
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5004, 0x1000, &this->memory_, &name, &func_offset));
}

TYPED_TEST_P(SymbolsTest, symtab_value_out_of_bounds) {
  Symbols symbols_end_at_100(0x1000, sizeof(TypeParam) * 2, sizeof(TypeParam), 0x2000, 0x100);
  Symbols symbols_end_at_200(0x1000, sizeof(TypeParam) * 2, sizeof(TypeParam), 0x2000, 0x200);

  TypeParam sym;
  uint64_t offset = 0x1000;

  this->InitSym(&sym, 0x5000, 0x10, 0xfb);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  offset += sizeof(sym);

  this->InitSym(&sym, 0x3000, 0x10, 0x100);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));

  // Put the name across the end of the tab.
  std::string fake_name("fake_function");
  this->memory_.SetMemory(0x20fb, fake_name.c_str(), fake_name.size() + 1);

  std::string name;
  uint64_t func_offset;
  // Verify that we can get the function name properly for both entries.
  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("fake_function", name);
  ASSERT_EQ(0U, func_offset);
  ASSERT_TRUE(symbols_end_at_200.GetName<TypeParam>(0x3000, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("function", name);
  ASSERT_EQ(0U, func_offset);

  // Now use the symbol table that ends at 0x100.
  ASSERT_FALSE(
      symbols_end_at_100.GetName<TypeParam>(0x5000, 0, &this->memory_, &name, &func_offset));
  ASSERT_FALSE(
      symbols_end_at_100.GetName<TypeParam>(0x3000, 0, &this->memory_, &name, &func_offset));
}

// Verify the entire func table is cached.
TYPED_TEST_P(SymbolsTest, symtab_read_cached) {
  Symbols symbols(0x1000, 3 * sizeof(TypeParam), sizeof(TypeParam), 0xa000, 0x1000);

  TypeParam sym;
  uint64_t offset = 0x1000;

  // Make sure that these entries are not in ascending order.
  this->InitSym(&sym, 0x5000, 0x10, 0x100);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  offset += sizeof(sym);

  this->InitSym(&sym, 0x2000, 0x300, 0x200);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  offset += sizeof(sym);

  this->InitSym(&sym, 0x1000, 0x100, 0x300);
  this->memory_.SetMemory(offset, &sym, sizeof(sym));
  offset += sizeof(sym);

  // Do call that should cache all of the entries (except the string data).
  std::string name;
  uint64_t func_offset;
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, 0, &this->memory_, &name, &func_offset));
  this->memory_.Clear();
  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, 0, &this->memory_, &name, &func_offset));

  // Clear the memory and only put the symbol data string data in memory.
  this->memory_.Clear();

  std::string fake_name;
  fake_name = "first_entry";
  this->memory_.SetMemory(0xa100, fake_name.c_str(), fake_name.size() + 1);
  fake_name = "second_entry";
  this->memory_.SetMemory(0xa200, fake_name.c_str(), fake_name.size() + 1);
  fake_name = "third_entry";
  this->memory_.SetMemory(0xa300, fake_name.c_str(), fake_name.size() + 1);

  ASSERT_TRUE(symbols.GetName<TypeParam>(0x5001, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("first_entry", name);
  ASSERT_EQ(1U, func_offset);

  ASSERT_TRUE(symbols.GetName<TypeParam>(0x2002, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("second_entry", name);
  ASSERT_EQ(2U, func_offset);

  ASSERT_TRUE(symbols.GetName<TypeParam>(0x1003, 0, &this->memory_, &name, &func_offset));
  ASSERT_EQ("third_entry", name);
  ASSERT_EQ(3U, func_offset);
}

REGISTER_TYPED_TEST_CASE_P(SymbolsTest, function_bounds_check, no_symbol, multiple_entries,
                           multiple_entries_nonstandard_size, load_bias, symtab_value_out_of_bounds,
                           symtab_read_cached);

typedef ::testing::Types<Elf32_Sym, Elf64_Sym> SymbolsTestTypes;
INSTANTIATE_TYPED_TEST_CASE_P(, SymbolsTest, SymbolsTestTypes);