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") { ...@@ -722,6 +722,8 @@ jumbo_component("base") {
"sampling_heap_profiler/lock_free_address_hash_set.h", "sampling_heap_profiler/lock_free_address_hash_set.h",
"sampling_heap_profiler/module_cache.cc", "sampling_heap_profiler/module_cache.cc",
"sampling_heap_profiler/module_cache.h", "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.cc",
"sampling_heap_profiler/sampling_heap_profiler.h", "sampling_heap_profiler/sampling_heap_profiler.h",
"scoped_clear_errno.h", "scoped_clear_errno.h",
...@@ -1173,6 +1175,7 @@ jumbo_component("base") { ...@@ -1173,6 +1175,7 @@ jumbo_component("base") {
"process/process_posix.cc", "process/process_posix.cc",
"profiler/native_stack_sampler_posix.cc", "profiler/native_stack_sampler_posix.cc",
"rand_util_posix.cc", "rand_util_posix.cc",
"sampling_heap_profiler/module_cache_posix.cc",
"strings/string_util_posix.h", "strings/string_util_posix.h",
"strings/sys_string_conversions_posix.cc", "strings/sys_string_conversions_posix.cc",
"sync_socket_posix.cc", "sync_socket_posix.cc",
...@@ -1421,6 +1424,7 @@ jumbo_component("base") { ...@@ -1421,6 +1424,7 @@ jumbo_component("base") {
"process/process_metrics_posix.cc", "process/process_metrics_posix.cc",
"profiler/native_stack_sampler_posix.cc", "profiler/native_stack_sampler_posix.cc",
"rand_util_fuchsia.cc", "rand_util_fuchsia.cc",
"sampling_heap_profiler/module_cache_posix.cc",
"strings/string_util_posix.h", "strings/string_util_posix.h",
"strings/sys_string_conversions_posix.cc", "strings/sys_string_conversions_posix.cc",
"sync_socket_posix.cc", "sync_socket_posix.cc",
...@@ -1628,7 +1632,10 @@ jumbo_component("base") { ...@@ -1628,7 +1632,10 @@ jumbo_component("base") {
# Desktop Mac. # Desktop Mac.
if (is_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 += [ sources += [
"mac/scoped_typeref.h", "mac/scoped_typeref.h",
"memory/platform_shared_memory_region_mac.cc", "memory/platform_shared_memory_region_mac.cc",
......
...@@ -56,13 +56,6 @@ class NativeStackSampler { ...@@ -56,13 +56,6 @@ class NativeStackSampler {
// any NativeStackSampler object. // any NativeStackSampler object.
static std::unique_ptr<StackBuffer> CreateStackBuffer(); 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 // The following functions are all called on the SamplingThread (not the
// thread being sampled). // thread being sampled).
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "base/mac/mach_logging.h" #include "base/mac/mach_logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
extern "C" { extern "C" {
...@@ -35,8 +36,6 @@ namespace base { ...@@ -35,8 +36,6 @@ namespace base {
using Frame = StackSamplingProfiler::Frame; using Frame = StackSamplingProfiler::Frame;
using InternalFrame = StackSamplingProfiler::InternalFrame; using InternalFrame = StackSamplingProfiler::InternalFrame;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using ProfileBuilder = StackSamplingProfiler::ProfileBuilder; using ProfileBuilder = StackSamplingProfiler::ProfileBuilder;
namespace { namespace {
...@@ -46,7 +45,7 @@ namespace { ...@@ -46,7 +45,7 @@ namespace {
struct ModuleCacheEntry { struct ModuleCacheEntry {
ModuleCacheEntry(uintptr_t start, ModuleCacheEntry(uintptr_t start,
uintptr_t end, uintptr_t end,
InternalModule internal_module) ModuleCache::Module internal_module)
: base_address(start), : base_address(start),
end_address(end), end_address(end),
internal_module(std::move(internal_module)){}; internal_module(std::move(internal_module)){};
...@@ -58,70 +57,9 @@ struct ModuleCacheEntry { ...@@ -58,70 +57,9 @@ struct ModuleCacheEntry {
uintptr_t end_address; uintptr_t end_address;
// Module information. // 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 -------------------------------------------------------------- // Stack walking --------------------------------------------------------------
// Fills |state| with |target_thread|'s context. // Fills |state| with |target_thread|'s context.
...@@ -210,6 +148,8 @@ uint32_t GetFrameOffset(int compact_unwind_info) { ...@@ -210,6 +148,8 @@ uint32_t GetFrameOffset(int compact_unwind_info) {
(((1 << __builtin_popcount(UNWIND_X86_64_RBP_FRAME_OFFSET))) - 1)); (((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 // 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. // unw_init_local. If so, the stack walk should be aborted at the leaf frame.
bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) { bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) {
...@@ -238,11 +178,13 @@ bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) { ...@@ -238,11 +178,13 @@ bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) {
vm_size_t size = sizeof(unused); vm_size_t size = sizeof(unused);
return vm_read_overwrite(current_task(), return vm_read_overwrite(current_task(),
reinterpret_cast<vm_address_t>(info.dli_fbase) + reinterpret_cast<vm_address_t>(info.dli_fbase) +
GetModuleTextSize(info.dli_fbase), ModuleCache::GetModuleTextSize(info.dli_fbase),
sizeof(unused), sizeof(unused),
reinterpret_cast<vm_address_t>(&unused), &size) != 0; reinterpret_cast<vm_address_t>(&unused), &size) != 0;
} }
namespace {
// Check if the cursor contains a valid-looking frame pointer for frame pointer // 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 // unwinds. If the stack frame has a frame pointer, stepping the cursor will
// involve indexing memory access off of that pointer. In that case, // involve indexing memory access off of that pointer. In that case,
...@@ -336,6 +278,8 @@ class ScopedSuspendThread { ...@@ -336,6 +278,8 @@ class ScopedSuspendThread {
DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread); DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread);
}; };
} // namespace
// NativeStackSamplerMac ------------------------------------------------------ // NativeStackSamplerMac ------------------------------------------------------
class NativeStackSamplerMac : public NativeStackSampler { class NativeStackSamplerMac : public NativeStackSampler {
...@@ -351,9 +295,9 @@ class NativeStackSamplerMac : public NativeStackSampler { ...@@ -351,9 +295,9 @@ class NativeStackSamplerMac : public NativeStackSampler {
ProfileBuilder* profile_builder) override; ProfileBuilder* profile_builder) override;
private: private:
// Returns the InternalModule containing |instruction_pointer|, adding it to // Returns the ModuleCache::Module containing |instruction_pointer|, adding it
// module_cache_entry_ if it's not already present. // to module_cache_entry_ if it's not already present.
InternalModule GetInternalModule(uintptr_t instruction_pointer); ModuleCache::Module GetInternalModule(uintptr_t instruction_pointer);
// Walks the stack represented by |unwind_context|, calling back to the // Walks the stack represented by |unwind_context|, calling back to the
// provided lambda for each frame. Returns false if an error occurred, // provided lambda for each frame. Returns false if an error occurred,
...@@ -488,17 +432,18 @@ std::vector<InternalFrame> NativeStackSamplerMac::RecordStackFrames( ...@@ -488,17 +432,18 @@ std::vector<InternalFrame> NativeStackSamplerMac::RecordStackFrames(
return HasValidRbp(unwind_cursor, new_stack_top); return HasValidRbp(unwind_cursor, new_stack_top);
}; };
WalkStack( WalkStack(thread_state,
thread_state, [&internal_frames](uintptr_t frame_ip,
[&internal_frames](uintptr_t frame_ip, InternalModule internal_module) { ModuleCache::Module internal_module) {
internal_frames.emplace_back(frame_ip, std::move(internal_module)); internal_frames.emplace_back(frame_ip,
}, std::move(internal_module));
continue_predicate); },
continue_predicate);
return internal_frames; return internal_frames;
} }
InternalModule NativeStackSamplerMac::GetInternalModule( ModuleCache::Module NativeStackSamplerMac::GetInternalModule(
uintptr_t instruction_pointer) { uintptr_t instruction_pointer) {
// Check if |instruction_pointer| is in the address range of a module we've // Check if |instruction_pointer| is in the address range of a module we've
// already seen. // already seen.
...@@ -511,7 +456,10 @@ InternalModule NativeStackSamplerMac::GetInternalModule( ...@@ -511,7 +456,10 @@ InternalModule NativeStackSamplerMac::GetInternalModule(
if (loc != module_cache_entry_.end()) if (loc != module_cache_entry_.end())
return loc->internal_module; 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_cache_entry_.emplace_back(module.base_address,
module.base_address + module.size, module); module.base_address + module.size, module);
return module; return module;
...@@ -543,7 +491,7 @@ bool NativeStackSamplerMac::WalkStackFromContext( ...@@ -543,7 +491,7 @@ bool NativeStackSamplerMac::WalkStackFromContext(
// libunwind adds the expected stack size, it will look for the return // 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 // 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. // 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) if (!internal_module.is_valid)
return false; return false;
...@@ -608,8 +556,6 @@ void NativeStackSamplerMac::WalkStack( ...@@ -608,8 +556,6 @@ void NativeStackSamplerMac::WalkStack(
} }
} }
} // namespace
// NativeStackSampler --------------------------------------------------------- // NativeStackSampler ---------------------------------------------------------
// static // static
...@@ -619,18 +565,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create( ...@@ -619,18 +565,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::make_unique<NativeStackSamplerMac>(thread_id, test_delegate); 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 // static
size_t NativeStackSampler::GetStackBufferSize() { size_t NativeStackSampler::GetStackBufferSize() {
// In platform_thread_mac's GetDefaultThreadStackSize(), RLIMIT_STACK is used // In platform_thread_mac's GetDefaultThreadStackSize(), RLIMIT_STACK is used
......
...@@ -12,13 +12,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create( ...@@ -12,13 +12,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::unique_ptr<NativeStackSampler>(); return std::unique_ptr<NativeStackSampler>();
} }
// static
StackSamplingProfiler::InternalModule NativeStackSampler::GetModuleForAddress(
uintptr_t address) {
// TODO(alph): Implement it.
return StackSamplingProfiler::InternalModule();
}
size_t NativeStackSampler::GetStackBufferSize() { size_t NativeStackSampler::GetStackBufferSize() {
return 0; return 0;
} }
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include <windows.h> #include <windows.h>
#include <objbase.h>
#include <stddef.h> #include <stddef.h>
#include <winternl.h> #include <winternl.h>
...@@ -21,21 +20,14 @@ ...@@ -21,21 +20,14 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/profiler/win32_stack_frame_unwinder.h" #include "base/profiler/win32_stack_frame_unwinder.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/stl_util.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/time/time.h"
#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
namespace base { namespace base {
using Frame = StackSamplingProfiler::Frame; using Frame = StackSamplingProfiler::Frame;
using InternalFrame = StackSamplingProfiler::InternalFrame; using InternalFrame = StackSamplingProfiler::InternalFrame;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using ProfileBuilder = StackSamplingProfiler::ProfileBuilder; using ProfileBuilder = StackSamplingProfiler::ProfileBuilder;
// Stack recording functions -------------------------------------------------- // Stack recording functions --------------------------------------------------
...@@ -203,34 +195,6 @@ void RecordStack(CONTEXT* context, std::vector<RecordedFrame>* stack) { ...@@ -203,34 +195,6 @@ void RecordStack(CONTEXT* context, std::vector<RecordedFrame>* stack) {
#endif #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 ------------------------------------------------- // ScopedDisablePriorityBoost -------------------------------------------------
// Disables priority boost on a thread for the lifetime of the object. // Disables priority boost on a thread for the lifetime of the object.
...@@ -382,6 +346,8 @@ void SuspendThreadAndRecordStack( ...@@ -382,6 +346,8 @@ void SuspendThreadAndRecordStack(
RecordStack(&thread_context, stack); RecordStack(&thread_context, stack);
} }
} // namespace
// NativeStackSamplerWin ------------------------------------------------------ // NativeStackSamplerWin ------------------------------------------------------
class NativeStackSamplerWin : public NativeStackSampler { class NativeStackSamplerWin : public NativeStackSampler {
...@@ -397,10 +363,6 @@ class NativeStackSamplerWin : public NativeStackSampler { ...@@ -397,10 +363,6 @@ class NativeStackSamplerWin : public NativeStackSampler {
ProfileBuilder* profile_builder) override; ProfileBuilder* profile_builder) override;
private: 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 // Creates a set of internal frames with the information represented by
// |stack|. // |stack|.
std::vector<InternalFrame> CreateInternalFrames( std::vector<InternalFrame> CreateInternalFrames(
...@@ -414,7 +376,7 @@ class NativeStackSamplerWin : public NativeStackSampler { ...@@ -414,7 +376,7 @@ class NativeStackSamplerWin : public NativeStackSampler {
const void* const thread_stack_base_address_; const void* const thread_stack_base_address_;
// The internal module objects, indexed by the module handle. // 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); DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin);
}; };
...@@ -446,23 +408,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::RecordStackFrames( ...@@ -446,23 +408,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::RecordStackFrames(
return CreateInternalFrames(stack); 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( std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
const std::vector<RecordedFrame>& stack) { const std::vector<RecordedFrame>& stack) {
std::vector<InternalFrame> internal_frames; std::vector<InternalFrame> internal_frames;
...@@ -473,7 +418,7 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames( ...@@ -473,7 +418,7 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
HMODULE module_handle = frame.module.Get(); HMODULE module_handle = frame.module.Get();
if (!module_handle) { if (!module_handle) {
internal_frames.emplace_back(frame_ip, InternalModule()); internal_frames.emplace_back(frame_ip, ModuleCache::Module());
continue; continue;
} }
...@@ -483,7 +428,8 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames( ...@@ -483,7 +428,8 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
continue; continue;
} }
InternalModule internal_module = GetModuleForHandle(module_handle); ModuleCache::Module internal_module =
ModuleCache::CreateModuleForHandle(module_handle);
if (internal_module.is_valid) if (internal_module.is_valid)
module_cache_.insert(std::make_pair(module_handle, internal_module)); module_cache_.insert(std::make_pair(module_handle, internal_module));
...@@ -493,8 +439,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames( ...@@ -493,8 +439,6 @@ std::vector<InternalFrame> NativeStackSamplerWin::CreateInternalFrames(
return internal_frames; return internal_frames;
} }
} // namespace
// NativeStackSampler --------------------------------------------------------- // NativeStackSampler ---------------------------------------------------------
// static // static
...@@ -515,13 +459,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create( ...@@ -515,13 +459,6 @@ std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
return std::unique_ptr<NativeStackSampler>(); return std::unique_ptr<NativeStackSampler>();
} }
// static
StackSamplingProfiler::InternalModule NativeStackSampler::GetModuleForAddress(
uintptr_t address) {
// TODO(alph): Implement it.
return StackSamplingProfiler::InternalModule();
}
// static // static
size_t NativeStackSampler::GetStackBufferSize() { size_t NativeStackSampler::GetStackBufferSize() {
// The default Win32 reserved stack size is 1 MB and Chrome Windows threads // The default Win32 reserved stack size is 1 MB and Chrome Windows threads
......
...@@ -52,27 +52,6 @@ StackSamplingProfiler::Module::Module(uintptr_t base_address, ...@@ -52,27 +52,6 @@ StackSamplingProfiler::Module::Module(uintptr_t base_address,
StackSamplingProfiler::Module::~Module() = default; 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 -----------------------------------------------
StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer, StackSamplingProfiler::Frame::Frame(uintptr_t instruction_pointer,
...@@ -88,7 +67,7 @@ StackSamplingProfiler::Frame::Frame() ...@@ -88,7 +67,7 @@ StackSamplingProfiler::Frame::Frame()
StackSamplingProfiler::InternalFrame::InternalFrame( StackSamplingProfiler::InternalFrame::InternalFrame(
uintptr_t instruction_pointer, uintptr_t instruction_pointer,
InternalModule internal_module) ModuleCache::Module internal_module)
: instruction_pointer(instruction_pointer), : instruction_pointer(instruction_pointer),
internal_module(std::move(internal_module)) {} internal_module(std::move(internal_module)) {}
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/base_export.h" #include "base/base_export.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
...@@ -80,44 +81,6 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -80,44 +81,6 @@ class BASE_EXPORT StackSamplingProfiler {
FilePath filename; 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. // Frame represents an individual sampled stack frame with module information.
struct BASE_EXPORT Frame { struct BASE_EXPORT Frame {
Frame(uintptr_t instruction_pointer, size_t module_index); Frame(uintptr_t instruction_pointer, size_t module_index);
...@@ -141,14 +104,14 @@ class BASE_EXPORT StackSamplingProfiler { ...@@ -141,14 +104,14 @@ class BASE_EXPORT StackSamplingProfiler {
// to ProfileBuilder. // to ProfileBuilder.
struct BASE_EXPORT InternalFrame { struct BASE_EXPORT InternalFrame {
InternalFrame(uintptr_t instruction_pointer, InternalFrame(uintptr_t instruction_pointer,
InternalModule internal_module); ModuleCache::Module internal_module);
~InternalFrame(); ~InternalFrame();
// The sampled instruction pointer within the function. // The sampled instruction pointer within the function.
uintptr_t instruction_pointer; uintptr_t instruction_pointer;
// The module information. // The module information.
InternalModule internal_module; ModuleCache::Module internal_module;
}; };
// Sample represents a set of stack frames with some extra information. // Sample represents a set of stack frames with some extra information.
......
...@@ -68,9 +68,6 @@ using Frames = std::vector<Frame>; ...@@ -68,9 +68,6 @@ using Frames = std::vector<Frame>;
using InternalFrame = StackSamplingProfiler::InternalFrame; using InternalFrame = StackSamplingProfiler::InternalFrame;
using InternalFrames = std::vector<InternalFrame>; using InternalFrames = std::vector<InternalFrame>;
using InternalFrameSets = std::vector<std::vector<InternalFrame>>; using InternalFrameSets = std::vector<std::vector<InternalFrame>>;
using Module = StackSamplingProfiler::Module;
using InternalModule = StackSamplingProfiler::InternalModule;
using Sample = StackSamplingProfiler::Sample;
namespace { namespace {
......
...@@ -119,6 +119,8 @@ Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {} ...@@ -119,6 +119,8 @@ Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context, bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
ScopedModuleHandle* module) { ScopedModuleHandle* module) {
#ifdef _WIN64 #ifdef _WIN64
// TODO(chengx): update base::ModuleCache to return a ScopedModuleHandle and
// use it for this module lookup.
ScopedModuleHandle frame_module = ScopedModuleHandle frame_module =
unwind_functions_->GetModuleForProgramCounter(context->Rip); unwind_functions_->GetModuleForProgramCounter(context->Rip);
if (!frame_module.IsValid()) { if (!frame_module.IsValid()) {
......
...@@ -5,10 +5,28 @@ ...@@ -5,10 +5,28 @@
#include "base/sampling_heap_profiler/module_cache.h" #include "base/sampling_heap_profiler/module_cache.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
#include "base/profiler/native_stack_sampler.h"
namespace base { 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;
ModuleCache::~ModuleCache() = default; ModuleCache::~ModuleCache() = default;
...@@ -23,7 +41,7 @@ const ModuleCache::Module& ModuleCache::GetModuleForAddress(uintptr_t address) { ...@@ -23,7 +41,7 @@ const ModuleCache::Module& ModuleCache::GetModuleForAddress(uintptr_t address) {
return module; return module;
} }
auto module = NativeStackSampler::GetModuleForAddress(address); auto module = CreateModuleForAddress(address);
if (!module.is_valid) if (!module.is_valid)
return *invalid_module; return *invalid_module;
return modules_cache_map_.emplace(module.base_address, std::move(module)) return modules_cache_map_.emplace(module.base_address, std::move(module))
......
...@@ -6,15 +6,56 @@ ...@@ -6,15 +6,56 @@
#define BASE_SAMPLING_HEAP_PROFILER_MODULE_CACHE_H_ #define BASE_SAMPLING_HEAP_PROFILER_MODULE_CACHE_H_
#include <map> #include <map>
#include <string>
#include <vector> #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 { namespace base {
class BASE_EXPORT ModuleCache { class BASE_EXPORT ModuleCache {
public: 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();
~ModuleCache(); ~ModuleCache();
...@@ -23,6 +64,26 @@ class BASE_EXPORT ModuleCache { ...@@ -23,6 +64,26 @@ class BASE_EXPORT ModuleCache {
std::vector<const Module*> GetModules() const; std::vector<const Module*> GetModules() const;
private: 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_; 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() { ...@@ -17,7 +17,7 @@ int AFunctionForTest() {
// Checks that ModuleCache returns the same module instance for // Checks that ModuleCache returns the same module instance for
// addresses within the module. // 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_ModuleCache ModuleCache
#define MAYBE_ModulesList ModulesList #define MAYBE_ModulesList ModulesList
#else #else
...@@ -32,7 +32,8 @@ TEST_F(ModuleCacheTest, MAYBE_ModuleCache) { ...@@ -32,7 +32,8 @@ TEST_F(ModuleCacheTest, MAYBE_ModuleCache) {
const ModuleCache::Module& module2 = cache.GetModuleForAddress(ptr2); const ModuleCache::Module& module2 = cache.GetModuleForAddress(ptr2);
EXPECT_EQ(&module1, &module2); EXPECT_EQ(&module1, &module2);
EXPECT_TRUE(module1.is_valid); 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); 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( ...@@ -60,8 +60,7 @@ void CallStackProfileBuilder::OnSampleCompleted(
// Dedup modules and convert InternalFrames to Frames. // Dedup modules and convert InternalFrames to Frames.
for (const auto& internal_frame : internal_frames) { for (const auto& internal_frame : internal_frames) {
const StackSamplingProfiler::InternalModule& module( const base::ModuleCache::Module& module(internal_frame.internal_module);
internal_frame.internal_module);
if (!module.is_valid) { if (!module.is_valid) {
sample_.frames.emplace_back(internal_frame.instruction_pointer, sample_.frames.emplace_back(internal_frame.instruction_pointer,
base::kUnknownModuleIndex); base::kUnknownModuleIndex);
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
using StackSamplingProfiler = base::StackSamplingProfiler; using StackSamplingProfiler = base::StackSamplingProfiler;
using InternalFrame = StackSamplingProfiler::InternalFrame; using InternalFrame = StackSamplingProfiler::InternalFrame;
using InternalModule = StackSamplingProfiler::InternalModule; using Module = base::ModuleCache::Module;
using CallStackProfile = StackSamplingProfiler::CallStackProfile; using CallStackProfile = StackSamplingProfiler::CallStackProfile;
namespace metrics { namespace metrics {
...@@ -55,10 +55,10 @@ TEST(CallStackProfileBuilderTest, OnSampleCompleted) { ...@@ -55,10 +55,10 @@ TEST(CallStackProfileBuilderTest, OnSampleCompleted) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile))); Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1", Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))}; base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccddccdd, "2", Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))}; base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1}; InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2}; InternalFrame frame2 = {0xaabbaabb, module2};
...@@ -81,10 +81,10 @@ TEST(CallStackProfileBuilderTest, OnProfileCompleted) { ...@@ -81,10 +81,10 @@ TEST(CallStackProfileBuilderTest, OnProfileCompleted) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile))); Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1", Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))}; base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccddccdd, "2", Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))}; base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1}; InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2}; InternalFrame frame2 = {0xaabbaabb, module2};
...@@ -108,9 +108,9 @@ TEST(CallStackProfileBuilderTest, InvalidModule) { ...@@ -108,9 +108,9 @@ TEST(CallStackProfileBuilderTest, InvalidModule) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile))); Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1; Module module1;
InternalModule module2 = {0xccddccdd, "2", Module module2 = {0xccddccdd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))}; base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1}; InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2}; InternalFrame frame2 = {0xaabbaabb, module2};
...@@ -135,10 +135,10 @@ TEST(CallStackProfileBuilderTest, DedupModules) { ...@@ -135,10 +135,10 @@ TEST(CallStackProfileBuilderTest, DedupModules) {
auto profile_builder = std::make_unique<CallStackProfileBuilder>( auto profile_builder = std::make_unique<CallStackProfileBuilder>(
Bind(&SaveProfile, Unretained(&profile))); Bind(&SaveProfile, Unretained(&profile)));
InternalModule module1 = {0xccccdddd, "1", Module module1 = {0xccccdddd, "1",
base::FilePath(FILE_PATH_LITERAL("file_path_1"))}; base::FilePath(FILE_PATH_LITERAL("file_path_1"))};
InternalModule module2 = {0xccccdddd, "2", Module module2 = {0xccccdddd, "2",
base::FilePath(FILE_PATH_LITERAL("file_path_2"))}; base::FilePath(FILE_PATH_LITERAL("file_path_2"))};
InternalFrame frame1 = {0xaaaabbbb, module1}; InternalFrame frame1 = {0xaaaabbbb, module1};
InternalFrame frame2 = {0xaabbaabb, module2}; 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