Commit bf5264fd authored by Alexei Filippov's avatar Alexei Filippov Committed by Commit Bot

[sampling heap profiler] Implement CreateModuleForAddress for Win platform.

Also move the function and its helpers to ModuleCache.

BUG=803276
TBR=dcheng@chromium.org
TBR=wittman@chromium.org

Reviewed-on: https://chromium-review.googlesource.com/1166206Reviewed-by: default avatarMike Wittman <wittman@chromium.org>
Change-Id: I28ad62df7a3f2f597f1a4f36069c51b541e7dc29
Reviewed-on: https://chromium-review.googlesource.com/1169533Reviewed-by: default avatarAlexei Filippov <alph@chromium.org>
Commit-Queue: Alexei Filippov <alph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581963}
parent abdf1cba
......@@ -722,6 +722,8 @@ jumbo_component("base") {
"sampling_heap_profiler/lock_free_address_hash_set.h",
"sampling_heap_profiler/module_cache.cc",
"sampling_heap_profiler/module_cache.h",
"sampling_heap_profiler/module_cache_mac.cc",
"sampling_heap_profiler/module_cache_win.cc",
"sampling_heap_profiler/sampling_heap_profiler.cc",
"sampling_heap_profiler/sampling_heap_profiler.h",
"scoped_clear_errno.h",
......@@ -1173,6 +1175,7 @@ jumbo_component("base") {
"process/process_posix.cc",
"profiler/native_stack_sampler_posix.cc",
"rand_util_posix.cc",
"sampling_heap_profiler/module_cache_posix.cc",
"strings/string_util_posix.h",
"strings/sys_string_conversions_posix.cc",
"sync_socket_posix.cc",
......@@ -1421,6 +1424,7 @@ jumbo_component("base") {
"process/process_metrics_posix.cc",
"profiler/native_stack_sampler_posix.cc",
"rand_util_fuchsia.cc",
"sampling_heap_profiler/module_cache_posix.cc",
"strings/string_util_posix.h",
"strings/sys_string_conversions_posix.cc",
"sync_socket_posix.cc",
......@@ -1628,7 +1632,10 @@ jumbo_component("base") {
# Desktop Mac.
if (is_mac) {
sources -= [ "profiler/native_stack_sampler_posix.cc" ]
sources -= [
"profiler/native_stack_sampler_posix.cc",
"sampling_heap_profiler/module_cache_posix.cc",
]
sources += [
"mac/scoped_typeref.h",
"memory/platform_shared_memory_region_mac.cc",
......
......@@ -56,13 +56,6 @@ class NativeStackSampler {
// any NativeStackSampler object.
static std::unique_ptr<StackBuffer> CreateStackBuffer();
// Creates a Module object for the specified memory address. If the address
// does not belong to a module returns an invalid module.
// TODO(alph): Move platform-specific module lookup code into
// base::ModuleCache and remove this function.
static StackSamplingProfiler::InternalModule GetModuleForAddress(
uintptr_t address);
// The following functions are all called on the SamplingThread (not the
// thread being sampled).
......
......@@ -25,6 +25,7 @@
#include "base/mac/mach_logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/strings/string_number_conversions.h"
extern "C" {
......@@ -35,8 +36,6 @@ namespace base {
using Frame = StackSamplingProfiler::Frame;
using InternalFrame = StackSamplingProfiler::InternalFrame;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using ProfileBuilder = StackSamplingProfiler::ProfileBuilder;
namespace {
......@@ -46,7 +45,7 @@ namespace {
struct ModuleCacheEntry {
ModuleCacheEntry(uintptr_t start,
uintptr_t end,
InternalModule internal_module)
ModuleCache::Module internal_module)
: base_address(start),
end_address(end),
internal_module(std::move(internal_module)){};
......@@ -58,70 +57,9 @@ struct ModuleCacheEntry {
uintptr_t end_address;
// Module information.
InternalModule internal_module;
ModuleCache::Module internal_module;
};
// Module identifiers ---------------------------------------------------------
// Returns the unique build ID for a module loaded at |module_addr|. Returns the
// empty string if the function fails to get the build ID.
//
// Build IDs are created by the concatenation of the module's GUID (Windows) /
// UUID (Mac) and an "age" field that indicates how many times that GUID/UUID
// has been reused. In Windows binaries, the "age" field is present in the
// module header, but on the Mac, UUIDs are never reused and so the "age" value
// appended to the UUID is always 0.
std::string GetUniqueId(const void* module_addr) {
const mach_header_64* mach_header =
reinterpret_cast<const mach_header_64*>(module_addr);
DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
size_t offset = sizeof(mach_header_64);
size_t offset_limit = sizeof(mach_header_64) + mach_header->sizeofcmds;
for (uint32_t i = 0; i < mach_header->ncmds; ++i) {
if (offset + sizeof(load_command) >= offset_limit)
return std::string();
const load_command* current_cmd = reinterpret_cast<const load_command*>(
reinterpret_cast<const uint8_t*>(mach_header) + offset);
if (offset + current_cmd->cmdsize > offset_limit) {
// This command runs off the end of the command list. This is malformed.
return std::string();
}
if (current_cmd->cmd == LC_UUID) {
if (current_cmd->cmdsize < sizeof(uuid_command)) {
// This "UUID command" is too small. This is malformed.
return std::string();
}
const uuid_command* uuid_cmd =
reinterpret_cast<const uuid_command*>(current_cmd);
static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
"UUID field of UUID command should be 16 bytes.");
// The ID is comprised of the UUID concatenated with the Mac's "age" value
// which is always 0.
return HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) + "0";
}
offset += current_cmd->cmdsize;
}
return std::string();
}
// Returns the size of the _TEXT segment of the module loaded at |module_addr|.
size_t GetModuleTextSize(const void* module_addr) {
const mach_header_64* mach_header =
reinterpret_cast<const mach_header_64*>(module_addr);
DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
unsigned long module_size;
getsegmentdata(mach_header, SEG_TEXT, &module_size);
return module_size;
}
// Stack walking --------------------------------------------------------------
// Fills |state| with |target_thread|'s context.
......@@ -210,6 +148,8 @@ uint32_t GetFrameOffset(int compact_unwind_info) {
(((1 << __builtin_popcount(UNWIND_X86_64_RBP_FRAME_OFFSET))) - 1));
}
} // namespace
// True if the unwind from |leaf_frame_rip| may trigger a crash bug in
// unw_init_local. If so, the stack walk should be aborted at the leaf frame.
bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) {
......@@ -238,11 +178,13 @@ bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) {
vm_size_t size = sizeof(unused);
return vm_read_overwrite(current_task(),
reinterpret_cast<vm_address_t>(info.dli_fbase) +
GetModuleTextSize(info.dli_fbase),
ModuleCache::GetModuleTextSize(info.dli_fbase),
sizeof(unused),
reinterpret_cast<vm_address_t>(&unused), &size) != 0;
}
namespace {
// Check if the cursor contains a valid-looking frame pointer for frame pointer
// unwinds. If the stack frame has a frame pointer, stepping the cursor will
// involve indexing memory access off of that pointer. In that case,
......@@ -336,6 +278,8 @@ class ScopedSuspendThread {
DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread);
};
} // namespace
// NativeStackSamplerMac ------------------------------------------------------
class NativeStackSamplerMac : public NativeStackSampler {
......@@ -351,9 +295,9 @@ class NativeStackSamplerMac : public NativeStackSampler {
ProfileBuilder* profile_builder) override;
private:
// Returns the InternalModule containing |instruction_pointer|, adding it to
// module_cache_entry_ if it's not already present.
InternalModule GetInternalModule(uintptr_t instruction_pointer);
// Returns the ModuleCache::Module containing |instruction_pointer|, adding it
// to module_cache_entry_ if it's not already present.
ModuleCache::Module GetInternalModule(uintptr_t instruction_pointer);
// Walks the stack represented by |unwind_context|, calling back to the
// provided lambda for each frame. Returns false if an error occurred,
......@@ -488,17 +432,18 @@ std::vector<InternalFrame> NativeStackSamplerMac::RecordStackFrames(
return HasValidRbp(unwind_cursor, new_stack_top);
};
WalkStack(
thread_state,
[&internal_frames](uintptr_t frame_ip, InternalModule internal_module) {
internal_frames.emplace_back(frame_ip, std::move(internal_module));
},
continue_predicate);
WalkStack(thread_state,
[&internal_frames](uintptr_t frame_ip,
ModuleCache::Module internal_module) {
internal_frames.emplace_back(frame_ip,
std::move(internal_module));
},
continue_predicate);
return internal_frames;
}
InternalModule NativeStackSamplerMac::GetInternalModule(
ModuleCache::Module NativeStackSamplerMac::GetInternalModule(
uintptr_t instruction_pointer) {
// Check if |instruction_pointer| is in the address range of a module we've
// already seen.
......@@ -511,7 +456,10 @@ InternalModule NativeStackSamplerMac::GetInternalModule(
if (loc != module_cache_entry_.end())
return loc->internal_module;
InternalModule module = GetModuleForAddress(instruction_pointer);
ModuleCache::Module module =
ModuleCache::CreateModuleForAddress(instruction_pointer);
// TODO(chengx): refactor to use the ModuleCache public interface and remove
// NativeStackSamplerMac module caching.
module_cache_entry_.emplace_back(module.base_address,
module.base_address + module.size, module);
return module;
......@@ -543,7 +491,7 @@ bool NativeStackSamplerMac::WalkStackFromContext(
// libunwind adds the expected stack size, it will look for the return
// address in the wrong place. This check should ensure that we bail before
// trying to deref a bad IP obtained this way in the previous frame.
InternalModule internal_module = GetInternalModule(rip);
ModuleCache::Module internal_module = GetInternalModule(rip);
if (!internal_module.is_valid)
return false;
......@@ -608,8 +556,6 @@ void NativeStackSamplerMac::WalkStack(
}
}
} // namespace
// NativeStackSampler ---------------------------------------------------------
// static
......@@ -619,18 +565,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::make_unique<NativeStackSamplerMac>(thread_id, test_delegate);
}
// static
StackSamplingProfiler::InternalModule NativeStackSampler::GetModuleForAddress(
uintptr_t address) {
Dl_info inf;
if (!dladdr(reinterpret_cast<const void*>(address), &inf))
return StackSamplingProfiler::InternalModule();
auto base_module_address = reinterpret_cast<uintptr_t>(inf.dli_fbase);
return StackSamplingProfiler::InternalModule(
base_module_address, GetUniqueId(inf.dli_fbase), FilePath(inf.dli_fname),
GetModuleTextSize(inf.dli_fbase));
}
// static
size_t NativeStackSampler::GetStackBufferSize() {
// In platform_thread_mac's GetDefaultThreadStackSize(), RLIMIT_STACK is used
......
......@@ -12,13 +12,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::unique_ptr<NativeStackSampler>();
}
// static
StackSamplingProfiler::InternalModule NativeStackSampler::GetModuleForAddress(
uintptr_t address) {
// TODO(alph): Implement it.
return StackSamplingProfiler::InternalModule();
}
size_t NativeStackSampler::GetStackBufferSize() {
return 0;
}
......
......@@ -6,7 +6,6 @@
#include <windows.h>
#include <objbase.h>
#include <stddef.h>
#include <winternl.h>
......@@ -21,21 +20,14 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/profiler/win32_stack_frame_unwinder.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
namespace base {
using Frame = StackSamplingProfiler::Frame;
using InternalFrame = StackSamplingProfiler::InternalFrame;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using ProfileBuilder = StackSamplingProfiler::ProfileBuilder;
// Stack recording functions --------------------------------------------------
......@@ -203,34 +195,6 @@ void RecordStack(CONTEXT* context, std::vector<RecordedFrame>* stack) {
#endif
}
// Gets the unique build ID for a module. Windows build IDs are created by a
// concatenation of a GUID and AGE fields found in the headers of a module. The
// GUID is stored in the first 16 bytes and the AGE is stored in the last 4
// bytes. Returns the empty string if the function fails to get the build ID.
//
// Example:
// dumpbin chrome.exe /headers | find "Format:"
// ... Format: RSDS, {16B2A428-1DED-442E-9A36-FCE8CBD29726}, 10, ...
//
// The resulting buildID string of this instance of chrome.exe is
// "16B2A4281DED442E9A36FCE8CBD2972610".
//
// Note that the AGE field is encoded in decimal, not hex.
std::string GetBuildIDForModule(HMODULE module_handle) {
GUID guid;
DWORD age;
win::PEImage(module_handle).GetDebugId(&guid, &age, /* pdb_file= */ nullptr);
const int kGUIDSize = 39;
string16 build_id;
int result =
::StringFromGUID2(guid, WriteInto(&build_id, kGUIDSize), kGUIDSize);
if (result != kGUIDSize)
return std::string();
RemoveChars(build_id, L"{}-", &build_id);
build_id += StringPrintf(L"%d", age);
return UTF16ToUTF8(build_id);
}
// ScopedDisablePriorityBoost -------------------------------------------------
// Disables priority boost on a thread for the lifetime of the object.
......@@ -382,6 +346,8 @@ void SuspendThreadAndRecordStack(
RecordStack(&thread_context, stack);
}
} // namespace
// NativeStackSamplerWin ------------------------------------------------------
class NativeStackSamplerWin : public NativeStackSampler {
......@@ -397,10 +363,6 @@ class NativeStackSamplerWin : public NativeStackSampler {
ProfileBuilder* profile_builder) override;
private:
// Attempts to query the module filename, base address, and id for
// |module_handle|, and returns them in an InternalModule object.
static InternalModule GetModuleForHandle(HMODULE module_handle);
// Creates a set of internal frames with the information represented by
// |stack|.
std::vector<InternalFrame> CreateInternalFrames(
......@@ -414,7 +376,7 @@ class NativeStackSamplerWin : public NativeStackSampler {
const void* const thread_stack_base_address_;
// The internal module objects, indexed by the module handle.
std::map<HMODULE, InternalModule> module_cache_;
std::map<HMODULE, ModuleCache::Module> module_cache_;
DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin);
};
......@@ -446,23 +408,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::RecordStackFrames(
return CreateInternalFrames(stack);
}
// static
InternalModule NativeStackSamplerWin::GetModuleForHandle(
HMODULE module_handle) {
wchar_t module_name[MAX_PATH];
DWORD result_length =
::GetModuleFileName(module_handle, module_name, size(module_name));
if (result_length == 0)
return InternalModule();
const std::string& module_id = GetBuildIDForModule(module_handle);
if (module_id.empty())
return InternalModule();
return InternalModule(reinterpret_cast<uintptr_t>(module_handle), module_id,
FilePath(module_name));
}
std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
const std::vector<RecordedFrame>& stack) {
std::vector<InternalFrame> internal_frames;
......@@ -473,7 +418,7 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
HMODULE module_handle = frame.module.Get();
if (!module_handle) {
internal_frames.emplace_back(frame_ip, InternalModule());
internal_frames.emplace_back(frame_ip, ModuleCache::Module());
continue;
}
......@@ -483,7 +428,8 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
continue;
}
InternalModule internal_module = GetModuleForHandle(module_handle);
ModuleCache::Module internal_module =
ModuleCache::CreateModuleForHandle(module_handle);
if (internal_module.is_valid)
module_cache_.insert(std::make_pair(module_handle, internal_module));
......@@ -493,8 +439,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
return internal_frames;
}
} // namespace
// NativeStackSampler ---------------------------------------------------------
// static
......@@ -515,13 +459,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::unique_ptr<NativeStackSampler>();
}
// static
StackSamplingProfiler::InternalModule NativeStackSampler::GetModuleForAddress(
uintptr_t address) {
// TODO(alph): Implement it.
return StackSamplingProfiler::InternalModule();
}
// static
size_t NativeStackSampler::GetStackBufferSize() {
// The default Win32 reserved stack size is 1 MB and Chrome Windows threads
......
......@@ -52,27 +52,6 @@ StackSamplingProfiler::Module::Module(uintptr_t base_address,
StackSamplingProfiler::Module::~Module() = default;
// StackSamplingProfiler::InternalModule --------------------------------------
StackSamplingProfiler::InternalModule::InternalModule() : is_valid(false) {}
StackSamplingProfiler::InternalModule::InternalModule(uintptr_t base_address,
const std::string& id,
const FilePath& filename)
: InternalModule(base_address, id, filename, 0) {}
StackSamplingProfiler::InternalModule::InternalModule(uintptr_t base_address,
const std::string& id,
const FilePath& filename,
size_t size)
: base_address(base_address),
id(id),
filename(filename),
is_valid(true),
size(size) {}
StackSamplingProfiler::InternalModule::~InternalModule() = default;
// StackSamplingProfiler::Frame -----------------------------------------------
StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer,
......@@ -88,7 +67,7 @@ StackSamplingProfiler::Frame::Frame()
StackSamplingProfiler::InternalFrame::InternalFrame(
uintptr_t instruction_pointer,
InternalModule internal_module)
ModuleCache::Module internal_module)
: instruction_pointer(instruction_pointer),
internal_module(std::move(internal_module)) {}
......
......@@ -15,6 +15,7 @@
#include "base/base_export.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/strings/string16.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
......@@ -80,44 +81,6 @@ class BASE_EXPORT StackSamplingProfiler {
FilePath filename;
};
// InternalModule represents the module (DLL or exe) and its validness state.
// Different from Module, it has an additional field "is_valid".
//
// This struct is only used for sampling data transfer from NativeStackSampler
// to ProfileBuilder.
struct BASE_EXPORT InternalModule {
InternalModule();
InternalModule(uintptr_t base_address,
const std::string& id,
const FilePath& filename);
InternalModule(uintptr_t base_address,
const std::string& id,
const FilePath& filename,
size_t size);
~InternalModule();
// Points to the base address of the module.
uintptr_t base_address;
// An opaque binary string that uniquely identifies a particular program
// version with high probability. This is parsed from headers of the loaded
// module.
// For binaries generated by GNU tools:
// Contents of the .note.gnu.build-id field.
// On Windows:
// GUID + AGE in the debug image headers of a module.
std::string id;
// The filename of the module.
FilePath filename;
// The validness of the module.
bool is_valid;
// Size of the module.
size_t size;
};
// Frame represents an individual sampled stack frame with module information.
struct BASE_EXPORT Frame {
Frame(uintptr_t instruction_pointer, size_t module_index);
......@@ -141,14 +104,14 @@ class BASE_EXPORT StackSamplingProfiler {
// to ProfileBuilder.
struct BASE_EXPORT InternalFrame {
InternalFrame(uintptr_t instruction_pointer,
InternalModule internal_module);
ModuleCache::Module internal_module);
~InternalFrame();
// The sampled instruction pointer within the function.
uintptr_t instruction_pointer;
// The module information.
InternalModule internal_module;
ModuleCache::Module internal_module;
};
// Sample represents a set of stack frames with some extra information.
......
......@@ -68,9 +68,6 @@ using Frames = std::vector<Frame>;
using InternalFrame = StackSamplingProfiler::InternalFrame;
using InternalFrames = std::vector<InternalFrame>;
using InternalFrameSets = std::vector<std::vector<InternalFrame>>;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using Sample = StackSamplingProfiler::Sample;
namespace {
......
......@@ -119,6 +119,8 @@ Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
ScopedModuleHandle* module) {
#ifdef _WIN64
// TODO(chengx): update base::ModuleCache to return a ScopedModuleHandle and
// use it for this module lookup.
ScopedModuleHandle frame_module =
unwind_functions_->GetModuleForProgramCounter(context->Rip);
if (!frame_module.IsValid()) {
......
......@@ -5,10 +5,28 @@
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/no_destructor.h"
#include "base/profiler/native_stack_sampler.h"
namespace base {
ModuleCache::Module::Module() : is_valid(false) {}
ModuleCache::Module::Module(uintptr_t base_address,
const std::string& id,
const FilePath& filename)
: Module(base_address, id, filename, 0) {}
ModuleCache::Module::Module(uintptr_t base_address,
const std::string& id,
const FilePath& filename,
size_t size)
: base_address(base_address),
id(id),
filename(filename),
is_valid(true),
size(size) {}
ModuleCache::Module::~Module() = default;
ModuleCache::ModuleCache() = default;
ModuleCache::~ModuleCache() = default;
......@@ -23,7 +41,7 @@ const ModuleCache::Module& ModuleCache::GetModuleForAddress(uintptr_t address) {
return module;
}
auto module = NativeStackSampler::GetModuleForAddress(address);
auto module = CreateModuleForAddress(address);
if (!module.is_valid)
return *invalid_module;
return modules_cache_map_.emplace(module.base_address, std::move(module))
......
......@@ -6,15 +6,56 @@
#define BASE_SAMPLING_HEAP_PROFILER_MODULE_CACHE_H_
#include <map>
#include <string>
#include <vector>
#include "base/profiler/stack_sampling_profiler.h"
#include "base/base_export.h"
#include "base/files/file_path.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
namespace base {
class BASE_EXPORT ModuleCache {
public:
using Module = StackSamplingProfiler::InternalModule;
// Module represents the module (DLL or exe) and its validness state.
// This struct is used for sampling data transfer from NativeStackSampler
// to ProfileBuilder as well as by SamplingHeapProfiler.
struct BASE_EXPORT Module {
Module();
Module(uintptr_t base_address,
const std::string& id,
const FilePath& filename);
Module(uintptr_t base_address,
const std::string& id,
const FilePath& filename,
size_t size);
~Module();
// Points to the base address of the module.
uintptr_t base_address;
// An opaque binary string that uniquely identifies a particular program
// version with high probability. This is parsed from headers of the loaded
// module.
// For binaries generated by GNU tools:
// Contents of the .note.gnu.build-id field.
// On Windows:
// GUID + AGE in the debug image headers of a module.
std::string id;
// The filename of the module.
FilePath filename;
// The validness of the module.
bool is_valid;
// Size of the module.
size_t size;
};
ModuleCache();
~ModuleCache();
......@@ -23,6 +64,26 @@ class BASE_EXPORT ModuleCache {
std::vector<const Module*> GetModules() const;
private:
// TODO(alph): Refactor corresponding functions to use public API instead,
// and drop friends.
// Creates a Module object for the specified memory address. If the address
// does not belong to a module returns an invalid module.
static Module CreateModuleForAddress(uintptr_t address);
friend class NativeStackSamplerMac;
#if defined(OS_MACOSX)
// Returns the size of the _TEXT segment of the module loaded
// at |module_addr|.
static size_t GetModuleTextSize(const void* module_addr);
friend bool MayTriggerUnwInitLocalCrash(uint64_t);
#endif
#if defined(OS_WIN)
static Module CreateModuleForHandle(HMODULE module_handle);
friend class NativeStackSamplerWin;
#endif
std::map<uintptr_t, Module> modules_cache_map_;
};
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/sampling_heap_profiler/module_cache.h"
#include <dlfcn.h>
#include <mach-o/getsect.h>
#include <uuid/uuid.h>
#include "base/strings/string_number_conversions.h"
namespace base {
namespace {
// Returns the unique build ID for a module loaded at |module_addr|. Returns the
// empty string if the function fails to get the build ID.
//
// Build IDs are created by the concatenation of the module's GUID (Windows) /
// UUID (Mac) and an "age" field that indicates how many times that GUID/UUID
// has been reused. In Windows binaries, the "age" field is present in the
// module header, but on the Mac, UUIDs are never reused and so the "age" value
// appended to the UUID is always 0.
std::string GetUniqueId(const void* module_addr) {
const mach_header_64* mach_header =
reinterpret_cast<const mach_header_64*>(module_addr);
DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
size_t offset = sizeof(mach_header_64);
size_t offset_limit = sizeof(mach_header_64) + mach_header->sizeofcmds;
for (uint32_t i = 0; i < mach_header->ncmds; ++i) {
if (offset + sizeof(load_command) >= offset_limit)
return std::string();
const load_command* current_cmd = reinterpret_cast<const load_command*>(
reinterpret_cast<const uint8_t*>(mach_header) + offset);
if (offset + current_cmd->cmdsize > offset_limit) {
// This command runs off the end of the command list. This is malformed.
return std::string();
}
if (current_cmd->cmd == LC_UUID) {
if (current_cmd->cmdsize < sizeof(uuid_command)) {
// This "UUID command" is too small. This is malformed.
return std::string();
}
const uuid_command* uuid_cmd =
reinterpret_cast<const uuid_command*>(current_cmd);
static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
"UUID field of UUID command should be 16 bytes.");
// The ID is comprised of the UUID concatenated with the Mac's "age" value
// which is always 0.
return HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) + "0";
}
offset += current_cmd->cmdsize;
}
return std::string();
}
} // namespace
// static
ModuleCache::Module ModuleCache::CreateModuleForAddress(uintptr_t address) {
Dl_info inf;
if (!dladdr(reinterpret_cast<const void*>(address), &inf))
return Module();
auto base_module_address = reinterpret_cast<uintptr_t>(inf.dli_fbase);
return Module(base_module_address, GetUniqueId(inf.dli_fbase),
FilePath(inf.dli_fname), GetModuleTextSize(inf.dli_fbase));
}
size_t ModuleCache::GetModuleTextSize(const void* module_addr) {
const mach_header_64* mach_header =
reinterpret_cast<const mach_header_64*>(module_addr);
DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
unsigned long module_size;
getsegmentdata(mach_header, SEG_TEXT, &module_size);
return module_size;
}
} // namespace base
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/sampling_heap_profiler/module_cache.h"
namespace base {
// static
ModuleCache::Module ModuleCache::CreateModuleForAddress(uintptr_t address) {
// TODO(alph): Implement it.
return Module();
}
} // namespace base
......@@ -17,7 +17,7 @@ int AFunctionForTest() {
// Checks that ModuleCache returns the same module instance for
// addresses within the module.
#if defined(OS_MACOSX) && !defined(OS_IOS)
#if defined(OS_MACOSX) && !defined(OS_IOS) || defined(OS_WIN)
#define MAYBE_ModuleCache ModuleCache
#define MAYBE_ModulesList ModulesList
#else
......@@ -32,7 +32,8 @@ TEST_F(ModuleCacheTest, MAYBE_ModuleCache) {
const ModuleCache::Module& module2 = cache.GetModuleForAddress(ptr2);
EXPECT_EQ(&module1, &module2);
EXPECT_TRUE(module1.is_valid);
EXPECT_LT(module1.base_address, ptr1);
EXPECT_GT(module1.size, 0u);
EXPECT_LE(module1.base_address, ptr1);
EXPECT_GT(module1.base_address + module1.size, ptr2);
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/sampling_heap_profiler/module_cache.h"
#include <objbase.h>
#include <psapi.h>
#include "base/process/process_handle.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
namespace base {
namespace {
// Gets the unique build ID for a module. Windows build IDs are created by a
// concatenation of a GUID and AGE fields found in the headers of a module. The
// GUID is stored in the first 16 bytes and the AGE is stored in the last 4
// bytes. Returns the empty string if the function fails to get the build ID.
//
// Example:
// dumpbin chrome.exe /headers | find "Format:"
// ... Format: RSDS, {16B2A428-1DED-442E-9A36-FCE8CBD29726}, 10, ...
//
// The resulting buildID string of this instance of chrome.exe is
// "16B2A4281DED442E9A36FCE8CBD2972610".
//
// Note that the AGE field is encoded in decimal, not hex.
std::string GetBuildIDForModule(HMODULE module_handle) {
GUID guid;
DWORD age;
win::PEImage(module_handle).GetDebugId(&guid, &age, /* pdb_file= */ nullptr);
const int kGUIDSize = 39;
string16 build_id;
int result =
::StringFromGUID2(guid, WriteInto(&build_id, kGUIDSize), kGUIDSize);
if (result != kGUIDSize)
return std::string();
RemoveChars(build_id, L"{}-", &build_id);
build_id += StringPrintf(L"%d", age);
return UTF16ToUTF8(build_id);
}
} // namespace
// static
ModuleCache::Module ModuleCache::CreateModuleForAddress(uintptr_t address) {
HMODULE module_handle = nullptr;
if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<LPCTSTR>(address),
&module_handle)) {
DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(::GetLastError()));
return Module();
}
Module module = CreateModuleForHandle(module_handle);
::CloseHandle(module_handle);
return module;
}
// static
ModuleCache::Module ModuleCache::CreateModuleForHandle(HMODULE module_handle) {
wchar_t module_name[MAX_PATH];
DWORD result_length =
::GetModuleFileName(module_handle, module_name, size(module_name));
if (result_length == 0)
return Module();
const std::string& module_id = GetBuildIDForModule(module_handle);
if (module_id.empty())
return Module();
MODULEINFO module_info;
if (!::GetModuleInformation(GetCurrentProcessHandle(), module_handle,
&module_info, sizeof(module_info))) {
return Module();
}
return Module(reinterpret_cast<uintptr_t>(module_info.lpBaseOfDll), module_id,
FilePath(module_name), module_info.SizeOfImage);
}
} // namespace base
......@@ -60,8 +60,7 @@ void CallStackProfileBuilder::OnSampleCompleted(
// Dedup modules and convert InternalFrames to Frames.
for (const auto& internal_frame : internal_frames) {
const StackSamplingProfiler::InternalModule& module(
internal_frame.internal_module);
const base::ModuleCache::Module& module(internal_frame.internal_module);
if (!module.is_valid) {
sample_.frames.emplace_back(internal_frame.instruction_pointer,
base::kUnknownModuleIndex);
......
......@@ -11,7 +11,7 @@
using StackSamplingProfiler = base::StackSamplingProfiler;
using InternalFrame = StackSamplingProfiler::InternalFrame;
using InternalModule = StackSamplingProfiler::InternalModule;
using Module = base::ModuleCache::Module;
using CallStackProfile = StackSamplingProfiler::CallStackProfile;
namespace metrics {
......@@ -55,10 +55,10 @@ TEST(CallStackProfileBuilderTest, OnSampleCompleted) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2};
......@@ -81,10 +81,10 @@ TEST(CallStackProfileBuilderTest, OnProfileCompleted) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2};
......@@ -108,9 +108,9 @@ TEST(CallStackProfileBuilderTest, InvalidModule) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1;
InternalModule module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
Module module1;
Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2};
......@@ -135,10 +135,10 @@ TEST(CallStackProfileBuilderTest, DedupModules) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccccdddd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
Module module2 = {0xccccdddd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2};
......
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