Commit b95277ba authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[Sampling profiler] Fix and test NativeUnwinderAndroid module extents

Reuses the ModuleCache POSIX module implementation for Android ELF
modules, which fixes the incorrect start and end addresses previously
generated by AndroidModule. Renames AndroidModule to NonElfModule.
Removes tests specific to the previous implementation.

Also includes a latent 64-bit bug fix where the register copy would
only copy half the registers.

Bug: 1004855
Change-Id: I3b26fac427043b2022b1d35b5c985e468ce35692
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2253221Reviewed-by: default avatarEtienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: Mike Wittman <wittman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#783689}
parent a3e70815
...@@ -18,8 +18,6 @@ ...@@ -18,8 +18,6 @@
#include "base/profiler/module_cache.h" #include "base/profiler/module_cache.h"
#include "base/profiler/native_unwinder.h" #include "base/profiler/native_unwinder.h"
#include "base/profiler/profile_builder.h" #include "base/profiler/profile_builder.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "build/build_config.h" #include "build/build_config.h"
#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
...@@ -33,46 +31,21 @@ ...@@ -33,46 +31,21 @@
namespace base { namespace base {
namespace { namespace {
// Returns the hex string build id given the binary build id from MapInfo. class NonElfModule : public ModuleCache::Module {
// Returns the empty string if no build id is present.
//
// Build IDs follow a cross-platform format consisting of two fields
// concatenated together:
// - the module's unique id encoded as a hex string, and
// - the age suffix for incremental builds.
//
// On POSIX, the unique id comes from the ELF binary's .note.gnu.build-id
// section. The age field is always 0.
std::string EncodeBuildID(StringPiece map_info_build_id) {
if (map_info_build_id.empty())
return std::string();
return HexEncode(map_info_build_id.data(), map_info_build_id.size()) + "0";
}
// We assume this is a file-backed region if the name looks like an absolute
// path, and return the basename. Otherwise we assume the region is not
// associated with a file and return the full name string.
FilePath CreateDebugBasename(StringPiece map_info_name) {
const FilePath path = FilePath(map_info_name);
return !map_info_name.empty() && map_info_name[0] == '/' ? path.BaseName()
: path;
}
class AndroidModule : public ModuleCache::Module {
public: public:
AndroidModule(unwindstack::MapInfo* map_info) NonElfModule(unwindstack::MapInfo* map_info)
: start_(map_info->start), : start_(map_info->start),
size_(map_info->end - map_info->start), size_(map_info->end - start_),
build_id_(EncodeBuildID(map_info->GetBuildID())), map_info_name_(map_info->name) {}
debug_basename_(CreateDebugBasename(map_info->name)) {} ~NonElfModule() override = default;
~AndroidModule() override = default;
uintptr_t GetBaseAddress() const override { return start_; } uintptr_t GetBaseAddress() const override { return start_; }
std::string GetId() const override { return build_id_; } std::string GetId() const override { return std::string(); }
FilePath GetDebugBasename() const override { return debug_basename_; } FilePath GetDebugBasename() const override {
return FilePath(map_info_name_);
}
// Gets the size of the module. // Gets the size of the module.
size_t GetSize() const override { return size_; } size_t GetSize() const override { return size_; }
...@@ -80,10 +53,10 @@ class AndroidModule : public ModuleCache::Module { ...@@ -80,10 +53,10 @@ class AndroidModule : public ModuleCache::Module {
// True if this is a native module. // True if this is a native module.
bool IsNative() const override { return true; } bool IsNative() const override { return true; }
private:
const uintptr_t start_; const uintptr_t start_;
const size_t size_; const size_t size_;
const std::string build_id_; const std::string map_info_name_;
const FilePath debug_basename_;
}; };
std::unique_ptr<unwindstack::Regs> CreateFromRegisterContext( std::unique_ptr<unwindstack::Regs> CreateFromRegisterContext(
...@@ -104,10 +77,10 @@ void CopyToRegisterContext(unwindstack::Regs* regs, ...@@ -104,10 +77,10 @@ void CopyToRegisterContext(unwindstack::Regs* regs,
RegisterContext* thread_context) { RegisterContext* thread_context) {
#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
memcpy(reinterpret_cast<void*>(&thread_context->arm_r0), regs->RawData(), memcpy(reinterpret_cast<void*>(&thread_context->arm_r0), regs->RawData(),
unwindstack::ARM_REG_LAST * sizeof(uint32_t)); unwindstack::ARM_REG_LAST * sizeof(uintptr_t));
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS) #elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS)
memcpy(reinterpret_cast<void*>(&thread_context->regs[0]), regs->RawData(), memcpy(reinterpret_cast<void*>(&thread_context->regs[0]), regs->RawData(),
unwindstack::ARM64_REG_LAST * sizeof(uint32_t)); unwindstack::ARM64_REG_LAST * sizeof(uintptr_t));
#else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) #else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
NOTREACHED(); NOTREACHED();
#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) #endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
...@@ -244,12 +217,29 @@ UnwindResult NativeUnwinderAndroid::TryUnwind(RegisterContext* thread_context, ...@@ -244,12 +217,29 @@ UnwindResult NativeUnwinderAndroid::TryUnwind(RegisterContext* thread_context,
void NativeUnwinderAndroid::AddInitialModulesFromMaps( void NativeUnwinderAndroid::AddInitialModulesFromMaps(
const unwindstack::Maps& memory_regions_map, const unwindstack::Maps& memory_regions_map,
ModuleCache* module_cache) { ModuleCache* module_cache) {
for (const auto& region : memory_regions_map) { // The effect of this loop is to create modules for the executable regions in
// Only add executable regions. // the memory map. Regions composing a mapped ELF file are handled specially
// however: for just one module extending from the ELF base address to the
// *last* executable region backed by the file is implicitly created by
// ModuleCache. This avoids duplicate module instances covering the same
// in-memory module in the case that a module has multiple mmapped executable
// regions.
for (const std::unique_ptr<unwindstack::MapInfo>& region :
memory_regions_map) {
if (!(region->flags & PROT_EXEC)) if (!(region->flags & PROT_EXEC))
continue; continue;
// Use the standard ModuleCache POSIX module representation for ELF files.
// This call returns the containing ELF module for the region, creating it
// if it doesn't exist.
if (module_cache->GetModuleForAddress(
static_cast<uintptr_t>(region->start))) {
continue;
}
// Non-ELF modules are represented with NonElfModule.
module_cache->AddCustomNativeModule( module_cache->AddCustomNativeModule(
std::make_unique<AndroidModule>(region.get())); std::make_unique<NonElfModule>(region.get()));
} }
} }
...@@ -264,7 +254,7 @@ void NativeUnwinderAndroid::EmitDexFrame(uintptr_t dex_pc, ...@@ -264,7 +254,7 @@ void NativeUnwinderAndroid::EmitDexFrame(uintptr_t dex_pc,
// AddInitialModules(). // AddInitialModules().
unwindstack::MapInfo* map_info = memory_regions_map_->Find(dex_pc); unwindstack::MapInfo* map_info = memory_regions_map_->Find(dex_pc);
if (map_info) { if (map_info) {
auto new_module = std::make_unique<AndroidModule>(map_info); auto new_module = std::make_unique<NonElfModule>(map_info);
module = new_module.get(); module = new_module.get();
module_cache->AddCustomNativeModule(std::move(new_module)); module_cache->AddCustomNativeModule(std::move(new_module));
} }
......
...@@ -6,9 +6,12 @@ ...@@ -6,9 +6,12 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <inttypes.h>
#include <stdio.h> // For printf address. #include <stdio.h> // For printf address.
#include <string.h> #include <string.h>
#include <algorithm>
#include <iterator> #include <iterator>
#include <vector>
#include "base/android/build_info.h" #include "base/android/build_info.h"
#include "base/android/jni_android.h" #include "base/android/jni_android.h"
...@@ -20,6 +23,7 @@ ...@@ -20,6 +23,7 @@
#include "base/profiler/stack_sampler.h" #include "base/profiler/stack_sampler.h"
#include "base/profiler/stack_sampling_profiler_test_util.h" #include "base/profiler/stack_sampling_profiler_test_util.h"
#include "base/profiler/thread_delegate_posix.h" #include "base/profiler/thread_delegate_posix.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -30,6 +34,11 @@ namespace base { ...@@ -30,6 +34,11 @@ namespace base {
namespace { namespace {
bool CompareModulesByBaseAddress(const ModuleCache::Module* a,
const ModuleCache::Module* b) {
return a->GetBaseAddress() < b->GetBaseAddress();
}
// Add a MapInfo with the provided values to |maps|. // Add a MapInfo with the provided values to |maps|.
void AddMapInfo(uint64_t start, void AddMapInfo(uint64_t start,
uint64_t end, uint64_t end,
...@@ -387,25 +396,8 @@ TEST(NativeUnwinderAndroidTest, UnwindStackMemoryTest) { ...@@ -387,25 +396,8 @@ TEST(NativeUnwinderAndroidTest, UnwindStackMemoryTest) {
check_read_succeeds(end - 1, 1); check_read_succeeds(end - 1, 1);
} }
// Checks the debug basename for a module with a path name. // Checks the debug basename is the whole name for a non-ELF module.
TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForPath) { TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForNonElf) {
unwindstack::Maps maps;
AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "/usr/lib/foo.so",
{0xAA}, maps);
ModuleCache module_cache;
NativeUnwinderAndroid::AddInitialModulesFromMaps(maps, &module_cache);
std::vector<const ModuleCache::Module*> modules = module_cache.GetModules();
ASSERT_EQ(1u, modules.size());
EXPECT_EQ("foo.so", modules[0]->GetDebugBasename().value());
}
// Checks the debug basename is the whole name for a module with a non-path
// name.
TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForNonPath) {
unwindstack::Maps maps; unwindstack::Maps maps;
AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "[foo / bar]", {0xAA}, AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "[foo / bar]", {0xAA},
...@@ -420,37 +412,48 @@ TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForNonPath) { ...@@ -420,37 +412,48 @@ TEST(NativeUnwinderAndroidTest, ModuleDebugBasenameForNonPath) {
EXPECT_EQ("[foo / bar]", modules[0]->GetDebugBasename().value()); EXPECT_EQ("[foo / bar]", modules[0]->GetDebugBasename().value());
} }
// Checks that the specified build id is returned. // Checks that modules are only created for executable memory regions.
TEST(NativeUnwinderAndroidTest, ModuleId) { TEST(NativeUnwinderAndroidTest, ModulesCreatedOnlyForExecutableRegions) {
unwindstack::Maps maps; unwindstack::Maps maps;
AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "[a]", {0xAA}, maps);
AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "/lib/foo.so", AddMapInfo(0x2000u, 0x3000u, 0u, PROT_READ, "[b]", {0xAB}, maps);
{0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}, maps); AddMapInfo(0x3000u, 0x4000u, 0u, PROT_READ | PROT_EXEC, "[c]", {0xAC}, maps);
ModuleCache module_cache; ModuleCache module_cache;
NativeUnwinderAndroid::AddInitialModulesFromMaps(maps, &module_cache); NativeUnwinderAndroid::AddInitialModulesFromMaps(maps, &module_cache);
std::vector<const ModuleCache::Module*> modules = module_cache.GetModules(); std::vector<const ModuleCache::Module*> modules = module_cache.GetModules();
std::sort(modules.begin(), modules.end(), CompareModulesByBaseAddress);
ASSERT_EQ(1u, modules.size()); ASSERT_EQ(2u, modules.size());
// The id should have a '0' age field appended. EXPECT_EQ(0x1000u, modules[0]->GetBaseAddress());
EXPECT_EQ("1234567890ABCDEF0", modules[0]->GetId()); EXPECT_EQ(0x3000u, modules[1]->GetBaseAddress());
} }
// Checks that an empty module id has no age field appended. // Checks that module address ranges don't overlap.
TEST(NativeUnwinderAndroidTest, EmptyModuleId) { TEST(NativeUnwinderAndroidTest, NonOverlappingModules) {
unwindstack::Maps maps;
AddMapInfo(0x1000u, 0x2000u, 0u, PROT_READ | PROT_EXEC, "/lib/foo.so",
std::string(), maps);
ModuleCache module_cache; ModuleCache module_cache;
NativeUnwinderAndroid::AddInitialModulesFromMaps(maps, &module_cache); std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps();
NativeUnwinderAndroid::AddInitialModulesFromMaps(*maps, &module_cache);
std::vector<const ModuleCache::Module*> modules = module_cache.GetModules(); std::vector<const ModuleCache::Module*> modules = module_cache.GetModules();
std::sort(modules.begin(), modules.end(), CompareModulesByBaseAddress);
auto loc = std::adjacent_find(
modules.begin(), modules.end(),
[](const ModuleCache::Module* m1, const ModuleCache::Module* m2) {
return m2->GetBaseAddress() < m1->GetBaseAddress() + m1->GetSize();
});
ASSERT_EQ(1u, modules.size()); const auto describe_module = [](const ModuleCache::Module* module) {
EXPECT_EQ(std::string(), modules[0]->GetId()); return StringPrintf(
"id \"%s\", debug basename \"%s\" at [0x%" PRIxPTR ", 0x%" PRIxPTR ")",
module->GetId().c_str(), module->GetDebugBasename().value().c_str(),
module->GetBaseAddress(), module->GetBaseAddress() + module->GetSize());
};
EXPECT_EQ(modules.end(), loc) << "module overlap found between\n"
<< " " << describe_module(*loc) << " and \n"
<< " " << describe_module(*std::next(loc));
} }
// ModuleCache::GetModuleForAddress() is not implemented for 64-bit arm. // ModuleCache::GetModuleForAddress() is not implemented for 64-bit arm.
...@@ -463,8 +466,9 @@ TEST(NativeUnwinderAndroidTest, EmptyModuleId) { ...@@ -463,8 +466,9 @@ TEST(NativeUnwinderAndroidTest, EmptyModuleId) {
// state created by the ModuleCache. Checks the module for a system library. // state created by the ModuleCache. Checks the module for a system library.
TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) { TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) {
ModuleCache unwinder_module_cache; ModuleCache unwinder_module_cache;
NativeUnwinderAndroid::AddInitialModulesFromMaps( std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps();
*NativeUnwinderAndroid::CreateMaps(), &unwinder_module_cache); NativeUnwinderAndroid::AddInitialModulesFromMaps(*maps,
&unwinder_module_cache);
const uintptr_t c_library_function_address = const uintptr_t c_library_function_address =
reinterpret_cast<uintptr_t>(&printf); reinterpret_cast<uintptr_t>(&printf);
...@@ -486,11 +490,12 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) { ...@@ -486,11 +490,12 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) {
reference_module_cache.GetModuleForAddress(c_library_function_address); reference_module_cache.GetModuleForAddress(c_library_function_address);
ASSERT_NE(nullptr, reference_module); ASSERT_NE(nullptr, reference_module);
// TODO(https://crbug.com/1004855): Fix base address and size discrepancies EXPECT_EQ(reference_module->GetBaseAddress(),
// and add checks. unwinder_module->GetBaseAddress());
EXPECT_EQ(reference_module->GetId(), unwinder_module->GetId()); EXPECT_EQ(reference_module->GetId(), unwinder_module->GetId());
EXPECT_EQ(reference_module->GetDebugBasename(), EXPECT_EQ(reference_module->GetDebugBasename(),
unwinder_module->GetDebugBasename()); unwinder_module->GetDebugBasename());
EXPECT_EQ(unwinder_module->GetSize(), reference_module->GetSize());
} }
// ModuleCache::GetModuleForAddress() is not implemented for 64-bit arm. // ModuleCache::GetModuleForAddress() is not implemented for 64-bit arm.
...@@ -504,8 +509,9 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) { ...@@ -504,8 +509,9 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_SystemLibrary) {
// library. // library.
TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_ChromeLibrary) { TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_ChromeLibrary) {
ModuleCache unwinder_module_cache; ModuleCache unwinder_module_cache;
NativeUnwinderAndroid::AddInitialModulesFromMaps( std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps();
*NativeUnwinderAndroid::CreateMaps(), &unwinder_module_cache); NativeUnwinderAndroid::AddInitialModulesFromMaps(*maps,
&unwinder_module_cache);
const uintptr_t chrome_function_address = const uintptr_t chrome_function_address =
reinterpret_cast<uintptr_t>(&CaptureScenario); reinterpret_cast<uintptr_t>(&CaptureScenario);
...@@ -533,7 +539,7 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_ChromeLibrary) { ...@@ -533,7 +539,7 @@ TEST(NativeUnwinderAndroidTest, MAYBE_ModuleState_ChromeLibrary) {
EXPECT_EQ(reference_module->GetId(), unwinder_module->GetId()); EXPECT_EQ(reference_module->GetId(), unwinder_module->GetId());
EXPECT_EQ(reference_module->GetDebugBasename(), EXPECT_EQ(reference_module->GetDebugBasename(),
unwinder_module->GetDebugBasename()); unwinder_module->GetDebugBasename());
// TODO(https://crbug.com/1004855): Fix size discrepancy and add check. EXPECT_EQ(unwinder_module->GetSize(), reference_module->GetSize());
} }
} // namespace base } // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment