Commit 815054b5 authored by bcwhite's avatar bcwhite Committed by Commit bot

Multi-Process Tracking Support

Fully support having multiple processes write information to the same breadcrumbs file.

This involves some process-specific operations plus the ability for a controlling process to clean-up after its dead children.

BUG=620813
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.win:win10_chromium_x64_rel_ng

Review-Url: https://codereview.chromium.org/2680123003
Cr-Commit-Position: refs/heads/master@{#457502}
parent 42561dc4
......@@ -23,6 +23,7 @@
#include "base/process/process_handle.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
namespace base {
......@@ -30,18 +31,13 @@ namespace debug {
namespace {
// A number that identifies the memory as having been initialized. It's
// arbitrary but happens to be the first 4 bytes of SHA1(ThreadActivityTracker).
// A version number is added on so that major structure changes won't try to
// read an older version (since the cookie won't match).
const uint32_t kHeaderCookie = 0xC0029B24UL + 2; // v2
// The minimum depth a stack should support.
const int kMinStackDepth = 2;
// The amount of memory set aside for holding arbitrary user data (key/value
// pairs) globally or associated with ActivityData entries.
const size_t kUserDataSize = 1 << 10; // 1 KiB
const size_t kProcessDataSize = 4 << 10; // 4 KiB
const size_t kGlobalDataSize = 16 << 10; // 16 KiB
const size_t kMaxUserDataNameLength =
static_cast<size_t>(std::numeric_limits<uint8_t>::max());
......@@ -49,6 +45,13 @@ const size_t kMaxUserDataNameLength =
// A constant used to indicate that module information is changing.
const uint32_t kModuleInformationChanging = 0x80000000;
// The key used to record process information.
const char kProcessPhaseDataKey[] = "process-phase";
// An atomically incrementing number, used to check for recreations of objects
// in the same memory space.
StaticAtomicSequenceNumber g_next_id;
union ThreadRef {
int64_t as_id;
#if defined(OS_WIN)
......@@ -64,6 +67,33 @@ union ThreadRef {
#endif
};
// Get the next non-zero identifier. It is only unique within a process.
uint32_t GetNextDataId() {
uint32_t id;
while ((id = g_next_id.GetNext()) == 0)
;
return id;
}
// Finds and reuses a specific allocation or creates a new one.
PersistentMemoryAllocator::Reference AllocateFrom(
PersistentMemoryAllocator* allocator,
uint32_t from_type,
size_t size,
uint32_t to_type) {
PersistentMemoryAllocator::Iterator iter(allocator);
PersistentMemoryAllocator::Reference ref;
while ((ref = iter.GetNextOfType(from_type)) != 0) {
DCHECK_LE(size, allocator->GetAllocSize(ref));
// This can fail if a another thread has just taken it. It is assumed that
// the memory is cleared during the "free" operation.
if (allocator->ChangeType(ref, to_type, from_type, /*clear=*/false))
return ref;
}
return allocator->Allocate(size, to_type);
}
// Determines the previous aligned index.
size_t RoundDownToAlignment(size_t index, size_t alignment) {
return index & (0 - alignment);
......@@ -76,6 +106,36 @@ size_t RoundUpToAlignment(size_t index, size_t alignment) {
} // namespace
OwningProcess::OwningProcess() {}
OwningProcess::~OwningProcess() {}
void OwningProcess::Release_Initialize() {
uint32_t old_id = data_id.load(std::memory_order_acquire);
DCHECK_EQ(0U, old_id);
process_id = GetCurrentProcId();
create_stamp = Time::Now().ToInternalValue();
data_id.store(GetNextDataId(), std::memory_order_release);
}
void OwningProcess::SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp) {
DCHECK_NE(0U, data_id);
process_id = pid;
create_stamp = stamp;
}
// static
bool OwningProcess::GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp) {
const OwningProcess* info = reinterpret_cast<const OwningProcess*>(memory);
uint32_t id = info->data_id.load(std::memory_order_acquire);
if (id == 0)
return false;
*out_id = static_cast<ProcessId>(info->process_id);
*out_stamp = info->create_stamp;
return id == info->data_id.load(std::memory_order_seq_cst);
}
// It doesn't matter what is contained in this (though it will be all zeros)
// as only the address of it is important.
......@@ -246,32 +306,31 @@ StringPiece ActivityUserData::TypedValue::GetStringReference() const {
return ref_value_;
}
// These are required because std::atomic is (currently) not a POD type and
// thus clang requires explicit out-of-line constructors and destructors even
// when they do nothing.
ActivityUserData::ValueInfo::ValueInfo() {}
ActivityUserData::ValueInfo::ValueInfo(ValueInfo&&) = default;
ActivityUserData::ValueInfo::~ValueInfo() {}
StaticAtomicSequenceNumber ActivityUserData::next_id_;
ActivityUserData::MemoryHeader::MemoryHeader() {}
ActivityUserData::MemoryHeader::~MemoryHeader() {}
ActivityUserData::FieldHeader::FieldHeader() {}
ActivityUserData::FieldHeader::~FieldHeader() {}
ActivityUserData::ActivityUserData(void* memory, size_t size)
: memory_(reinterpret_cast<char*>(memory)),
available_(RoundDownToAlignment(size, kMemoryAlignment)),
id_(reinterpret_cast<std::atomic<uint32_t>*>(memory)) {
header_(reinterpret_cast<MemoryHeader*>(memory)) {
// It's possible that no user data is being stored.
if (!memory_)
return;
DCHECK_LT(kMemoryAlignment, available_);
if (id_->load(std::memory_order_relaxed) == 0) {
// Generate a new ID and store it in the first 32-bit word of memory_.
// |id_| must be non-zero for non-sink instances.
uint32_t id;
while ((id = next_id_.GetNext()) == 0)
;
id_->store(id, std::memory_order_relaxed);
DCHECK_NE(0U, id_->load(std::memory_order_relaxed));
}
memory_ += kMemoryAlignment;
available_ -= kMemoryAlignment;
static_assert(0 == sizeof(MemoryHeader) % kMemoryAlignment, "invalid header");
DCHECK_LT(sizeof(MemoryHeader), available_);
if (header_->owner.data_id.load(std::memory_order_acquire) == 0)
header_->owner.Release_Initialize();
memory_ += sizeof(MemoryHeader);
available_ -= sizeof(MemoryHeader);
// If there is already data present, load that. This allows the same class
// to be used for analysis through snapshots.
......@@ -280,6 +339,75 @@ ActivityUserData::ActivityUserData(void* memory, size_t size)
ActivityUserData::~ActivityUserData() {}
bool ActivityUserData::CreateSnapshot(Snapshot* output_snapshot) const {
DCHECK(output_snapshot);
DCHECK(output_snapshot->empty());
// Find any new data that may have been added by an active instance of this
// class that is adding records.
ImportExistingData();
for (const auto& entry : values_) {
TypedValue value;
value.type_ = entry.second.type;
DCHECK_GE(entry.second.extent,
entry.second.size_ptr->load(std::memory_order_relaxed));
switch (entry.second.type) {
case RAW_VALUE:
case STRING_VALUE:
value.long_value_ =
std::string(reinterpret_cast<char*>(entry.second.memory),
entry.second.size_ptr->load(std::memory_order_relaxed));
break;
case RAW_VALUE_REFERENCE:
case STRING_VALUE_REFERENCE: {
ReferenceRecord* ref =
reinterpret_cast<ReferenceRecord*>(entry.second.memory);
value.ref_value_ = StringPiece(
reinterpret_cast<char*>(static_cast<uintptr_t>(ref->address)),
static_cast<size_t>(ref->size));
} break;
case BOOL_VALUE:
case CHAR_VALUE:
value.short_value_ = *reinterpret_cast<char*>(entry.second.memory);
break;
case SIGNED_VALUE:
case UNSIGNED_VALUE:
value.short_value_ = *reinterpret_cast<uint64_t*>(entry.second.memory);
break;
case END_OF_VALUES: // Included for completeness purposes.
NOTREACHED();
}
auto inserted = output_snapshot->insert(
std::make_pair(entry.second.name.as_string(), std::move(value)));
DCHECK(inserted.second); // True if inserted, false if existed.
}
return true;
}
const void* ActivityUserData::GetBaseAddress() const {
// The |memory_| pointer advances as elements are written but the |header_|
// value is always at the start of the block so just return that.
return header_;
}
void ActivityUserData::SetOwningProcessIdForTesting(ProcessId pid,
int64_t stamp) {
if (!header_)
return;
header_->owner.SetOwningProcessIdForTesting(pid, stamp);
}
// static
bool ActivityUserData::GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp) {
const MemoryHeader* header = reinterpret_cast<const MemoryHeader*>(memory);
return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp);
}
void ActivityUserData::Set(StringPiece name,
ValueType type,
const void* memory,
......@@ -308,13 +436,13 @@ void ActivityUserData::Set(StringPiece name,
// following field will be aligned properly.
size_t name_size = name.length();
size_t name_extent =
RoundUpToAlignment(sizeof(Header) + name_size, kMemoryAlignment) -
sizeof(Header);
RoundUpToAlignment(sizeof(FieldHeader) + name_size, kMemoryAlignment) -
sizeof(FieldHeader);
size_t value_extent = RoundUpToAlignment(size, kMemoryAlignment);
// The "base size" is the size of the header and (padded) string key. Stop
// now if there's not room enough for even this.
size_t base_size = sizeof(Header) + name_extent;
size_t base_size = sizeof(FieldHeader) + name_extent;
if (base_size > available_)
return;
......@@ -338,7 +466,7 @@ void ActivityUserData::Set(StringPiece name,
}
// Allocate a chunk of memory.
Header* header = reinterpret_cast<Header*>(memory_);
FieldHeader* header = reinterpret_cast<FieldHeader*>(memory_);
memory_ += full_size;
available_ -= full_size;
......@@ -348,9 +476,9 @@ void ActivityUserData::Set(StringPiece name,
DCHECK_EQ(0, header->value_size.load(std::memory_order_relaxed));
header->name_size = static_cast<uint8_t>(name_size);
header->record_size = full_size;
char* name_memory = reinterpret_cast<char*>(header) + sizeof(Header);
char* name_memory = reinterpret_cast<char*>(header) + sizeof(FieldHeader);
void* value_memory =
reinterpret_cast<char*>(header) + sizeof(Header) + name_extent;
reinterpret_cast<char*>(header) + sizeof(FieldHeader) + name_extent;
memcpy(name_memory, name.data(), name_size);
header->type.store(type, std::memory_order_release);
......@@ -364,7 +492,7 @@ void ActivityUserData::Set(StringPiece name,
info->name = persistent_name;
info->memory = value_memory;
info->size_ptr = &header->value_size;
info->extent = full_size - sizeof(Header) - name_extent;
info->extent = full_size - sizeof(FieldHeader) - name_extent;
info->type = type;
}
......@@ -389,8 +517,8 @@ void ActivityUserData::SetReference(StringPiece name,
}
void ActivityUserData::ImportExistingData() const {
while (available_ > sizeof(Header)) {
Header* header = reinterpret_cast<Header*>(memory_);
while (available_ > sizeof(FieldHeader)) {
FieldHeader* header = reinterpret_cast<FieldHeader*>(memory_);
ValueType type =
static_cast<ValueType>(header->type.load(std::memory_order_acquire));
if (type == END_OF_VALUES)
......@@ -398,8 +526,8 @@ void ActivityUserData::ImportExistingData() const {
if (header->record_size > available_)
return;
size_t value_offset = RoundUpToAlignment(sizeof(Header) + header->name_size,
kMemoryAlignment);
size_t value_offset = RoundUpToAlignment(
sizeof(FieldHeader) + header->name_size, kMemoryAlignment);
if (header->record_size == value_offset &&
header->value_size.load(std::memory_order_relaxed) == 1) {
value_offset -= 1;
......@@ -408,7 +536,7 @@ void ActivityUserData::ImportExistingData() const {
return;
ValueInfo info;
info.name = StringPiece(memory_ + sizeof(Header), header->name_size);
info.name = StringPiece(memory_ + sizeof(FieldHeader), header->name_size);
info.type = type;
info.memory = memory_ + value_offset;
info.size_ptr = &header->value_size;
......@@ -422,60 +550,6 @@ void ActivityUserData::ImportExistingData() const {
}
}
bool ActivityUserData::CreateSnapshot(Snapshot* output_snapshot) const {
DCHECK(output_snapshot);
DCHECK(output_snapshot->empty());
// Find any new data that may have been added by an active instance of this
// class that is adding records.
ImportExistingData();
for (const auto& entry : values_) {
TypedValue value;
value.type_ = entry.second.type;
DCHECK_GE(entry.second.extent,
entry.second.size_ptr->load(std::memory_order_relaxed));
switch (entry.second.type) {
case RAW_VALUE:
case STRING_VALUE:
value.long_value_ =
std::string(reinterpret_cast<char*>(entry.second.memory),
entry.second.size_ptr->load(std::memory_order_relaxed));
break;
case RAW_VALUE_REFERENCE:
case STRING_VALUE_REFERENCE: {
ReferenceRecord* ref =
reinterpret_cast<ReferenceRecord*>(entry.second.memory);
value.ref_value_ = StringPiece(
reinterpret_cast<char*>(static_cast<uintptr_t>(ref->address)),
static_cast<size_t>(ref->size));
} break;
case BOOL_VALUE:
case CHAR_VALUE:
value.short_value_ = *reinterpret_cast<char*>(entry.second.memory);
break;
case SIGNED_VALUE:
case UNSIGNED_VALUE:
value.short_value_ = *reinterpret_cast<uint64_t*>(entry.second.memory);
break;
case END_OF_VALUES: // Included for completeness purposes.
NOTREACHED();
}
auto inserted = output_snapshot->insert(
std::make_pair(entry.second.name.as_string(), std::move(value)));
DCHECK(inserted.second); // True if inserted, false if existed.
}
return true;
}
const void* ActivityUserData::GetBaseAddress() {
// The |memory_| pointer advances as elements are written but the |id_|
// value is always at the start of the block so just return that.
return id_;
}
// This information is kept for every thread that is tracked. It is filled
// the very first time the thread is seen. All fields must be of exact sizes
// so there is no issue moving between 32 and 64-bit builds.
......@@ -485,27 +559,15 @@ struct ThreadActivityTracker::Header {
GlobalActivityTracker::kTypeIdActivityTracker;
// Expected size for 32/64-bit check.
static constexpr size_t kExpectedInstanceSize = 80;
static constexpr size_t kExpectedInstanceSize =
OwningProcess::kExpectedInstanceSize + 72;
// This unique number indicates a valid initialization of the memory.
std::atomic<uint32_t> cookie;
// The number of Activity slots (spaces that can hold an Activity) that
// immediately follow this structure in memory.
uint32_t stack_slots;
// This information uniquely identifies a process.
OwningProcess owner;
// The process-id and thread-id (thread_ref.as_id) to which this data belongs.
// These identifiers are not guaranteed to mean anything but are unique, in
// combination, among all active trackers. It would be nice to always have
// the process_id be a 64-bit value but the necessity of having it atomic
// (for the memory barriers it provides) limits it to the natural word size
// of the machine.
#ifdef ARCH_CPU_64_BITS
std::atomic<int64_t> process_id;
#else
std::atomic<int32_t> process_id;
int32_t process_id_padding;
#endif
// The thread-id (thread_ref.as_id) to which this data belongs. This number
// is not guaranteed to mean anything but combined with the process-id from
// OwningProcess is unique among all active trackers.
ThreadRef thread_ref;
// The start-time and start-ticks when the data was created. Each activity
......@@ -514,6 +576,13 @@ struct ThreadActivityTracker::Header {
int64_t start_time;
int64_t start_ticks;
// The number of Activity slots (spaces that can hold an Activity) that
// immediately follow this structure in memory.
uint32_t stack_slots;
// Some padding to keep everything 64-bit aligned.
uint32_t padding;
// The current depth of the stack. This may be greater than the number of
// slots. If the depth exceeds the number of slots, the newest entries
// won't be recorded.
......@@ -596,9 +665,10 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
"ActivityData.data is not 64-bit aligned");
// Provided memory should either be completely initialized or all zeros.
if (header_->cookie.load(std::memory_order_relaxed) == 0) {
if (header_->owner.data_id.load(std::memory_order_relaxed) == 0) {
// This is a new file. Double-check other fields and then initialize.
DCHECK_EQ(0, header_->process_id.load(std::memory_order_relaxed));
DCHECK_EQ(0, header_->owner.process_id);
DCHECK_EQ(0, header_->owner.create_stamp);
DCHECK_EQ(0, header_->thread_ref.as_id);
DCHECK_EQ(0, header_->start_time);
DCHECK_EQ(0, header_->start_ticks);
......@@ -616,7 +686,6 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
header_->thread_ref.as_handle =
PlatformThread::CurrentHandle().platform_handle();
#endif
header_->process_id.store(GetCurrentProcId(), std::memory_order_relaxed);
header_->start_time = base::Time::Now().ToInternalValue();
header_->start_ticks = base::TimeTicks::Now().ToInternalValue();
......@@ -626,7 +695,7 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
// This is done last so as to guarantee that everything above is "released"
// by the time this value gets written.
header_->cookie.store(kHeaderCookie, std::memory_order_release);
header_->owner.Release_Initialize();
valid_ = true;
DCHECK(IsValid());
......@@ -771,11 +840,9 @@ void ThreadActivityTracker::ReleaseUserData(
}
bool ThreadActivityTracker::IsValid() const {
if (header_->cookie.load(std::memory_order_acquire) != kHeaderCookie ||
header_->process_id.load(std::memory_order_relaxed) == 0 ||
header_->thread_ref.as_id == 0 ||
header_->start_time == 0 ||
header_->start_ticks == 0 ||
if (header_->owner.data_id.load(std::memory_order_acquire) == 0 ||
header_->owner.process_id == 0 || header_->thread_ref.as_id == 0 ||
header_->start_time == 0 || header_->start_ticks == 0 ||
header_->stack_slots != stack_slots_ ||
header_->thread_name[sizeof(header_->thread_name) - 1] != '\0') {
return false;
......@@ -806,12 +873,12 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
output_snapshot->activity_stack.reserve(stack_slots_);
for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
// Remember the process and thread IDs to ensure they aren't replaced
// during the snapshot operation. Use "acquire" to ensure that all the
// non-atomic fields of the structure are valid (at least at the current
// moment in time).
const int64_t starting_process_id =
header_->process_id.load(std::memory_order_acquire);
// Remember the data IDs to ensure nothing is replaced during the snapshot
// operation. Use "acquire" so that all the non-atomic fields of the
// structure are valid (at least at the current moment in time).
const uint32_t starting_id =
header_->owner.data_id.load(std::memory_order_acquire);
const int64_t starting_process_id = header_->owner.process_id;
const int64_t starting_thread_id = header_->thread_ref.as_id;
// Write a non-zero value to |stack_unchanged| so it's possible to detect
......@@ -841,19 +908,11 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
// TODO(bcwhite): Snapshot other things here.
// Get the general thread information. Loading of "process_id" is guaranteed
// to be last so that it's possible to detect below if any content has
// changed while reading it. It's technically possible for a thread to end,
// have its data cleared, a new thread get created with the same IDs, and
// it perform an action which starts tracking all in the time since the
// ID reads above but the chance is so unlikely that it's not worth the
// effort and complexity of protecting against it (perhaps with an
// "unchanged" field like is done for the stack).
// Get the general thread information.
output_snapshot->thread_name =
std::string(header_->thread_name, sizeof(header_->thread_name) - 1);
output_snapshot->thread_id = header_->thread_ref.as_id;
output_snapshot->process_id =
header_->process_id.load(std::memory_order_seq_cst);
output_snapshot->process_id = header_->owner.process_id;
// All characters of the thread-name buffer were copied so as to not break
// if the trailing NUL were missing. Now limit the length if the actual
......@@ -861,9 +920,10 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
output_snapshot->thread_name.resize(
strlen(output_snapshot->thread_name.c_str()));
// If the process or thread ID has changed then the tracker has exited and
// the memory reused by a new one. Try again.
if (output_snapshot->process_id != starting_process_id ||
// If the data ID has changed then the tracker has exited and the memory
// reused by a new one. Try again.
if (header_->owner.data_id.load(std::memory_order_seq_cst) != starting_id ||
output_snapshot->process_id != starting_process_id ||
output_snapshot->thread_id != starting_thread_id) {
continue;
}
......@@ -892,6 +952,23 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
return false;
}
const void* ThreadActivityTracker::GetBaseAddress() {
return header_;
}
void ThreadActivityTracker::SetOwningProcessIdForTesting(ProcessId pid,
int64_t stamp) {
header_->owner.SetOwningProcessIdForTesting(pid, stamp);
}
// static
bool ThreadActivityTracker::GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp) {
const Header* header = reinterpret_cast<const Header*>(memory);
return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp);
}
// static
size_t ThreadActivityTracker::SizeForStackDepth(int stack_depth) {
return static_cast<size_t>(stack_depth) * sizeof(Activity) + sizeof(Header);
......@@ -979,6 +1056,9 @@ bool GlobalActivityTracker::ModuleInfoRecord::EncodeFrom(
pickle_size = pickler.size();
changes.store(0, std::memory_order_relaxed);
// Initialize the owner info.
owner.Release_Initialize();
// Now set those fields that can change.
return UpdateFrom(info);
}
......@@ -1053,15 +1133,16 @@ ActivityUserData& GlobalActivityTracker::ScopedThreadActivity::user_data() {
return *user_data_;
}
GlobalActivityTracker::GlobalUserData::GlobalUserData(void* memory, size_t size)
GlobalActivityTracker::ThreadSafeUserData::ThreadSafeUserData(void* memory,
size_t size)
: ActivityUserData(memory, size) {}
GlobalActivityTracker::GlobalUserData::~GlobalUserData() {}
GlobalActivityTracker::ThreadSafeUserData::~ThreadSafeUserData() {}
void GlobalActivityTracker::GlobalUserData::Set(StringPiece name,
ValueType type,
const void* memory,
size_t size) {
void GlobalActivityTracker::ThreadSafeUserData::Set(StringPiece name,
ValueType type,
const void* memory,
size_t size) {
AutoLock lock(data_lock_);
ActivityUserData::Set(name, type, memory, size);
}
......@@ -1186,6 +1267,174 @@ void GlobalActivityTracker::ReleaseTrackerForCurrentThreadForTesting() {
delete tracker;
}
void GlobalActivityTracker::SetBackgroundTaskRunner(
const scoped_refptr<TaskRunner>& runner) {
AutoLock lock(global_tracker_lock_);
background_task_runner_ = runner;
}
void GlobalActivityTracker::SetProcessExitCallback(
ProcessExitCallback callback) {
AutoLock lock(global_tracker_lock_);
process_exit_callback_ = callback;
}
void GlobalActivityTracker::RecordProcessLaunch(
ProcessId process_id,
const FilePath::StringType& cmd) {
DCHECK_NE(GetCurrentProcId(), process_id);
base::AutoLock lock(global_tracker_lock_);
if (base::ContainsKey(known_processes_, process_id)) {
// TODO(bcwhite): Measure this in UMA.
NOTREACHED() << "Process #" << process_id
<< " was previously recorded as \"launched\""
<< " with no corresponding exit.";
known_processes_.erase(process_id);
}
#if defined(OS_WIN)
known_processes_.insert(std::make_pair(process_id, UTF16ToUTF8(cmd)));
#else
known_processes_.insert(std::make_pair(process_id, cmd));
#endif
}
void GlobalActivityTracker::RecordProcessLaunch(
ProcessId process_id,
const FilePath::StringType& exe,
const FilePath::StringType& args) {
if (exe.find(FILE_PATH_LITERAL(" "))) {
RecordProcessLaunch(process_id,
FilePath::StringType(FILE_PATH_LITERAL("\"")) + exe +
FILE_PATH_LITERAL("\" ") + args);
} else {
RecordProcessLaunch(process_id, exe + FILE_PATH_LITERAL(' ') + args);
}
}
void GlobalActivityTracker::RecordProcessExit(ProcessId process_id,
int exit_code) {
DCHECK_NE(GetCurrentProcId(), process_id);
scoped_refptr<TaskRunner> task_runner;
std::string command_line;
{
base::AutoLock lock(global_tracker_lock_);
task_runner = background_task_runner_;
auto found = known_processes_.find(process_id);
if (found != known_processes_.end()) {
command_line = std::move(found->second);
known_processes_.erase(found);
} else {
DLOG(ERROR) << "Recording exit of unknown process #" << process_id;
}
}
// Use the current time to differentiate the process that just exited
// from any that might be created in the future with the same ID.
int64_t now_stamp = Time::Now().ToInternalValue();
// The persistent allocator is thread-safe so run the iteration and
// adjustments on a worker thread if one was provided.
if (task_runner && !task_runner->RunsTasksOnCurrentThread()) {
task_runner->PostTask(
FROM_HERE,
Bind(&GlobalActivityTracker::CleanupAfterProcess, Unretained(this),
process_id, now_stamp, exit_code, Passed(&command_line)));
return;
}
CleanupAfterProcess(process_id, now_stamp, exit_code,
std::move(command_line));
}
void GlobalActivityTracker::SetProcessPhase(ProcessPhase phase) {
process_data().SetInt(kProcessPhaseDataKey, phase);
}
void GlobalActivityTracker::CleanupAfterProcess(ProcessId process_id,
int64_t exit_stamp,
int exit_code,
std::string&& command_line) {
// The process may not have exited cleanly so its necessary to go through
// all the data structures it may have allocated in the persistent memory
// segment and mark them as "released". This will allow them to be reused
// later on.
PersistentMemoryAllocator::Iterator iter(allocator_.get());
PersistentMemoryAllocator::Reference ref;
ProcessExitCallback process_exit_callback;
{
AutoLock lock(global_tracker_lock_);
process_exit_callback = process_exit_callback_;
}
if (process_exit_callback) {
// Find the processes user-data record so the process phase can be passed
// to the callback.
ActivityUserData::Snapshot process_data_snapshot;
while ((ref = iter.GetNextOfType(kTypeIdProcessDataRecord)) != 0) {
const void* memory = allocator_->GetAsArray<char>(
ref, kTypeIdProcessDataRecord, PersistentMemoryAllocator::kSizeAny);
ProcessId found_id;
int64_t create_stamp;
if (ActivityUserData::GetOwningProcessId(memory, &found_id,
&create_stamp)) {
if (found_id == process_id && create_stamp < exit_stamp) {
const ActivityUserData process_data(const_cast<void*>(memory),
allocator_->GetAllocSize(ref));
process_data.CreateSnapshot(&process_data_snapshot);
break; // No need to look for any others.
}
}
}
iter.Reset(); // So it starts anew when used below.
// Record the process's phase at exit so callback doesn't need to go
// searching based on a private key value.
ProcessPhase exit_phase = PROCESS_PHASE_UNKNOWN;
auto phase = process_data_snapshot.find(kProcessPhaseDataKey);
if (phase != process_data_snapshot.end())
exit_phase = static_cast<ProcessPhase>(phase->second.GetInt());
// Perform the callback.
process_exit_callback.Run(process_id, exit_stamp, exit_code, exit_phase,
std::move(command_line),
std::move(process_data_snapshot));
}
// Find all allocations associated with the exited process and free them.
uint32_t type;
while ((ref = iter.GetNext(&type)) != 0) {
switch (type) {
case kTypeIdActivityTracker:
case kTypeIdUserDataRecord:
case kTypeIdProcessDataRecord:
case ModuleInfoRecord::kPersistentTypeId: {
const void* memory = allocator_->GetAsArray<char>(
ref, type, PersistentMemoryAllocator::kSizeAny);
ProcessId found_id;
int64_t create_stamp;
// By convention, the OwningProcess structure is always the first
// field of the structure so there's no need to handle all the
// cases separately.
if (OwningProcess::GetOwningProcessId(memory, &found_id,
&create_stamp)) {
// Only change the type to be "free" if the process ID matches and
// the creation time is before the exit time (so PID re-use doesn't
// cause the erasure of something that is in-use). Memory is cleared
// here, rather than when it's needed, so as to limit the impact at
// that critical time.
if (found_id == process_id && create_stamp < exit_stamp)
allocator_->ChangeType(ref, ~type, type, /*clear=*/true);
}
} break;
}
}
}
void GlobalActivityTracker::RecordLogMessage(StringPiece message) {
// Allocate at least one extra byte so the string is NUL terminated. All
// memory returned by the allocator is guaranteed to be zeroed.
......@@ -1249,12 +1498,20 @@ GlobalActivityTracker::GlobalActivityTracker(
kTypeIdUserDataRecordFree,
kUserDataSize,
kCachedUserDataMemories,
/*make_iterable=*/false),
/*make_iterable=*/true),
process_data_(allocator_->GetAsArray<char>(
AllocateFrom(allocator_.get(),
kTypeIdProcessDataRecordFree,
kProcessDataSize,
kTypeIdProcessDataRecord),
kTypeIdProcessDataRecord,
kProcessDataSize),
kProcessDataSize),
global_data_(
allocator_->GetAsArray<char>(
allocator_->Allocate(kGlobalDataSize, kTypeIdGlobalDataRecord),
kTypeIdGlobalDataRecord,
PersistentMemoryAllocator::kSizeAny),
kGlobalDataSize),
kGlobalDataSize) {
// Ensure the passed memory is valid and empty (iterator finds nothing).
uint32_t type;
......@@ -1264,10 +1521,15 @@ GlobalActivityTracker::GlobalActivityTracker(
DCHECK(!g_tracker_);
subtle::Release_Store(&g_tracker_, reinterpret_cast<uintptr_t>(this));
// The global records must be iterable in order to be found by an analyzer.
// The data records must be iterable in order to be found by an analyzer.
allocator_->MakeIterable(allocator_->GetAsReference(
process_data_.GetBaseAddress(), kTypeIdProcessDataRecord));
allocator_->MakeIterable(allocator_->GetAsReference(
global_data_.GetBaseAddress(), kTypeIdGlobalDataRecord));
// Note that this process has launched.
SetProcessPhase(PROCESS_LAUNCHED);
// Fetch and record all activated field trials.
FieldTrial::ActiveGroups active_groups;
FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
......
......@@ -23,12 +23,15 @@
#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/location.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/process/process_handle.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_local_storage.h"
......@@ -41,7 +44,6 @@ class FilePath;
class Lock;
class PlatformThreadHandle;
class Process;
class StaticAtomicSequenceNumber;
class WaitableEvent;
namespace debug {
......@@ -56,6 +58,39 @@ enum : int {
kActivityCallStackSize = 10,
};
// A class for keeping all information needed to verify that a structure is
// associated with a given process.
struct OwningProcess {
OwningProcess();
~OwningProcess();
// Initializes structure with the current process id and the current time.
// These can uniquely identify a process. A unique non-zero data_id will be
// set making it possible to tell using atomic reads if the data has changed.
void Release_Initialize();
// Explicitly sets the process ID.
void SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp);
// Gets the associated process ID, in native form, and the creation timestamp
// from memory without loading the entire structure for analysis. This will
// return false if no valid process ID is available.
static bool GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp);
// SHA1(base::debug::OwningProcess): Increment this if structure changes!
static constexpr uint32_t kPersistentTypeId = 0xB1179672 + 1;
// Expected size for 32/64-bit check by PersistentMemoryAllocator.
static constexpr size_t kExpectedInstanceSize = 24;
std::atomic<uint32_t> data_id;
uint32_t padding;
int64_t process_id;
int64_t create_stamp;
};
// The data associated with an activity is dependent upon the activity type.
// This union defines all of the various fields. All fields must be explicitly
// sized types to ensure no interoperability problems between 32-bit and
......@@ -293,7 +328,9 @@ struct Activity {
// This class manages arbitrary user data that can be associated with activities
// done by a thread by supporting key/value pairs of any type. This can provide
// additional information during debugging. It is also used to store arbitrary
// global data. All updates must be done from the same thread.
// global data. All updates must be done from the same thread though other
// threads can read it concurrently if they create new objects using the same
// memory.
class BASE_EXPORT ActivityUserData {
public:
// List of known value type. REFERENCE types must immediately follow the non-
......@@ -355,7 +392,7 @@ class BASE_EXPORT ActivityUserData {
// contents have been overwritten by another thread. The return value is
// always non-zero unless it's actually just a data "sink".
uint32_t id() const {
return memory_ ? id_->load(std::memory_order_relaxed) : 0;
return header_ ? header_->owner.data_id.load(std::memory_order_relaxed) : 0;
}
// Writes a |value| (as part of a key/value pair) that will be included with
......@@ -409,7 +446,17 @@ class BASE_EXPORT ActivityUserData {
bool CreateSnapshot(Snapshot* output_snapshot) const;
// Gets the base memory address used for storing data.
const void* GetBaseAddress();
const void* GetBaseAddress() const;
// Explicitly sets the process ID.
void SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp);
// Gets the associated process ID, in native form, and the creation timestamp
// from tracker memory without loading the entire structure for analysis. This
// will return false if no valid process ID is available.
static bool GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp);
protected:
virtual void Set(StringPiece name,
......@@ -422,20 +469,31 @@ class BASE_EXPORT ActivityUserData {
enum : size_t { kMemoryAlignment = sizeof(uint64_t) };
// A structure used to reference data held outside of persistent memory.
struct ReferenceRecord {
uint64_t address;
uint64_t size;
// A structure that defines the structure header in memory.
struct MemoryHeader {
MemoryHeader();
~MemoryHeader();
OwningProcess owner; // Information about the creating process.
};
// Header to a key/value record held in persistent memory.
struct Header {
struct FieldHeader {
FieldHeader();
~FieldHeader();
std::atomic<uint8_t> type; // Encoded ValueType
uint8_t name_size; // Length of "name" key.
std::atomic<uint16_t> value_size; // Actual size of of the stored value.
uint16_t record_size; // Total storage of name, value, header.
};
// A structure used to reference data held outside of persistent memory.
struct ReferenceRecord {
uint64_t address;
uint64_t size;
};
// This record is used to hold known value is a map so that they can be
// found and overwritten later.
struct ValueInfo {
......@@ -470,12 +528,8 @@ class BASE_EXPORT ActivityUserData {
mutable char* memory_;
mutable size_t available_;
// A pointer to the unique ID for this instance.
std::atomic<uint32_t>* const id_;
// This ID is used to create unique indentifiers for user data so that it's
// possible to tell if the information has been overwritten.
static StaticAtomicSequenceNumber next_id_;
// A pointer to the memory header for this instance.
MemoryHeader* const header_;
DISALLOW_COPY_AND_ASSIGN(ActivityUserData);
};
......@@ -618,6 +672,19 @@ class BASE_EXPORT ThreadActivityTracker {
// implementation does not support concurrent snapshot operations.
bool CreateSnapshot(Snapshot* output_snapshot) const;
// Gets the base memory address used for storing data.
const void* GetBaseAddress();
// Explicitly sets the process ID.
void SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp);
// Gets the associated process ID, in native form, and the creation timestamp
// from tracker memory without loading the entire structure for analysis. This
// will return false if no valid process ID is available.
static bool GetOwningProcessId(const void* memory,
ProcessId* out_id,
int64_t* out_stamp);
// Calculates the memory size required for a given stack depth, including
// the internal header structure for the stack.
static size_t SizeForStackDepth(int stack_depth);
......@@ -649,15 +716,45 @@ class BASE_EXPORT GlobalActivityTracker {
// will be safely ignored. These are public so that an external process
// can recognize records of this type within an allocator.
enum : uint32_t {
kTypeIdActivityTracker = 0x5D7381AF + 3, // SHA1(ActivityTracker) v3
kTypeIdUserDataRecord = 0x615EDDD7 + 2, // SHA1(UserDataRecord) v2
kTypeIdActivityTracker = 0x5D7381AF + 4, // SHA1(ActivityTracker) v4
kTypeIdUserDataRecord = 0x615EDDD7 + 3, // SHA1(UserDataRecord) v3
kTypeIdGlobalLogMessage = 0x4CF434F9 + 1, // SHA1(GlobalLogMessage) v1
kTypeIdGlobalDataRecord = kTypeIdUserDataRecord + 1000,
kTypeIdProcessDataRecord = kTypeIdUserDataRecord + 0x100,
kTypeIdGlobalDataRecord = kTypeIdUserDataRecord + 0x200,
kTypeIdActivityTrackerFree = ~kTypeIdActivityTracker,
kTypeIdUserDataRecordFree = ~kTypeIdUserDataRecord,
kTypeIdProcessDataRecordFree = ~kTypeIdProcessDataRecord,
};
// An enumeration of common process life stages. All entries are given an
// explicit number so they are known and remain constant; this allows for
// cross-version analysis either locally or on a server.
enum ProcessPhase : int {
// The phases are generic and may have meaning to the tracker.
PROCESS_PHASE_UNKNOWN = 0,
PROCESS_LAUNCHED = 1,
PROCESS_LAUNCH_FAILED = 2,
PROCESS_EXITED_CLEANLY = 10,
PROCESS_EXITED_WITH_CODE = 11,
// Add here whatever is useful for analysis.
PROCESS_SHUTDOWN_STARTED = 100,
PROCESS_MAIN_LOOP_STARTED = 101,
};
// A callback made when a process exits to allow immediate analysis of its
// data. Note that the system may reuse the |process_id| so when fetching
// records it's important to ensure that what is returned was created before
// the |exit_stamp|. Movement of |process_data| information is allowed.
using ProcessExitCallback =
Callback<void(int64_t process_id,
int64_t exit_stamp,
int exit_code,
ProcessPhase exit_phase,
std::string&& command_line,
ActivityUserData::Snapshot&& process_data)>;
// This structure contains information about a loaded module, as shown to
// users of the tracker.
struct BASE_EXPORT ModuleInfo {
......@@ -789,6 +886,49 @@ class BASE_EXPORT GlobalActivityTracker {
// Releases the activity-tracker for the current thread (for testing only).
void ReleaseTrackerForCurrentThreadForTesting();
// Sets a task-runner that can be used for background work.
void SetBackgroundTaskRunner(const scoped_refptr<TaskRunner>& runner);
// Sets an optional callback to be called when a process exits.
void SetProcessExitCallback(ProcessExitCallback callback);
// Manages process lifetimes. These are called by the process that launched
// and reaped the subprocess, not the subprocess itself. If it is expensive
// to generate the parameters, Get() the global tracker and call these
// conditionally rather than using the static versions.
void RecordProcessLaunch(ProcessId process_id,
const FilePath::StringType& cmd);
void RecordProcessLaunch(ProcessId process_id,
const FilePath::StringType& exe,
const FilePath::StringType& args);
void RecordProcessExit(ProcessId process_id, int exit_code);
static void RecordProcessLaunchIfEnabled(ProcessId process_id,
const FilePath::StringType& cmd) {
GlobalActivityTracker* tracker = Get();
if (tracker)
tracker->RecordProcessLaunch(process_id, cmd);
}
static void RecordProcessLaunchIfEnabled(ProcessId process_id,
const FilePath::StringType& exe,
const FilePath::StringType& args) {
GlobalActivityTracker* tracker = Get();
if (tracker)
tracker->RecordProcessLaunch(process_id, exe, args);
}
static void RecordProcessExitIfEnabled(ProcessId process_id, int exit_code) {
GlobalActivityTracker* tracker = Get();
if (tracker)
tracker->RecordProcessExit(process_id, exit_code);
}
// Sets the "phase" of the current process, useful for knowing what it was
// doing when it last reported.
void SetProcessPhase(ProcessPhase phase);
static void SetProcessPhaseIfEnabled(ProcessPhase phase) {
GlobalActivityTracker* tracker = Get();
if (tracker)
tracker->SetProcessPhase(phase);
}
// Records a log message. The current implementation does NOT recycle these
// only store critical messages such as FATAL ones.
void RecordLogMessage(StringPiece message);
......@@ -818,7 +958,12 @@ class BASE_EXPORT GlobalActivityTracker {
tracker->RecordFieldTrial(trial_name, group_name);
}
// Accesses the process data record for storing arbitrary key/value pairs.
// Updates to this are thread-safe.
ActivityUserData& process_data() { return process_data_; }
// Accesses the global data record for storing arbitrary key/value pairs.
// Updates to this are thread-safe.
ActivityUserData& global_data() { return global_data_; }
private:
......@@ -837,10 +982,10 @@ class BASE_EXPORT GlobalActivityTracker {
// A wrapper around ActivityUserData that is thread-safe and thus can be used
// in the global scope without the requirement of being called from only one
// thread.
class GlobalUserData : public ActivityUserData {
class ThreadSafeUserData : public ActivityUserData {
public:
GlobalUserData(void* memory, size_t size);
~GlobalUserData() override;
ThreadSafeUserData(void* memory, size_t size);
~ThreadSafeUserData() override;
private:
void Set(StringPiece name,
......@@ -850,7 +995,7 @@ class BASE_EXPORT GlobalActivityTracker {
Lock data_lock_;
DISALLOW_COPY_AND_ASSIGN(GlobalUserData);
DISALLOW_COPY_AND_ASSIGN(ThreadSafeUserData);
};
// State of a module as stored in persistent memory. This supports a single
......@@ -862,7 +1007,8 @@ class BASE_EXPORT GlobalActivityTracker {
static constexpr uint32_t kPersistentTypeId = 0x05DB5F41 + 1;
// Expected size for 32/64-bit check by PersistentMemoryAllocator.
static constexpr size_t kExpectedInstanceSize = 56;
static constexpr size_t kExpectedInstanceSize =
OwningProcess::kExpectedInstanceSize + 56;
// The atomic unfortunately makes this a "complex" class on some compilers
// and thus requires an out-of-line constructor & destructor even though
......@@ -870,6 +1016,7 @@ class BASE_EXPORT GlobalActivityTracker {
ModuleInfoRecord();
~ModuleInfoRecord();
OwningProcess owner; // The process that created this record.
uint64_t address; // The base address of the module.
uint64_t load_time; // Time of last load/unload.
uint64_t size; // The size of the module in bytes.
......@@ -933,6 +1080,12 @@ class BASE_EXPORT GlobalActivityTracker {
// be tracked. |value| is a pointer to a ManagedActivityTracker.
static void OnTLSDestroy(void* value);
// Does process-exit work. This can be run on any thread.
void CleanupAfterProcess(ProcessId process_id,
int64_t exit_stamp,
int exit_code,
std::string&& command_line);
// The persistent-memory allocator from which the memory for all trackers
// is taken.
std::unique_ptr<PersistentMemoryAllocator> allocator_;
......@@ -955,9 +1108,9 @@ class BASE_EXPORT GlobalActivityTracker {
ActivityTrackerMemoryAllocator user_data_allocator_;
base::Lock user_data_allocator_lock_;
// An object for holding global arbitrary key value pairs. Values must always
// be written from the main UI thread.
GlobalUserData global_data_;
// An object for holding arbitrary key value pairs with thread-safe access.
ThreadSafeUserData process_data_;
ThreadSafeUserData global_data_;
// A map of global module information, keyed by module path.
std::map<const std::string, ModuleInfoRecord*> modules_;
......@@ -966,6 +1119,21 @@ class BASE_EXPORT GlobalActivityTracker {
// The active global activity tracker.
static subtle::AtomicWord g_tracker_;
// A lock that is used to protect access to the following fields.
base::Lock global_tracker_lock_;
// The collection of processes being tracked and their command-lines.
std::map<int64_t, std::string> known_processes_;
// A task-runner that can be used for doing background processing.
scoped_refptr<TaskRunner> background_task_runner_;
// A callback performed when a subprocess exits, including its exit-code
// and the phase it was in when that occurred. This will be called via
// the |background_task_runner_| if one is set or whatever thread reaped
// the process otherwise.
ProcessExitCallback process_exit_callback_;
DISALLOW_COPY_AND_ASSIGN(GlobalActivityTracker);
};
......
......@@ -84,45 +84,73 @@ class ActivityTrackerTest : public testing::Test {
return GlobalActivityTracker::Get()->user_data_allocator_.cache_used();
}
void HandleProcessExit(int64_t id,
int64_t stamp,
int code,
GlobalActivityTracker::ProcessPhase phase,
std::string&& command,
ActivityUserData::Snapshot&& data) {
exit_id = id;
exit_stamp = stamp;
exit_code = code;
exit_phase = phase;
exit_command = std::move(command);
exit_data = std::move(data);
}
static void DoNothing() {}
int64_t exit_id = 0;
int64_t exit_stamp;
int exit_code;
GlobalActivityTracker::ProcessPhase exit_phase;
std::string exit_command;
ActivityUserData::Snapshot exit_data;
};
TEST_F(ActivityTrackerTest, UserDataTest) {
char buffer[256];
memset(buffer, 0, sizeof(buffer));
ActivityUserData data(buffer, sizeof(buffer));
const size_t space = sizeof(buffer) - 8;
size_t space = sizeof(buffer) - sizeof(ActivityUserData::MemoryHeader);
ASSERT_EQ(space, data.available_);
data.SetInt("foo", 1);
ASSERT_EQ(space - 24, data.available_);
space -= 24;
ASSERT_EQ(space, data.available_);
data.SetUint("b", 1U); // Small names fit beside header in a word.
ASSERT_EQ(space - 24 - 16, data.available_);
space -= 16;
ASSERT_EQ(space, data.available_);
data.Set("c", buffer, 10);
ASSERT_EQ(space - 24 - 16 - 24, data.available_);
space -= 24;
ASSERT_EQ(space, data.available_);
data.SetString("dear john", "it's been fun");
ASSERT_EQ(space - 24 - 16 - 24 - 32, data.available_);
space -= 32;
ASSERT_EQ(space, data.available_);
data.Set("c", buffer, 20);
ASSERT_EQ(space - 24 - 16 - 24 - 32, data.available_);
ASSERT_EQ(space, data.available_);
data.SetString("dear john", "but we're done together");
ASSERT_EQ(space - 24 - 16 - 24 - 32, data.available_);
ASSERT_EQ(space, data.available_);
data.SetString("dear john", "bye");
ASSERT_EQ(space - 24 - 16 - 24 - 32, data.available_);
ASSERT_EQ(space, data.available_);
data.SetChar("d", 'x');
ASSERT_EQ(space - 24 - 16 - 24 - 32 - 8, data.available_);
space -= 8;
ASSERT_EQ(space, data.available_);
data.SetBool("ee", true);
ASSERT_EQ(space - 24 - 16 - 24 - 32 - 8 - 16, data.available_);
space -= 16;
ASSERT_EQ(space, data.available_);
data.SetString("f", "");
ASSERT_EQ(space - 24 - 16 - 24 - 32 - 8 - 16 - 8, data.available_);
space -= 8;
ASSERT_EQ(space, data.available_);
}
TEST_F(ActivityTrackerTest, PushPopTest) {
......@@ -250,6 +278,16 @@ TEST_F(ActivityTrackerTest, CreateWithFileTest) {
// GlobalActivityTracker tests below.
TEST_F(ActivityTrackerTest, BasicTest) {
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3);
GlobalActivityTracker* global = GlobalActivityTracker::Get();
// Ensure the data repositories have backing store, indicated by non-zero ID.
EXPECT_NE(0U, global->process_data().id());
EXPECT_NE(0U, global->global_data().id());
EXPECT_NE(global->process_data().id(), global->global_data().id());
}
class SimpleActivityThread : public SimpleThread {
public:
SimpleActivityThread(const std::string& name,
......@@ -336,5 +374,107 @@ TEST_F(ActivityTrackerTest, ThreadDeathTest) {
EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount());
}
TEST_F(ActivityTrackerTest, ProcessDeathTest) {
// This doesn't actually create and destroy a process. Instead, it uses for-
// testing interfaces to simulate data created by other processes.
const ProcessId other_process_id = GetCurrentProcId() + 1;
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3);
GlobalActivityTracker* global = GlobalActivityTracker::Get();
ThreadActivityTracker* thread = global->GetOrCreateTrackerForCurrentThread();
// Get callbacks for process exit.
global->SetProcessExitCallback(
Bind(&ActivityTrackerTest::HandleProcessExit, Unretained(this)));
// Pretend than another process has started.
global->RecordProcessLaunch(other_process_id, FILE_PATH_LITERAL("foo --bar"));
// Do some activities.
PendingTask task(FROM_HERE, base::Bind(&DoNothing));
ScopedTaskRunActivity activity(task);
ActivityUserData& user_data = activity.user_data();
ASSERT_NE(0U, user_data.id());
// Get the memory-allocator references to that data.
PersistentMemoryAllocator::Reference proc_data_ref =
global->allocator()->GetAsReference(
global->process_data().GetBaseAddress(),
GlobalActivityTracker::kTypeIdProcessDataRecord);
ASSERT_TRUE(proc_data_ref);
PersistentMemoryAllocator::Reference tracker_ref =
global->allocator()->GetAsReference(
thread->GetBaseAddress(),
GlobalActivityTracker::kTypeIdActivityTracker);
ASSERT_TRUE(tracker_ref);
PersistentMemoryAllocator::Reference user_data_ref =
global->allocator()->GetAsReference(
user_data.GetBaseAddress(),
GlobalActivityTracker::kTypeIdUserDataRecord);
ASSERT_TRUE(user_data_ref);
// Make a copy of the thread-tracker state so it can be restored later.
const size_t tracker_size = global->allocator()->GetAllocSize(tracker_ref);
std::unique_ptr<char[]> tracker_copy(new char[tracker_size]);
memcpy(tracker_copy.get(), thread->GetBaseAddress(), tracker_size);
// Change the objects to appear to be owned by another process.
ProcessId owning_id;
int64_t stamp;
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
global->process_data().GetBaseAddress(), &owning_id, &stamp));
EXPECT_NE(other_process_id, owning_id);
ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId(
thread->GetBaseAddress(), &owning_id, &stamp));
EXPECT_NE(other_process_id, owning_id);
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(),
&owning_id, &stamp));
EXPECT_NE(other_process_id, owning_id);
global->process_data().SetOwningProcessIdForTesting(other_process_id, stamp);
thread->SetOwningProcessIdForTesting(other_process_id, stamp);
user_data.SetOwningProcessIdForTesting(other_process_id, stamp);
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
global->process_data().GetBaseAddress(), &owning_id, &stamp));
EXPECT_EQ(other_process_id, owning_id);
ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId(
thread->GetBaseAddress(), &owning_id, &stamp));
EXPECT_EQ(other_process_id, owning_id);
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(),
&owning_id, &stamp));
EXPECT_EQ(other_process_id, owning_id);
// Check that process exit will perform callback and free the allocations.
ASSERT_EQ(0, exit_id);
ASSERT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecord,
global->allocator()->GetType(proc_data_ref));
ASSERT_EQ(GlobalActivityTracker::kTypeIdActivityTracker,
global->allocator()->GetType(tracker_ref));
ASSERT_EQ(GlobalActivityTracker::kTypeIdUserDataRecord,
global->allocator()->GetType(user_data_ref));
global->RecordProcessExit(other_process_id, 0);
EXPECT_EQ(other_process_id, exit_id);
EXPECT_EQ("foo --bar", exit_command);
EXPECT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecordFree,
global->allocator()->GetType(proc_data_ref));
EXPECT_EQ(GlobalActivityTracker::kTypeIdActivityTrackerFree,
global->allocator()->GetType(tracker_ref));
EXPECT_EQ(GlobalActivityTracker::kTypeIdUserDataRecordFree,
global->allocator()->GetType(user_data_ref));
// Restore memory contents and types so things don't crash when doing real
// process clean-up.
memcpy(const_cast<void*>(thread->GetBaseAddress()), tracker_copy.get(),
tracker_size);
global->allocator()->ChangeType(
proc_data_ref, GlobalActivityTracker::kTypeIdProcessDataRecord,
GlobalActivityTracker::kTypeIdUserDataRecordFree, false);
global->allocator()->ChangeType(
tracker_ref, GlobalActivityTracker::kTypeIdActivityTracker,
GlobalActivityTracker::kTypeIdActivityTrackerFree, false);
global->allocator()->ChangeType(
user_data_ref, GlobalActivityTracker::kTypeIdUserDataRecord,
GlobalActivityTracker::kTypeIdUserDataRecordFree, false);
}
} // namespace debug
} // namespace base
......@@ -17,6 +17,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/debug/activity_tracker.h"
#include "base/debug/stack_trace.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
......@@ -94,7 +95,12 @@ bool GetAppOutputInternal(const StringPiece16& cl,
NOTREACHED() << "Failed to start process";
return false;
}
base::win::ScopedProcessInformation proc_info(temp_process_info);
base::debug::GlobalActivityTracker* tracker =
base::debug::GlobalActivityTracker::Get();
if (tracker)
tracker->RecordProcessLaunch(proc_info.process_id(), cl.as_string());
// Close our writing end of pipe now. Otherwise later read would not be able
// to detect end of child's output.
......@@ -119,6 +125,8 @@ bool GetAppOutputInternal(const StringPiece16& cl,
int exit_code;
base::TerminationStatus status = GetTerminationStatus(
proc_info.process_handle(), &exit_code);
base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(
proc_info.process_id(), exit_code);
return status != base::TERMINATION_STATUS_PROCESS_CRASHED &&
status != base::TERMINATION_STATUS_ABNORMAL_TERMINATION;
}
......@@ -317,6 +325,8 @@ Process LaunchProcess(const string16& cmdline,
if (options.wait)
WaitForSingleObject(process_info.process_handle(), INFINITE);
base::debug::GlobalActivityTracker::RecordProcessLaunchIfEnabled(
process_info.process_id(), cmdline);
return Process(process_info.TakeProcessHandle());
}
......@@ -344,6 +354,8 @@ Process LaunchElevatedProcess(const CommandLine& cmdline,
if (options.wait)
WaitForSingleObject(shex_info.hProcess, INFINITE);
base::debug::GlobalActivityTracker::RecordProcessLaunchIfEnabled(
GetProcessId(shex_info.hProcess), file, arguments);
return Process(shex_info.hProcess);
}
......
......@@ -139,6 +139,10 @@ bool Process::Terminate(int exit_code, bool wait) const {
} else if (!result) {
DPLOG(ERROR) << "Unable to terminate process";
}
if (result) {
base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(Pid(),
exit_code);
}
return result;
}
......@@ -162,6 +166,9 @@ bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
if (exit_code)
*exit_code = temp_code;
base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(
Pid(), static_cast<int>(temp_code));
return true;
}
......
......@@ -6,6 +6,7 @@
#include <memory>
#include "base/debug/activity_tracker.h"
#include "content/public/app/content_main_runner.h"
namespace content {
......@@ -14,13 +15,34 @@ int ContentMain(const ContentMainParams& params) {
std::unique_ptr<ContentMainRunner> main_runner(ContentMainRunner::Create());
int exit_code = main_runner->Initialize(params);
if (exit_code >= 0)
if (exit_code >= 0) {
base::debug::GlobalActivityTracker* tracker =
base::debug::GlobalActivityTracker::Get();
if (tracker) {
tracker->SetProcessPhase(
base::debug::GlobalActivityTracker::PROCESS_LAUNCH_FAILED);
tracker->process_data().SetInt("exit-code", exit_code);
}
return exit_code;
}
exit_code = main_runner->Run();
main_runner->Shutdown();
base::debug::GlobalActivityTracker* tracker =
base::debug::GlobalActivityTracker::Get();
if (tracker) {
if (exit_code == 0) {
tracker->SetProcessPhaseIfEnabled(
base::debug::GlobalActivityTracker::PROCESS_EXITED_CLEANLY);
} else {
tracker->SetProcessPhaseIfEnabled(
base::debug::GlobalActivityTracker::PROCESS_EXITED_WITH_CODE);
tracker->process_data().SetInt("exit-code", exit_code);
}
}
return exit_code;
}
......
......@@ -10,6 +10,7 @@
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/debug/activity_tracker.h"
#include "base/debug/profiler.h"
#include "base/files/file_util.h"
#include "base/hash.h"
......@@ -836,6 +837,13 @@ sandbox::ResultCode StartSandboxedProcess(
TRACE_EVENT_END0("startup", "StartProcessWithAccess::LAUNCHPROCESS");
base::debug::GlobalActivityTracker* tracker =
base::debug::GlobalActivityTracker::Get();
if (tracker) {
tracker->RecordProcessLaunch(target.process_id(),
cmd_line->GetCommandLineString());
}
if (sandbox::SBOX_ALL_OK != result) {
UMA_HISTOGRAM_SPARSE_SLOWLY("Process.Sandbox.Launch.Error", last_error);
if (result == sandbox::SBOX_ERROR_GENERIC)
......
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