mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 22:16:13 +00:00
[snapshot] Add support for thread names
This CL adds a new method ThreadSnapshot::ThreadName(), implements it in each snapshot implementation, and adds tests for iOS, macOS, Linux, Windows, and Fuchsia. Bug: crashpad:327 Change-Id: I35031975223854c19d977e057dd026a40d33fd41 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3671776 Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Ben Hamilton <benhamilton@google.com> Reviewed-by: Ben Hamilton <benhamilton@google.com>
This commit is contained in:
parent
0a14d52dad
commit
ed8cfeb2cd
@ -811,6 +811,22 @@ void InProcessIntermediateDumpHandler::WriteThreadInfo(
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO");
|
||||
}
|
||||
|
||||
thread_extended_info extended_info;
|
||||
count = THREAD_EXTENDED_INFO_COUNT;
|
||||
kr = thread_info(thread,
|
||||
THREAD_EXTENDED_INFO,
|
||||
reinterpret_cast<thread_info_t>(&extended_info),
|
||||
&count);
|
||||
if (kr == KERN_SUCCESS) {
|
||||
WritePropertyBytes(
|
||||
writer,
|
||||
IntermediateDumpKey::kThreadName,
|
||||
reinterpret_cast<const void*>(extended_info.pth_name),
|
||||
strnlen(extended_info.pth_name, sizeof(extended_info.pth_name)));
|
||||
} else {
|
||||
CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_EXTENDED_INFO");
|
||||
}
|
||||
|
||||
thread_precedence_policy precedence;
|
||||
count = THREAD_PRECEDENCE_POLICY_COUNT;
|
||||
boolean_t get_default = FALSE;
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "client/simple_string_dictionary.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/file/filesystem.h"
|
||||
@ -206,6 +207,8 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) {
|
||||
}
|
||||
|
||||
TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
|
||||
const ScopedSetThreadName scoped_set_thread_name("TestThreads");
|
||||
|
||||
WriteReport();
|
||||
internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
|
||||
ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {}));
|
||||
@ -221,6 +224,7 @@ TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
|
||||
&count),
|
||||
0);
|
||||
EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id);
|
||||
EXPECT_EQ(threads[0]->ThreadName(), "TestThreads");
|
||||
}
|
||||
|
||||
TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "minidump/minidump_module_writer.h"
|
||||
#include "minidump/minidump_system_info_writer.h"
|
||||
#include "minidump/minidump_thread_id_map.h"
|
||||
#include "minidump/minidump_thread_name_list_writer.h"
|
||||
#include "minidump/minidump_thread_writer.h"
|
||||
#include "minidump/minidump_unloaded_module_writer.h"
|
||||
#include "minidump/minidump_user_extension_stream_data_source.h"
|
||||
@ -34,6 +35,7 @@
|
||||
#include "snapshot/exception_snapshot.h"
|
||||
#include "snapshot/module_snapshot.h"
|
||||
#include "snapshot/process_snapshot.h"
|
||||
#include "snapshot/thread_snapshot.h"
|
||||
#include "util/file/file_writer.h"
|
||||
#include "util/numeric/safe_assignment.h"
|
||||
|
||||
@ -94,6 +96,21 @@ void MinidumpFileWriter::InitializeFromSnapshot(
|
||||
add_stream_result = AddStream(std::move(thread_list));
|
||||
DCHECK(add_stream_result);
|
||||
|
||||
bool has_thread_name = false;
|
||||
for (const ThreadSnapshot* thread_snapshot : process_snapshot->Threads()) {
|
||||
if (!thread_snapshot->ThreadName().empty()) {
|
||||
has_thread_name = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_thread_name) {
|
||||
auto thread_name_list = std::make_unique<MinidumpThreadNameListWriter>();
|
||||
thread_name_list->InitializeFromSnapshot(process_snapshot->Threads(),
|
||||
thread_id_map);
|
||||
add_stream_result = AddStream(std::move(thread_name_list));
|
||||
DCHECK(add_stream_result);
|
||||
}
|
||||
|
||||
const ExceptionSnapshot* exception_snapshot = process_snapshot->Exception();
|
||||
if (exception_snapshot) {
|
||||
auto exception = std::make_unique<MinidumpExceptionWriter>();
|
||||
|
@ -17,21 +17,47 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "minidump/minidump_thread_id_map.h"
|
||||
#include "snapshot/thread_snapshot.h"
|
||||
#include "util/file/file_writer.h"
|
||||
#include "util/numeric/safe_assignment.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
MinidumpThreadNameWriter::MinidumpThreadNameWriter()
|
||||
: MinidumpWritable(), thread_name_(), name_() {}
|
||||
: MinidumpWritable(), rva_of_thread_name_(), thread_id_(), name_() {}
|
||||
|
||||
MinidumpThreadNameWriter::~MinidumpThreadNameWriter() {}
|
||||
|
||||
const MINIDUMP_THREAD_NAME* MinidumpThreadNameWriter::MinidumpThreadName()
|
||||
const {
|
||||
void MinidumpThreadNameWriter::InitializeFromSnapshot(
|
||||
const ThreadSnapshot* thread_snapshot,
|
||||
const MinidumpThreadIDMap& thread_id_map) {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
|
||||
const auto it = thread_id_map.find(thread_snapshot->ThreadID());
|
||||
DCHECK(it != thread_id_map.end());
|
||||
SetThreadId(it->second);
|
||||
SetThreadName(thread_snapshot->ThreadName());
|
||||
}
|
||||
|
||||
RVA64 MinidumpThreadNameWriter::RvaOfThreadName() const {
|
||||
DCHECK_EQ(state(), kStateWritable);
|
||||
|
||||
return &thread_name_;
|
||||
return rva_of_thread_name_;
|
||||
}
|
||||
|
||||
uint32_t MinidumpThreadNameWriter::ThreadId() const {
|
||||
DCHECK_EQ(state(), kStateWritable);
|
||||
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
bool MinidumpThreadNameWriter::Freeze() {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
|
||||
name_->RegisterRVA(&rva_of_thread_name_);
|
||||
|
||||
return MinidumpWritable::Freeze();
|
||||
}
|
||||
|
||||
void MinidumpThreadNameWriter::SetThreadName(const std::string& name) {
|
||||
@ -46,10 +72,9 @@ void MinidumpThreadNameWriter::SetThreadName(const std::string& name) {
|
||||
size_t MinidumpThreadNameWriter::SizeOfObject() {
|
||||
DCHECK_GE(state(), kStateFrozen);
|
||||
|
||||
// This object doesn’t directly write anything itself. Its
|
||||
// MINIDUMP_THREAD_NAME is written by its parent as part of a
|
||||
// MINIDUMP_THREAD_NAME_LIST, and its children are responsible for writing
|
||||
// themselves.
|
||||
// This object doesn’t directly write anything itself. Its parent writes the
|
||||
// MINIDUMP_THREAD_NAME objects as part of a MINIDUMP_THREAD_NAME_LIST, and
|
||||
// its children are responsible for writing themselves.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -63,24 +88,6 @@ std::vector<internal::MinidumpWritable*> MinidumpThreadNameWriter::Children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
bool MinidumpThreadNameWriter::WillWriteAtOffsetImpl(FileOffset offset) {
|
||||
DCHECK_EQ(state(), kStateFrozen);
|
||||
|
||||
// This cannot use RegisterRVA(&thread_name_.RvaOfThreadName), since
|
||||
// &MINIDUMP_THREAD_NAME_LIST::RvaOfThreadName is not aligned on a pointer
|
||||
// boundary, so it causes failures on 32-bit ARM.
|
||||
//
|
||||
// Instead, manually update the RVA64 to the current file offset since the
|
||||
// child thread_name_ will write its contents at that offset.
|
||||
decltype(thread_name_.RvaOfThreadName) local_rva_of_thread_name;
|
||||
if (!AssignIfInRange(&local_rva_of_thread_name, offset)) {
|
||||
LOG(ERROR) << "offset " << offset << " out of range";
|
||||
return false;
|
||||
}
|
||||
thread_name_.RvaOfThreadName = local_rva_of_thread_name;
|
||||
return MinidumpWritable::WillWriteAtOffsetImpl(offset);
|
||||
}
|
||||
|
||||
bool MinidumpThreadNameWriter::WriteObject(FileWriterInterface* file_writer) {
|
||||
DCHECK_EQ(state(), kStateWritable);
|
||||
|
||||
@ -96,6 +103,19 @@ MinidumpThreadNameListWriter::MinidumpThreadNameListWriter()
|
||||
|
||||
MinidumpThreadNameListWriter::~MinidumpThreadNameListWriter() {}
|
||||
|
||||
void MinidumpThreadNameListWriter::InitializeFromSnapshot(
|
||||
const std::vector<const ThreadSnapshot*>& thread_snapshots,
|
||||
const MinidumpThreadIDMap& thread_id_map) {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
DCHECK(thread_names_.empty());
|
||||
|
||||
for (const ThreadSnapshot* thread_snapshot : thread_snapshots) {
|
||||
auto thread = std::make_unique<MinidumpThreadNameWriter>();
|
||||
thread->InitializeFromSnapshot(thread_snapshot, thread_id_map);
|
||||
AddThreadName(std::move(thread));
|
||||
}
|
||||
}
|
||||
|
||||
void MinidumpThreadNameListWriter::AddThreadName(
|
||||
std::unique_ptr<MinidumpThreadNameWriter> thread_name) {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
@ -150,10 +170,15 @@ bool MinidumpThreadNameListWriter::WriteObject(
|
||||
std::vector<WritableIoVec> iovecs(1, iov);
|
||||
iovecs.reserve(thread_names_.size() + 1);
|
||||
|
||||
std::vector<MINIDUMP_THREAD_NAME> minidump_thread_names;
|
||||
minidump_thread_names.reserve(thread_names_.size());
|
||||
for (const auto& thread_name : thread_names_) {
|
||||
iov.iov_base = thread_name->MinidumpThreadName();
|
||||
iov.iov_len = sizeof(MINIDUMP_THREAD_NAME);
|
||||
iovecs.emplace_back(iov);
|
||||
auto& minidump_thread_name = minidump_thread_names.emplace_back();
|
||||
minidump_thread_name.ThreadId = thread_name->ThreadId();
|
||||
minidump_thread_name.RvaOfThreadName = thread_name->RvaOfThreadName();
|
||||
iov.iov_base = &minidump_thread_name;
|
||||
iov.iov_len = sizeof(minidump_thread_name);
|
||||
iovecs.push_back(iov);
|
||||
}
|
||||
|
||||
return file_writer->WriteIoVec(&iovecs);
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include "minidump/minidump_stream_writer.h"
|
||||
#include "minidump/minidump_string_writer.h"
|
||||
#include "minidump/minidump_thread_id_map.h"
|
||||
#include "minidump/minidump_writable.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -45,28 +46,57 @@ class MinidumpThreadNameWriter final : public internal::MinidumpWritable {
|
||||
|
||||
~MinidumpThreadNameWriter() override;
|
||||
|
||||
//! \brief Returns a MINIDUMP_THREAD_NAME referencing this object’s data.
|
||||
//! \brief Initializes the MINIDUMP_THREAD_NAME based on \a thread_snapshot.
|
||||
//!
|
||||
//! This method is expected to be called by a MinidumpThreadNameListWriter in
|
||||
//! order to obtain a MINIDUMP_THREAD_NAME to include in its list.
|
||||
//! \param[in] thread_snapshot The thread snapshot to use as source data.
|
||||
//! \param[in] thread_id_map A MinidumpThreadIDMap to be consulted to
|
||||
//! determine the 32-bit minidump thread ID to use for \a thread_snapshot.
|
||||
//!
|
||||
//! \note Valid in #kStateMutable.
|
||||
void InitializeFromSnapshot(const ThreadSnapshot* thread_snapshot,
|
||||
const MinidumpThreadIDMap& thread_id_map);
|
||||
|
||||
//! \brief Sets the ThreadId for MINIDUMP_THREAD_NAME::ThreadId.
|
||||
void SetThreadId(uint32_t thread_id) { thread_id_ = thread_id; }
|
||||
|
||||
//! \brief Gets the ThreadId for MINIDUMP_THREAD_NAME::ThreadId.
|
||||
//!
|
||||
//! \note Valid in #kStateWritable.
|
||||
const MINIDUMP_THREAD_NAME* MinidumpThreadName() const;
|
||||
|
||||
//! \brief Sets MINIDUMP_THREAD_NAME::ThreadId.
|
||||
void SetThreadId(uint32_t thread_id) { thread_name_.ThreadId = thread_id; }
|
||||
uint32_t ThreadId() const;
|
||||
|
||||
//! \brief Sets MINIDUMP_THREAD_NAME::RvaOfThreadName.
|
||||
void SetThreadName(const std::string& thread_name);
|
||||
|
||||
//! \brief Returns an RVA64 which has been updated with the relative address
|
||||
//! of the thread name.
|
||||
//!
|
||||
//! This method is expected to be called by a MinidumpThreadNameListWriter in
|
||||
//! order to obtain the RVA64 of the thread name.
|
||||
//!
|
||||
//! \note Valid in #kStateWritable.
|
||||
RVA64 RvaOfThreadName() const;
|
||||
|
||||
private:
|
||||
// MinidumpWritable:
|
||||
bool Freeze() override;
|
||||
size_t SizeOfObject() override;
|
||||
std::vector<MinidumpWritable*> Children() override;
|
||||
bool WillWriteAtOffsetImpl(FileOffset offset) override;
|
||||
bool WriteObject(FileWriterInterface* file_writer) override;
|
||||
|
||||
MINIDUMP_THREAD_NAME thread_name_;
|
||||
// This exists as a separate field so MinidumpWritable::RegisterRVA() can be
|
||||
// used on a guaranteed-aligned pointer (MINIDUMP_THREAD_NAME::RvaOfThreadName
|
||||
// is not 64-bit aligned, causing issues on ARM).
|
||||
RVA64 rva_of_thread_name_;
|
||||
|
||||
// Although this class manages the data for a MINIDUMP_THREAD_NAME, it does
|
||||
// not directly hold a MINIDUMP_THREAD_NAME, as that struct contains a
|
||||
// non-aligned RVA64 field which prevents it use with
|
||||
// MinidumpWritable::RegisterRVA().
|
||||
//
|
||||
// Instead, this class individually holds the fields of the
|
||||
// MINIDUMP_THREAD_NAME which are fetched by MinidumpThreadNameListWriter.
|
||||
uint32_t thread_id_;
|
||||
|
||||
std::unique_ptr<internal::MinidumpUTF16StringWriter> name_;
|
||||
};
|
||||
|
||||
@ -83,6 +113,18 @@ class MinidumpThreadNameListWriter final
|
||||
|
||||
~MinidumpThreadNameListWriter() override;
|
||||
|
||||
//! \brief Adds an initialized MINIDUMP_THREAD_NAME for each thread in \a
|
||||
//! thread_snapshots to the MINIDUMP_THREAD_NAME_LIST.
|
||||
//!
|
||||
//! \param[in] thread_snapshots The thread snapshots to use as source data.
|
||||
//! \param[in] thread_id_map A MinidumpThreadIDMap previously built by
|
||||
//! MinidumpThreadListWriter::InitializeFromSnapshot().
|
||||
//!
|
||||
//! \note Valid in #kStateMutable.
|
||||
void InitializeFromSnapshot(
|
||||
const std::vector<const ThreadSnapshot*>& thread_snapshots,
|
||||
const MinidumpThreadIDMap& thread_id_map);
|
||||
|
||||
//! \brief Adds a MinidumpThreadNameWriter to the MINIDUMP_THREAD_LIST.
|
||||
//!
|
||||
//! This object takes ownership of \a thread_name and becomes its parent in
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "minidump/minidump_file_writer.h"
|
||||
#include "minidump/minidump_system_info_writer.h"
|
||||
#include "minidump/test/minidump_file_writer_test_util.h"
|
||||
#include "minidump/test/minidump_string_writer_test_util.h"
|
||||
#include "minidump/test/minidump_writable_test_util.h"
|
||||
@ -137,6 +138,64 @@ TEST(MinidumpThreadNameListWriter, OneThread) {
|
||||
kThreadName));
|
||||
}
|
||||
|
||||
TEST(MinidumpThreadNameListWriter, OneThreadWithLeadingPadding) {
|
||||
MinidumpFileWriter minidump_file_writer;
|
||||
|
||||
// Add a stream before the MINIDUMP_THREAD_NAME_LIST to ensure the thread name
|
||||
// MINIDUMP_STRING requires leading padding to align to a 4-byte boundary.
|
||||
auto system_info_writer = std::make_unique<MinidumpSystemInfoWriter>();
|
||||
system_info_writer->SetCSDVersion("");
|
||||
ASSERT_TRUE(minidump_file_writer.AddStream(std::move(system_info_writer)));
|
||||
|
||||
auto thread_list_writer = std::make_unique<MinidumpThreadNameListWriter>();
|
||||
|
||||
constexpr uint32_t kThreadID = 0x11111111;
|
||||
const std::string kThreadName = "ariadne";
|
||||
|
||||
auto thread_name_list_writer =
|
||||
std::make_unique<MinidumpThreadNameListWriter>();
|
||||
auto thread_name_writer = std::make_unique<MinidumpThreadNameWriter>();
|
||||
thread_name_writer->SetThreadId(kThreadID);
|
||||
thread_name_writer->SetThreadName(kThreadName);
|
||||
thread_name_list_writer->AddThreadName(std::move(thread_name_writer));
|
||||
|
||||
ASSERT_TRUE(
|
||||
minidump_file_writer.AddStream(std::move(thread_name_list_writer)));
|
||||
|
||||
StringFile string_file;
|
||||
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
|
||||
|
||||
ASSERT_GT(string_file.string().size(),
|
||||
sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
|
||||
sizeof(MINIDUMP_THREAD_NAME_LIST) +
|
||||
1 * sizeof(MINIDUMP_THREAD_NAME));
|
||||
|
||||
const uint32_t kExpectedStreams = 2;
|
||||
const MINIDUMP_DIRECTORY* directory;
|
||||
const MINIDUMP_HEADER* header =
|
||||
MinidumpHeaderAtStart(string_file.string(), &directory);
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, kExpectedStreams, 0));
|
||||
ASSERT_TRUE(directory);
|
||||
|
||||
ASSERT_EQ(directory[0].StreamType, kMinidumpStreamTypeSystemInfo);
|
||||
ASSERT_EQ(directory[1].StreamType, kMinidumpStreamTypeThreadNameList);
|
||||
|
||||
const MINIDUMP_THREAD_NAME_LIST* thread_name_list =
|
||||
MinidumpWritableAtLocationDescriptor<MINIDUMP_THREAD_NAME_LIST>(
|
||||
string_file.string(), directory[1].Location);
|
||||
ASSERT_TRUE(thread_name_list);
|
||||
|
||||
EXPECT_EQ(thread_name_list->NumberOfThreadNames, 1u);
|
||||
|
||||
MINIDUMP_THREAD_NAME expected = {};
|
||||
expected.ThreadId = kThreadID;
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(ExpectThreadName(&expected,
|
||||
&thread_name_list->ThreadNames[0],
|
||||
string_file.string(),
|
||||
kThreadName));
|
||||
}
|
||||
|
||||
TEST(MinidumpThreadNameListWriter, TwoThreads_DifferentNames) {
|
||||
MinidumpFileWriter minidump_file_writer;
|
||||
auto thread_list_writer = std::make_unique<MinidumpThreadNameListWriter>();
|
||||
|
@ -22,8 +22,10 @@
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/multiprocess_exec.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/fuchsia/scoped_task_suspend.h"
|
||||
|
||||
@ -32,6 +34,8 @@ namespace test {
|
||||
namespace {
|
||||
|
||||
TEST(ProcessReaderFuchsia, SelfBasic) {
|
||||
const ScopedSetThreadName scoped_set_thread_name("SelfBasic");
|
||||
|
||||
ProcessReaderFuchsia process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(*zx::process::self()));
|
||||
|
||||
@ -75,7 +79,7 @@ TEST(ProcessReaderFuchsia, SelfBasic) {
|
||||
ZX_OK);
|
||||
EXPECT_EQ(threads[0].id, info.koid);
|
||||
EXPECT_EQ(threads[0].state, ZX_THREAD_STATE_RUNNING);
|
||||
EXPECT_EQ(threads[0].name, "initial-thread");
|
||||
EXPECT_EQ(threads[0].name, "SelfBasic");
|
||||
}
|
||||
|
||||
constexpr char kTestMemory[] = "Read me from another process";
|
||||
@ -118,27 +122,44 @@ TEST(ProcessReaderFuchsia, ChildBasic) {
|
||||
test.Run();
|
||||
}
|
||||
|
||||
struct ThreadData {
|
||||
zx_handle_t port;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
void* SignalAndSleep(void* arg) {
|
||||
const ThreadData* thread_data = reinterpret_cast<const ThreadData*>(arg);
|
||||
const ScopedSetThreadName scoped_set_thread_name(thread_data->name);
|
||||
zx_port_packet_t packet = {};
|
||||
packet.type = ZX_PKT_TYPE_USER;
|
||||
zx_port_queue(*reinterpret_cast<zx_handle_t*>(arg), &packet);
|
||||
zx_port_queue(thread_data->port, &packet);
|
||||
zx_nanosleep(ZX_TIME_INFINITE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CRASHPAD_CHILD_TEST_MAIN(ProcessReaderChildThreadsTestMain) {
|
||||
const ScopedSetThreadName scoped_set_thread_name(
|
||||
"ProcessReaderChildThreadsTest-Main");
|
||||
|
||||
// Create 5 threads with stack sizes of 4096, 8192, ...
|
||||
zx_handle_t port;
|
||||
zx_status_t status = zx_port_create(0, &port);
|
||||
EXPECT_EQ(status, ZX_OK);
|
||||
|
||||
constexpr size_t kNumThreads = 5;
|
||||
struct ThreadData thread_data[kNumThreads] = {{0}};
|
||||
|
||||
for (size_t i = 0; i < kNumThreads; ++i) {
|
||||
thread_data[i] = {
|
||||
.port = port,
|
||||
.name = base::StringPrintf("ProcessReaderChildThreadsTest-%zu", i + 1),
|
||||
};
|
||||
pthread_attr_t attr;
|
||||
EXPECT_EQ(pthread_attr_init(&attr), 0);
|
||||
EXPECT_EQ(pthread_attr_setstacksize(&attr, (i + 1) * 4096), 0);
|
||||
pthread_t thread;
|
||||
EXPECT_EQ(pthread_create(&thread, &attr, &SignalAndSleep, &port), 0);
|
||||
EXPECT_EQ(pthread_create(&thread, &attr, &SignalAndSleep, &thread_data[i]),
|
||||
0);
|
||||
}
|
||||
|
||||
// Wait until all threads are ready.
|
||||
@ -179,10 +200,14 @@ class ThreadsChildTest : public MultiprocessExec {
|
||||
const auto& threads = process_reader.Threads();
|
||||
EXPECT_EQ(threads.size(), 6u);
|
||||
|
||||
EXPECT_EQ(threads[0].name, "ProcessReaderChildThreadsTest-main");
|
||||
|
||||
for (size_t i = 1; i < 6; ++i) {
|
||||
ASSERT_GT(threads[i].stack_regions.size(), 0u);
|
||||
EXPECT_GT(threads[i].stack_regions[0].size(), 0u);
|
||||
EXPECT_LE(threads[i].stack_regions[0].size(), i * 4096u);
|
||||
EXPECT_EQ(threads[i].name,
|
||||
base::StringPrintf("ProcessReaderChildThreadsTest-%zu", i));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ ThreadSnapshotFuchsia::ThreadSnapshotFuchsia()
|
||||
context_arch_(),
|
||||
context_(),
|
||||
stack_(),
|
||||
thread_name_(),
|
||||
thread_id_(ZX_KOID_INVALID),
|
||||
thread_specific_data_address_(0),
|
||||
initialized_() {}
|
||||
@ -60,6 +61,7 @@ bool ThreadSnapshotFuchsia::Initialize(
|
||||
// TODO(scottmg): Handle split stack by adding other parts to ExtraMemory().
|
||||
}
|
||||
|
||||
thread_name_ = thread.name;
|
||||
thread_id_ = thread.id;
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
@ -81,6 +83,11 @@ uint64_t ThreadSnapshotFuchsia::ThreadID() const {
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotFuchsia::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int ThreadSnapshotFuchsia::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
// There is not (currently) a suspend count for threads on Fuchsia.
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <stdint.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/cpu_context.h"
|
||||
#include "snapshot/fuchsia/process_reader_fuchsia.h"
|
||||
@ -56,6 +58,7 @@ class ThreadSnapshotFuchsia final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -71,6 +74,7 @@ class ThreadSnapshotFuchsia final : public ThreadSnapshot {
|
||||
#endif
|
||||
CPUContext context_;
|
||||
MemorySnapshotGeneric stack_;
|
||||
std::string thread_name_;
|
||||
zx_koid_t thread_id_;
|
||||
zx_vaddr_t thread_specific_data_address_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
@ -392,6 +392,7 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
|
||||
Key::kThreadContextMemoryRegionData, "string", 6));
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(writer->AddPropertyBytes(Key::kThreadName, "ariadne", 7));
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,6 +412,7 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
|
||||
uint64_t thread_id = 1;
|
||||
for (auto thread : threads) {
|
||||
EXPECT_EQ(thread->ThreadID(), thread_id);
|
||||
EXPECT_EQ(thread->ThreadName(), "ariadne");
|
||||
EXPECT_EQ(thread->SuspendCount(), 666);
|
||||
EXPECT_EQ(thread->Priority(), 5);
|
||||
EXPECT_EQ(thread->ThreadSpecificDataAddress(), thread_id++);
|
||||
|
@ -75,6 +75,7 @@ ThreadSnapshotIOSIntermediateDump::ThreadSnapshotIOSIntermediateDump()
|
||||
#endif
|
||||
context_(),
|
||||
stack_(),
|
||||
thread_name_(),
|
||||
thread_id_(0),
|
||||
thread_specific_data_address_(0),
|
||||
suspend_count_(0),
|
||||
@ -100,6 +101,7 @@ bool ThreadSnapshotIOSIntermediateDump::Initialize(
|
||||
GetDataValueFromMap(thread_data, Key::kThreadID, &thread_id_);
|
||||
GetDataValueFromMap(
|
||||
thread_data, Key::kThreadDataAddress, &thread_specific_data_address_);
|
||||
GetDataStringFromMap(thread_data, Key::kThreadName, &thread_name_);
|
||||
|
||||
#if defined(ARCH_CPU_X86_64)
|
||||
typedef x86_thread_state64_t thread_state_type;
|
||||
@ -218,6 +220,11 @@ uint64_t ThreadSnapshotIOSIntermediateDump::ThreadID() const {
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotIOSIntermediateDump::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int ThreadSnapshotIOSIntermediateDump::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return suspend_count_;
|
||||
|
@ -15,6 +15,8 @@
|
||||
#ifndef CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_THREAD_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_
|
||||
#define CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_THREAD_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/cpu_context.h"
|
||||
#include "snapshot/ios/memory_snapshot_ios_intermediate_dump.h"
|
||||
@ -49,6 +51,7 @@ class ThreadSnapshotIOSIntermediateDump final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -65,6 +68,7 @@ class ThreadSnapshotIOSIntermediateDump final : public ThreadSnapshot {
|
||||
CPUContext context_;
|
||||
std::vector<uint8_t> exception_stack_memory_;
|
||||
MemorySnapshotIOSIntermediateDump stack_;
|
||||
std::string thread_name_;
|
||||
uint64_t thread_id_;
|
||||
uint64_t thread_specific_data_address_;
|
||||
int suspend_count_;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/linux/debug_rendezvous.h"
|
||||
#include "util/linux/auxiliary_vector.h"
|
||||
@ -52,6 +53,7 @@ ProcessReaderLinux::Thread::Thread()
|
||||
: thread_info(),
|
||||
stack_region_address(0),
|
||||
stack_region_size(0),
|
||||
name(),
|
||||
tid(-1),
|
||||
static_priority(-1),
|
||||
nice_value(-1) {}
|
||||
@ -64,6 +66,23 @@ bool ProcessReaderLinux::Thread::InitializePtrace(
|
||||
return false;
|
||||
}
|
||||
|
||||
// From man proc(5):
|
||||
//
|
||||
// /proc/[pid]/comm (since Linux 2.6.33)
|
||||
//
|
||||
// Different threads in the same process may have different comm values,
|
||||
// accessible via /proc/[pid]/task/[tid]/comm.
|
||||
const std::string path = base::StringPrintf(
|
||||
"/proc/%d/task/%d/comm", connection->GetProcessID(), tid);
|
||||
if (connection->ReadFileContents(base::FilePath(path), &name)) {
|
||||
if (!name.empty() && name.back() == '\n') {
|
||||
// Remove the final newline character.
|
||||
name.pop_back();
|
||||
}
|
||||
} else {
|
||||
// Continue on without the thread name.
|
||||
}
|
||||
|
||||
// TODO(jperaza): Collect scheduling priorities via the broker when they can't
|
||||
// be collected directly.
|
||||
have_priorities = false;
|
||||
|
@ -60,6 +60,7 @@ class ProcessReaderLinux {
|
||||
ThreadInfo thread_info;
|
||||
LinuxVMAddress stack_region_address;
|
||||
LinuxVMSize stack_region_size;
|
||||
std::string name;
|
||||
pid_t tid;
|
||||
int sched_policy;
|
||||
int static_priority;
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "test/linux/get_tls.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "test/scoped_module_handle.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/file_writer.h"
|
||||
@ -169,7 +170,9 @@ class TestThreadPool {
|
||||
|
||||
void StartThreads(size_t thread_count, size_t stack_size = 0) {
|
||||
for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
|
||||
threads_.push_back(std::make_unique<Thread>());
|
||||
const std::string thread_name =
|
||||
base::StringPrintf("ThreadPool-%zu", thread_index);
|
||||
threads_.push_back(std::make_unique<Thread>(thread_name));
|
||||
Thread* thread = threads_.back().get();
|
||||
|
||||
pthread_attr_t attr;
|
||||
@ -211,22 +214,26 @@ class TestThreadPool {
|
||||
}
|
||||
|
||||
pid_t GetThreadExpectation(size_t thread_index,
|
||||
ThreadExpectation* expectation) {
|
||||
ThreadExpectation* expectation,
|
||||
std::string* thread_name_expectation) {
|
||||
CHECK_LT(thread_index, threads_.size());
|
||||
|
||||
const Thread* thread = threads_[thread_index].get();
|
||||
*expectation = thread->expectation;
|
||||
*thread_name_expectation = thread->name;
|
||||
return thread->tid;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Thread {
|
||||
Thread()
|
||||
explicit Thread(const std::string& name)
|
||||
: pthread(),
|
||||
expectation(),
|
||||
ready_semaphore(0),
|
||||
exit_semaphore(0),
|
||||
tid(-1) {}
|
||||
tid(-1),
|
||||
name(name) {
|
||||
}
|
||||
~Thread() {}
|
||||
|
||||
pthread_t pthread;
|
||||
@ -235,10 +242,12 @@ class TestThreadPool {
|
||||
Semaphore ready_semaphore;
|
||||
Semaphore exit_semaphore;
|
||||
pid_t tid;
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
static void* ThreadMain(void* argument) {
|
||||
Thread* thread = static_cast<Thread*>(argument);
|
||||
const ScopedSetThreadName scoped_set_thread_name(thread->name);
|
||||
|
||||
CHECK_EQ(setpriority(PRIO_PROCESS, 0, thread->expectation.nice_value), 0)
|
||||
<< ErrnoMessage("setpriority");
|
||||
@ -260,20 +269,24 @@ class TestThreadPool {
|
||||
};
|
||||
|
||||
using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>;
|
||||
using ThreadNameMap = std::map<pid_t, std::string>;
|
||||
|
||||
void ExpectThreads(const ThreadMap& thread_map,
|
||||
const ThreadNameMap& thread_name_map,
|
||||
const std::vector<ProcessReaderLinux::Thread>& threads,
|
||||
PtraceConnection* connection) {
|
||||
ASSERT_EQ(threads.size(), thread_map.size());
|
||||
ASSERT_EQ(threads.size(), thread_name_map.size());
|
||||
|
||||
MemoryMap memory_map;
|
||||
ASSERT_TRUE(memory_map.Initialize(connection));
|
||||
|
||||
for (const auto& thread : threads) {
|
||||
SCOPED_TRACE(
|
||||
base::StringPrintf("Thread id %d, tls 0x%" PRIx64
|
||||
base::StringPrintf("Thread id %d, name %s, tls 0x%" PRIx64
|
||||
", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64,
|
||||
thread.tid,
|
||||
thread.name.c_str(),
|
||||
thread.thread_info.thread_specific_data_address,
|
||||
thread.stack_region_address,
|
||||
thread.stack_region_size));
|
||||
@ -306,6 +319,10 @@ void ExpectThreads(const ThreadMap& thread_map,
|
||||
EXPECT_EQ(thread.sched_policy, iterator->second.sched_policy);
|
||||
EXPECT_EQ(thread.static_priority, iterator->second.static_priority);
|
||||
EXPECT_EQ(thread.nice_value, iterator->second.nice_value);
|
||||
|
||||
const auto& thread_name_iterator = thread_name_map.find(thread.tid);
|
||||
ASSERT_NE(thread_name_iterator, thread_name_map.end());
|
||||
EXPECT_EQ(thread.name, thread_name_iterator->second);
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +339,7 @@ class ChildThreadTest : public Multiprocess {
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
ThreadMap thread_map;
|
||||
ThreadNameMap thread_name_map;
|
||||
for (size_t thread_index = 0; thread_index < kThreadCount + 1;
|
||||
++thread_index) {
|
||||
pid_t tid;
|
||||
@ -331,6 +349,14 @@ class ChildThreadTest : public Multiprocess {
|
||||
CheckedReadFileExactly(
|
||||
ReadPipeHandle(), &expectation, sizeof(expectation));
|
||||
thread_map[tid] = expectation;
|
||||
|
||||
std::string::size_type thread_name_length;
|
||||
CheckedReadFileExactly(
|
||||
ReadPipeHandle(), &thread_name_length, sizeof(thread_name_length));
|
||||
std::string thread_name(thread_name_length, '\0');
|
||||
CheckedReadFileExactly(
|
||||
ReadPipeHandle(), thread_name.data(), thread_name_length);
|
||||
thread_name_map[tid] = thread_name;
|
||||
}
|
||||
|
||||
DirectPtraceConnection connection;
|
||||
@ -340,19 +366,22 @@ class ChildThreadTest : public Multiprocess {
|
||||
ASSERT_TRUE(process_reader.Initialize(&connection));
|
||||
const std::vector<ProcessReaderLinux::Thread>& threads =
|
||||
process_reader.Threads();
|
||||
ExpectThreads(thread_map, threads, &connection);
|
||||
ExpectThreads(thread_map, thread_name_map, threads, &connection);
|
||||
}
|
||||
|
||||
void MultiprocessChild() override {
|
||||
TestThreadPool thread_pool;
|
||||
thread_pool.StartThreads(kThreadCount, stack_size_);
|
||||
|
||||
const std::string current_thread_name = "MultiprocChild";
|
||||
const ScopedSetThreadName scoped_set_thread_name(current_thread_name);
|
||||
|
||||
TestThreadPool::ThreadExpectation expectation;
|
||||
#if defined(MEMORY_SANITIZER)
|
||||
// memset() + re-initialization is required to zero padding bytes for MSan.
|
||||
memset(&expectation, 0, sizeof(expectation));
|
||||
#endif // defined(MEMORY_SANITIZER)
|
||||
expectation = {};
|
||||
expectation = {0};
|
||||
expectation.tls = GetTLS();
|
||||
expectation.stack_address = reinterpret_cast<LinuxVMAddress>(&thread_pool);
|
||||
|
||||
@ -373,11 +402,28 @@ class ChildThreadTest : public Multiprocess {
|
||||
|
||||
CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
|
||||
CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
|
||||
const std::string::size_type current_thread_name_length =
|
||||
current_thread_name.length();
|
||||
CheckedWriteFile(WritePipeHandle(),
|
||||
¤t_thread_name_length,
|
||||
sizeof(current_thread_name_length));
|
||||
CheckedWriteFile(WritePipeHandle(),
|
||||
current_thread_name.data(),
|
||||
current_thread_name_length);
|
||||
|
||||
for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) {
|
||||
tid = thread_pool.GetThreadExpectation(thread_index, &expectation);
|
||||
std::string thread_name_expectation;
|
||||
tid = thread_pool.GetThreadExpectation(
|
||||
thread_index, &expectation, &thread_name_expectation);
|
||||
CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
|
||||
CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
|
||||
const std::string::size_type thread_name_length =
|
||||
thread_name_expectation.length();
|
||||
CheckedWriteFile(
|
||||
WritePipeHandle(), &thread_name_length, sizeof(thread_name_length));
|
||||
CheckedWriteFile(WritePipeHandle(),
|
||||
thread_name_expectation.data(),
|
||||
thread_name_length);
|
||||
}
|
||||
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
|
@ -133,6 +133,7 @@ ThreadSnapshotLinux::ThreadSnapshotLinux()
|
||||
context_(),
|
||||
stack_(),
|
||||
thread_specific_data_address_(0),
|
||||
thread_name_(),
|
||||
thread_id_(-1),
|
||||
priority_(-1),
|
||||
initialized_() {}
|
||||
@ -200,6 +201,7 @@ bool ThreadSnapshotLinux::Initialize(
|
||||
thread_specific_data_address_ =
|
||||
thread.thread_info.thread_specific_data_address;
|
||||
|
||||
thread_name_ = thread.name;
|
||||
thread_id_ = thread.tid;
|
||||
|
||||
priority_ =
|
||||
@ -234,6 +236,11 @@ uint64_t ThreadSnapshotLinux::ThreadID() const {
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotLinux::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int ThreadSnapshotLinux::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return 0;
|
||||
|
@ -57,6 +57,7 @@ class ThreadSnapshotLinux final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -80,6 +81,7 @@ class ThreadSnapshotLinux final : public ThreadSnapshot {
|
||||
CPUContext context_;
|
||||
MemorySnapshotGeneric stack_;
|
||||
LinuxVMAddress thread_specific_data_address_;
|
||||
std::string thread_name_;
|
||||
pid_t thread_id_;
|
||||
int priority_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
@ -75,6 +75,7 @@ ProcessReaderMac::Thread::Thread()
|
||||
: thread_context(),
|
||||
float_context(),
|
||||
debug_context(),
|
||||
name(),
|
||||
id(0),
|
||||
stack_region_address(0),
|
||||
stack_region_size(0),
|
||||
@ -365,6 +366,20 @@ void ProcessReaderMac::InitializeThreads() {
|
||||
thread.thread_specific_data_address = identifier_info.thread_handle;
|
||||
}
|
||||
|
||||
thread_extended_info extended_info;
|
||||
count = THREAD_EXTENDED_INFO_COUNT;
|
||||
kr = thread_info(thread.port,
|
||||
THREAD_EXTENDED_INFO,
|
||||
reinterpret_cast<thread_info_t>(&extended_info),
|
||||
&count);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
MACH_LOG(WARNING, kr) << "thread_info(THREAD_EXTENDED_INFO)";
|
||||
} else {
|
||||
thread.name.assign(
|
||||
extended_info.pth_name,
|
||||
strnlen(extended_info.pth_name, sizeof(extended_info.pth_name)));
|
||||
}
|
||||
|
||||
thread_precedence_policy precedence;
|
||||
count = THREAD_PRECEDENCE_POLICY_COUNT;
|
||||
boolean_t get_default = FALSE;
|
||||
|
@ -75,6 +75,7 @@ class ProcessReaderMac {
|
||||
ThreadContext thread_context;
|
||||
FloatContext float_context;
|
||||
DebugContext debug_context;
|
||||
std::string name;
|
||||
uint64_t id;
|
||||
mach_vm_address_t stack_region_address;
|
||||
mach_vm_size_t stack_region_size;
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "test/mac/dyld.h"
|
||||
#include "test/mac/mach_errors.h"
|
||||
#include "test/mac/mach_multiprocess.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/mac/mac_util.h"
|
||||
#include "util/mach/mach_extensions.h"
|
||||
@ -139,6 +140,9 @@ uint64_t PthreadToThreadID(pthread_t pthread) {
|
||||
}
|
||||
|
||||
TEST(ProcessReaderMac, SelfOneThread) {
|
||||
const ScopedSetThreadName scoped_set_thread_name(
|
||||
"ProcessReaderMac/SelfOneThread");
|
||||
|
||||
ProcessReaderMac process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
|
||||
|
||||
@ -151,6 +155,7 @@ TEST(ProcessReaderMac, SelfOneThread) {
|
||||
ASSERT_GE(threads.size(), 1u);
|
||||
|
||||
EXPECT_EQ(threads[0].id, PthreadToThreadID(pthread_self()));
|
||||
EXPECT_EQ(threads[0].name, "ProcessReaderMac/SelfOneThread");
|
||||
|
||||
thread_t thread_self = MachThreadSelf();
|
||||
EXPECT_EQ(threads[0].port, thread_self);
|
||||
@ -163,9 +168,11 @@ class TestThreadPool {
|
||||
struct ThreadExpectation {
|
||||
mach_vm_address_t stack_address;
|
||||
int suspend_count;
|
||||
std::string thread_name;
|
||||
};
|
||||
|
||||
TestThreadPool() : thread_infos_() {}
|
||||
TestThreadPool(const std::string& thread_name_prefix)
|
||||
: thread_infos_(), thread_name_prefix_(thread_name_prefix) {}
|
||||
|
||||
TestThreadPool(const TestThreadPool&) = delete;
|
||||
TestThreadPool& operator=(const TestThreadPool&) = delete;
|
||||
@ -199,7 +206,10 @@ class TestThreadPool {
|
||||
ASSERT_TRUE(thread_infos_.empty());
|
||||
|
||||
for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
|
||||
thread_infos_.push_back(std::make_unique<ThreadInfo>());
|
||||
std::string thread_name = base::StringPrintf(
|
||||
"%s-%zu", thread_name_prefix_.c_str(), thread_index);
|
||||
thread_infos_.push_back(
|
||||
std::make_unique<ThreadInfo>(std::move(thread_name)));
|
||||
ThreadInfo* thread_info = thread_infos_.back().get();
|
||||
|
||||
int rv = pthread_create(
|
||||
@ -235,18 +245,20 @@ class TestThreadPool {
|
||||
const auto& thread_info = thread_infos_[thread_index];
|
||||
expectation->stack_address = thread_info->stack_address;
|
||||
expectation->suspend_count = thread_info->suspend_count;
|
||||
expectation->thread_name = thread_info->thread_name;
|
||||
|
||||
return PthreadToThreadID(thread_info->pthread);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ThreadInfo {
|
||||
ThreadInfo()
|
||||
ThreadInfo(const std::string& thread_name)
|
||||
: pthread(nullptr),
|
||||
stack_address(0),
|
||||
ready_semaphore(0),
|
||||
exit_semaphore(0),
|
||||
suspend_count(0) {}
|
||||
suspend_count(0),
|
||||
thread_name(thread_name) {}
|
||||
|
||||
~ThreadInfo() {}
|
||||
|
||||
@ -270,10 +282,14 @@ class TestThreadPool {
|
||||
|
||||
// The thread’s suspend count.
|
||||
int suspend_count;
|
||||
|
||||
// The thread's name.
|
||||
const std::string thread_name;
|
||||
};
|
||||
|
||||
static void* ThreadMain(void* argument) {
|
||||
ThreadInfo* thread_info = static_cast<ThreadInfo*>(argument);
|
||||
const ScopedSetThreadName scoped_set_thread_name(thread_info->thread_name);
|
||||
|
||||
thread_info->stack_address =
|
||||
FromPointerCast<mach_vm_address_t>(&thread_info);
|
||||
@ -293,6 +309,9 @@ class TestThreadPool {
|
||||
// This is a vector of pointers because the address of a ThreadInfo object is
|
||||
// passed to each thread’s ThreadMain(), so they cannot move around in memory.
|
||||
std::vector<std::unique_ptr<ThreadInfo>> thread_infos_;
|
||||
|
||||
// Prefix to use for each thread's name, suffixed with "-$threadindex".
|
||||
const std::string thread_name_prefix_;
|
||||
};
|
||||
|
||||
using ThreadMap = std::map<uint64_t, TestThreadPool::ThreadExpectation>;
|
||||
@ -328,6 +347,7 @@ void ExpectSeveralThreads(ThreadMap* thread_map,
|
||||
EXPECT_LT(iterator->second.stack_address, thread_stack_region_end);
|
||||
|
||||
EXPECT_EQ(thread.suspend_count, iterator->second.suspend_count);
|
||||
EXPECT_EQ(thread.name, iterator->second.thread_name);
|
||||
|
||||
// Remove the thread from the expectation map since it’s already been
|
||||
// found. This makes it easy to check for duplicate thread IDs, and makes
|
||||
@ -374,7 +394,7 @@ TEST(ProcessReaderMac, SelfSeveralThreads) {
|
||||
ProcessReaderMac process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
|
||||
|
||||
TestThreadPool thread_pool;
|
||||
TestThreadPool thread_pool("SelfSeveralThreads");
|
||||
constexpr size_t kChildThreads = 16;
|
||||
ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(kChildThreads));
|
||||
|
||||
@ -392,6 +412,8 @@ TEST(ProcessReaderMac, SelfSeveralThreads) {
|
||||
// There can’t be any duplicate thread IDs.
|
||||
EXPECT_EQ(thread_map.count(thread_id), 0u);
|
||||
|
||||
expectation.thread_name =
|
||||
base::StringPrintf("SelfSeveralThreads-%zu", thread_index);
|
||||
thread_map[thread_id] = expectation;
|
||||
}
|
||||
|
||||
@ -431,8 +453,11 @@ uint64_t GetThreadID() {
|
||||
|
||||
class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
public:
|
||||
explicit ProcessReaderThreadedChild(size_t thread_count)
|
||||
: MachMultiprocess(), thread_count_(thread_count) {}
|
||||
explicit ProcessReaderThreadedChild(const std::string thread_name_prefix,
|
||||
size_t thread_count)
|
||||
: MachMultiprocess(),
|
||||
thread_name_prefix_(thread_name_prefix),
|
||||
thread_count_(thread_count) {}
|
||||
|
||||
ProcessReaderThreadedChild(const ProcessReaderThreadedChild&) = delete;
|
||||
ProcessReaderThreadedChild& operator=(const ProcessReaderThreadedChild&) =
|
||||
@ -463,6 +488,15 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
CheckedReadFileExactly(read_handle,
|
||||
&expectation.suspend_count,
|
||||
sizeof(expectation.suspend_count));
|
||||
std::string::size_type expected_thread_name_length;
|
||||
CheckedReadFileExactly(read_handle,
|
||||
&expected_thread_name_length,
|
||||
sizeof(expected_thread_name_length));
|
||||
std::string expected_thread_name(expected_thread_name_length, '\0');
|
||||
CheckedReadFileExactly(read_handle,
|
||||
expected_thread_name.data(),
|
||||
expected_thread_name_length);
|
||||
expectation.thread_name = expected_thread_name;
|
||||
|
||||
// There can’t be any duplicate thread IDs.
|
||||
EXPECT_EQ(thread_map.count(thread_id), 0u);
|
||||
@ -479,9 +513,13 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
}
|
||||
|
||||
void MachMultiprocessChild() override {
|
||||
TestThreadPool thread_pool;
|
||||
TestThreadPool thread_pool(thread_name_prefix_);
|
||||
ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(thread_count_));
|
||||
|
||||
const std::string current_thread_name(base::StringPrintf(
|
||||
"%s-MachMultiprocessChild", thread_name_prefix_.c_str()));
|
||||
const ScopedSetThreadName scoped_set_thread_name(current_thread_name);
|
||||
|
||||
FileHandle write_handle = WritePipeHandle();
|
||||
|
||||
// This thread isn’t part of the thread pool, but the parent will be able
|
||||
@ -500,6 +538,13 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
CheckedWriteFile(write_handle,
|
||||
&expectation.suspend_count,
|
||||
sizeof(expectation.suspend_count));
|
||||
const std::string::size_type current_thread_name_length =
|
||||
current_thread_name.length();
|
||||
CheckedWriteFile(write_handle,
|
||||
¤t_thread_name_length,
|
||||
sizeof(current_thread_name_length));
|
||||
CheckedWriteFile(
|
||||
write_handle, current_thread_name.data(), current_thread_name_length);
|
||||
|
||||
// Write an entry for everything in the thread pool.
|
||||
for (size_t thread_index = 0; thread_index < thread_count_;
|
||||
@ -513,6 +558,16 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
CheckedWriteFile(write_handle,
|
||||
&expectation.suspend_count,
|
||||
sizeof(expectation.suspend_count));
|
||||
const std::string thread_pool_thread_name = base::StringPrintf(
|
||||
"%s-%zu", thread_name_prefix_.c_str(), thread_index);
|
||||
const std::string::size_type thread_pool_thread_name_length =
|
||||
thread_pool_thread_name.length();
|
||||
CheckedWriteFile(write_handle,
|
||||
&thread_pool_thread_name_length,
|
||||
sizeof(thread_pool_thread_name_length));
|
||||
CheckedWriteFile(write_handle,
|
||||
thread_pool_thread_name.data(),
|
||||
thread_pool_thread_name_length);
|
||||
}
|
||||
|
||||
// Wait for the parent to signal that it’s OK to exit by closing its end of
|
||||
@ -520,19 +575,22 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
}
|
||||
|
||||
const std::string thread_name_prefix_;
|
||||
size_t thread_count_;
|
||||
};
|
||||
|
||||
TEST(ProcessReaderMac, ChildOneThread) {
|
||||
// The main thread plus zero child threads equals one thread.
|
||||
constexpr size_t kChildThreads = 0;
|
||||
ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
|
||||
ProcessReaderThreadedChild process_reader_threaded_child("ChildOneThread",
|
||||
kChildThreads);
|
||||
process_reader_threaded_child.Run();
|
||||
}
|
||||
|
||||
TEST(ProcessReaderMac, ChildSeveralThreads) {
|
||||
constexpr size_t kChildThreads = 64;
|
||||
ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
|
||||
ProcessReaderThreadedChild process_reader_threaded_child(
|
||||
"ChildSeveralThreads", kChildThreads);
|
||||
process_reader_threaded_child.Run();
|
||||
}
|
||||
|
||||
|
@ -26,13 +26,13 @@ ThreadSnapshotMac::ThreadSnapshotMac()
|
||||
context_union_(),
|
||||
context_(),
|
||||
stack_(),
|
||||
thread_name_(),
|
||||
thread_id_(0),
|
||||
thread_specific_data_address_(0),
|
||||
thread_(MACH_PORT_NULL),
|
||||
suspend_count_(0),
|
||||
priority_(0),
|
||||
initialized_() {
|
||||
}
|
||||
initialized_() {}
|
||||
|
||||
ThreadSnapshotMac::~ThreadSnapshotMac() {
|
||||
}
|
||||
@ -44,6 +44,7 @@ bool ThreadSnapshotMac::Initialize(
|
||||
|
||||
thread_ = process_reader_thread.port;
|
||||
thread_id_ = process_reader_thread.id;
|
||||
thread_name_ = process_reader_thread.name;
|
||||
suspend_count_ = process_reader_thread.suspend_count;
|
||||
priority_ = process_reader_thread.priority;
|
||||
thread_specific_data_address_ =
|
||||
@ -108,6 +109,11 @@ uint64_t ThreadSnapshotMac::ThreadID() const {
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotMac::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int ThreadSnapshotMac::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return suspend_count_;
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include <mach/mach.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "snapshot/cpu_context.h"
|
||||
#include "snapshot/mac/process_reader_mac.h"
|
||||
@ -60,6 +62,7 @@ class ThreadSnapshotMac final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -78,6 +81,7 @@ class ThreadSnapshotMac final : public ThreadSnapshot {
|
||||
} context_union_;
|
||||
CPUContext context_;
|
||||
MemorySnapshotGeneric stack_;
|
||||
std::string thread_name_;
|
||||
uint64_t thread_id_;
|
||||
uint64_t thread_specific_data_address_;
|
||||
thread_t thread_;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "minidump/minidump_extensions.h"
|
||||
#include "snapshot/memory_map_region_snapshot.h"
|
||||
#include "snapshot/minidump/minidump_simple_string_dictionary_reader.h"
|
||||
#include "snapshot/minidump/minidump_string_reader.h"
|
||||
#include "util/file/file_io.h"
|
||||
|
||||
namespace crashpad {
|
||||
@ -576,12 +577,16 @@ bool ProcessSnapshotMinidump::InitializeThreads() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeThreadNames()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
|
||||
const RVA thread_rva = stream_it->second->Rva + sizeof(thread_count) +
|
||||
thread_index * sizeof(MINIDUMP_THREAD);
|
||||
|
||||
auto thread = std::make_unique<internal::ThreadSnapshotMinidump>();
|
||||
if (!thread->Initialize(file_reader_, thread_rva, arch_)) {
|
||||
if (!thread->Initialize(file_reader_, thread_rva, arch_, thread_names_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -591,6 +596,59 @@ bool ProcessSnapshotMinidump::InitializeThreads() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessSnapshotMinidump::InitializeThreadNames() {
|
||||
const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadNameList);
|
||||
if (stream_it == stream_map_.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_NAME_LIST)) {
|
||||
LOG(ERROR) << "thread_name_list size mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_reader_->SeekSet(stream_it->second->Rva)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t thread_name_count;
|
||||
if (!file_reader_->ReadExactly(&thread_name_count,
|
||||
sizeof(thread_name_count))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(MINIDUMP_THREAD_NAME_LIST) +
|
||||
thread_name_count * sizeof(MINIDUMP_THREAD_NAME) !=
|
||||
stream_it->second->DataSize) {
|
||||
LOG(ERROR) << "thread_name_list size mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t thread_name_index = 0; thread_name_index < thread_name_count;
|
||||
++thread_name_index) {
|
||||
const RVA thread_name_rva =
|
||||
stream_it->second->Rva + sizeof(thread_name_count) +
|
||||
thread_name_index * sizeof(MINIDUMP_THREAD_NAME);
|
||||
if (!file_reader_->SeekSet(thread_name_rva)) {
|
||||
return false;
|
||||
}
|
||||
MINIDUMP_THREAD_NAME minidump_thread_name;
|
||||
if (!file_reader_->ReadExactly(&minidump_thread_name,
|
||||
sizeof(minidump_thread_name))) {
|
||||
return false;
|
||||
}
|
||||
std::string name;
|
||||
if (!internal::ReadMinidumpUTF16String(
|
||||
file_reader_, minidump_thread_name.RvaOfThreadName, &name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
thread_names_.emplace(minidump_thread_name.ThreadId, std::move(name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessSnapshotMinidump::InitializeSystemSnapshot() {
|
||||
const auto& stream_it = stream_map_.find(kMinidumpStreamTypeSystemInfo);
|
||||
if (stream_it == stream_map_.end()) {
|
||||
|
@ -112,6 +112,10 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
|
||||
// Initialize().
|
||||
bool InitializeThreads();
|
||||
|
||||
// Initializes data carried in a MINIDUMP_THREAD_NAME_LIST stream on behalf of
|
||||
// Initialize().
|
||||
bool InitializeThreadNames();
|
||||
|
||||
// Initializes data carried in a MINIDUMP_MEMORY_INFO_LIST stream on behalf of
|
||||
// Initialize().
|
||||
bool InitializeMemoryInfo();
|
||||
@ -147,6 +151,7 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
|
||||
std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
|
||||
std::vector<std::unique_ptr<internal::ModuleSnapshotMinidump>> modules_;
|
||||
std::vector<std::unique_ptr<internal::ThreadSnapshotMinidump>> threads_;
|
||||
std::map<uint32_t, std::string> thread_names_;
|
||||
std::vector<UnloadedModuleSnapshot> unloaded_modules_;
|
||||
std::vector<std::unique_ptr<internal::MemoryMapRegionSnapshotMinidump>>
|
||||
mem_regions_;
|
||||
|
@ -728,6 +728,104 @@ TEST(ProcessSnapshotMinidump, Threads) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ProcessSnapshotMinidump, ThreadsWithNames) {
|
||||
StringFile string_file;
|
||||
|
||||
MINIDUMP_HEADER header = {};
|
||||
EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
constexpr uint32_t kMinidumpThreadCount = 4;
|
||||
constexpr uint32_t kBaseThreadId = 42;
|
||||
|
||||
const std::string thread_names[kMinidumpThreadCount] = {
|
||||
"ariadne",
|
||||
"theseus",
|
||||
"pasiphae",
|
||||
"minos",
|
||||
};
|
||||
|
||||
RVA64 thread_name_rva64s[kMinidumpThreadCount];
|
||||
for (uint32_t i = 0; i < kMinidumpThreadCount; i++) {
|
||||
thread_name_rva64s[i] = static_cast<RVA64>(string_file.SeekGet());
|
||||
auto name16 = base::UTF8ToUTF16(thread_names[i]);
|
||||
uint32_t size =
|
||||
base::checked_cast<uint32_t>(sizeof(name16[0]) * name16.size());
|
||||
EXPECT_TRUE(string_file.Write(&size, sizeof(size)));
|
||||
EXPECT_TRUE(string_file.Write(&name16[0], size));
|
||||
}
|
||||
|
||||
MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
|
||||
minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
|
||||
minidump_thread_list_directory.Location.DataSize =
|
||||
sizeof(MINIDUMP_THREAD_LIST) +
|
||||
kMinidumpThreadCount * sizeof(MINIDUMP_THREAD);
|
||||
minidump_thread_list_directory.Location.Rva =
|
||||
static_cast<RVA>(string_file.SeekGet());
|
||||
|
||||
// Fields in MINIDUMP_THREAD_LIST.
|
||||
EXPECT_TRUE(
|
||||
string_file.Write(&kMinidumpThreadCount, sizeof(kMinidumpThreadCount)));
|
||||
for (uint32_t minidump_thread_index = 0;
|
||||
minidump_thread_index < kMinidumpThreadCount;
|
||||
++minidump_thread_index) {
|
||||
MINIDUMP_THREAD minidump_thread = {};
|
||||
minidump_thread.ThreadId = kBaseThreadId + minidump_thread_index;
|
||||
EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
|
||||
}
|
||||
|
||||
header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
|
||||
EXPECT_TRUE(string_file.Write(&minidump_thread_list_directory,
|
||||
sizeof(minidump_thread_list_directory)));
|
||||
|
||||
MINIDUMP_DIRECTORY minidump_thread_name_list_directory = {};
|
||||
minidump_thread_name_list_directory.StreamType =
|
||||
kMinidumpStreamTypeThreadNameList;
|
||||
minidump_thread_name_list_directory.Location.DataSize =
|
||||
sizeof(MINIDUMP_THREAD_NAME_LIST) +
|
||||
kMinidumpThreadCount * sizeof(MINIDUMP_THREAD_NAME);
|
||||
minidump_thread_name_list_directory.Location.Rva =
|
||||
static_cast<RVA>(string_file.SeekGet());
|
||||
|
||||
// Fields in MINIDUMP_THREAD_NAME_LIST.
|
||||
EXPECT_TRUE(
|
||||
string_file.Write(&kMinidumpThreadCount, sizeof(kMinidumpThreadCount)));
|
||||
for (uint32_t minidump_thread_index = 0;
|
||||
minidump_thread_index < kMinidumpThreadCount;
|
||||
++minidump_thread_index) {
|
||||
MINIDUMP_THREAD_NAME minidump_thread_name = {0};
|
||||
minidump_thread_name.ThreadId = kBaseThreadId + minidump_thread_index;
|
||||
minidump_thread_name.RvaOfThreadName =
|
||||
thread_name_rva64s[minidump_thread_index];
|
||||
EXPECT_TRUE(
|
||||
string_file.Write(&minidump_thread_name, sizeof(minidump_thread_name)));
|
||||
}
|
||||
|
||||
header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
|
||||
ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory,
|
||||
sizeof(minidump_thread_list_directory)));
|
||||
ASSERT_TRUE(string_file.Write(&minidump_thread_name_list_directory,
|
||||
sizeof(minidump_thread_name_list_directory)));
|
||||
|
||||
header.Signature = MINIDUMP_SIGNATURE;
|
||||
header.Version = MINIDUMP_VERSION;
|
||||
header.NumberOfStreams = 2;
|
||||
EXPECT_TRUE(string_file.SeekSet(0));
|
||||
EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
ProcessSnapshotMinidump process_snapshot;
|
||||
EXPECT_TRUE(process_snapshot.Initialize(&string_file));
|
||||
|
||||
std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
|
||||
ASSERT_EQ(threads.size(), kMinidumpThreadCount);
|
||||
|
||||
size_t idx = 0;
|
||||
for (const auto& thread : threads) {
|
||||
EXPECT_EQ(thread->ThreadID(), kBaseThreadId + idx);
|
||||
EXPECT_EQ(thread->ThreadName(), thread_names[idx]);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ProcessSnapshotMinidump, System) {
|
||||
const char* cpu_info = "GenuineIntel";
|
||||
const uint32_t* cpu_info_bytes = reinterpret_cast<const uint32_t*>(cpu_info);
|
||||
|
@ -26,15 +26,18 @@ namespace internal {
|
||||
ThreadSnapshotMinidump::ThreadSnapshotMinidump()
|
||||
: ThreadSnapshot(),
|
||||
minidump_thread_(),
|
||||
thread_name_(),
|
||||
context_(),
|
||||
stack_(),
|
||||
initialized_() {}
|
||||
|
||||
ThreadSnapshotMinidump::~ThreadSnapshotMinidump() {}
|
||||
|
||||
bool ThreadSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
|
||||
RVA minidump_thread_rva,
|
||||
CPUArchitecture arch) {
|
||||
bool ThreadSnapshotMinidump::Initialize(
|
||||
FileReaderInterface* file_reader,
|
||||
RVA minidump_thread_rva,
|
||||
CPUArchitecture arch,
|
||||
const std::map<uint32_t, std::string>& thread_names) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
std::vector<unsigned char> minidump_context;
|
||||
|
||||
@ -67,6 +70,10 @@ bool ThreadSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
|
||||
if (!stack_.Initialize(file_reader, stack_info_location)) {
|
||||
return false;
|
||||
}
|
||||
const auto thread_name_iter = thread_names.find(minidump_thread_.ThreadId);
|
||||
if (thread_name_iter != thread_names.end()) {
|
||||
thread_name_ = thread_name_iter->second;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
@ -77,6 +84,11 @@ uint64_t ThreadSnapshotMinidump::ThreadID() const {
|
||||
return minidump_thread_.ThreadId;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotMinidump::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int ThreadSnapshotMinidump::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return minidump_thread_.SuspendCount;
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "minidump/minidump_extensions.h"
|
||||
#include "snapshot/cpu_context.h"
|
||||
#include "snapshot/minidump/memory_snapshot_minidump.h"
|
||||
@ -46,16 +48,20 @@ class ThreadSnapshotMinidump : public ThreadSnapshot {
|
||||
//! the thread’s MINIDUMP_THREAD structure is located.
|
||||
//! \param[in] arch The architecture of the system this thread is running on.
|
||||
//! Used to decode CPU Context.
|
||||
//! \param[in] thread_names Map from thread ID to thread name previously read
|
||||
//! from the minidump's MINIDUMP_THREAD_NAME_LIST.
|
||||
//!
|
||||
//! \return `true` if the snapshot could be created, `false` otherwise with
|
||||
//! an appropriate message logged.
|
||||
bool Initialize(FileReaderInterface* file_reader,
|
||||
RVA minidump_thread_rva,
|
||||
CPUArchitecture arch);
|
||||
CPUArchitecture arch,
|
||||
const std::map<uint32_t, std::string>& thread_names);
|
||||
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -71,6 +77,7 @@ class ThreadSnapshotMinidump : public ThreadSnapshot {
|
||||
bool InitializeContext(const std::vector<unsigned char>& minidump_context);
|
||||
|
||||
MINIDUMP_THREAD minidump_thread_;
|
||||
std::string thread_name_;
|
||||
MinidumpContextConverter context_;
|
||||
MemorySnapshotMinidump stack_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
@ -39,6 +39,10 @@ uint64_t ThreadSnapshotSanitized::ThreadID() const {
|
||||
return snapshot_->ThreadID();
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotSanitized::ThreadName() const {
|
||||
return snapshot_->ThreadName();
|
||||
}
|
||||
|
||||
int ThreadSnapshotSanitized::SuspendCount() const {
|
||||
return snapshot_->SuspendCount();
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include "snapshot/thread_snapshot.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "snapshot/sanitized/memory_snapshot_sanitized.h"
|
||||
#include "util/misc/range_set.h"
|
||||
|
||||
@ -44,6 +46,7 @@ class ThreadSnapshotSanitized final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
|
@ -43,6 +43,10 @@ uint64_t TestThreadSnapshot::ThreadID() const {
|
||||
return thread_id_;
|
||||
}
|
||||
|
||||
std::string TestThreadSnapshot::ThreadName() const {
|
||||
return thread_name_;
|
||||
}
|
||||
|
||||
int TestThreadSnapshot::SuspendCount() const {
|
||||
return suspend_count_;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -63,6 +64,9 @@ class TestThreadSnapshot final : public ThreadSnapshot {
|
||||
}
|
||||
|
||||
void SetThreadID(uint64_t thread_id) { thread_id_ = thread_id; }
|
||||
void SetThreadName(const std::string& thread_name) {
|
||||
thread_name_ = thread_name;
|
||||
}
|
||||
void SetSuspendCount(int suspend_count) { suspend_count_ = suspend_count; }
|
||||
void SetPriority(int priority) { priority_ = priority; }
|
||||
void SetThreadSpecificDataAddress(uint64_t thread_specific_data_address) {
|
||||
@ -83,6 +87,7 @@ class TestThreadSnapshot final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
@ -96,6 +101,7 @@ class TestThreadSnapshot final : public ThreadSnapshot {
|
||||
CPUContext context_;
|
||||
std::unique_ptr<MemorySnapshot> stack_;
|
||||
uint64_t thread_id_;
|
||||
std::string thread_name_;
|
||||
int suspend_count_;
|
||||
int priority_;
|
||||
uint64_t thread_specific_data_address_;
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace crashpad {
|
||||
@ -51,6 +52,9 @@ class ThreadSnapshot {
|
||||
//! unique system-wide.
|
||||
virtual uint64_t ThreadID() const = 0;
|
||||
|
||||
//! \brief Returns the thread's name.
|
||||
virtual std::string ThreadName() const = 0;
|
||||
|
||||
//! \brief Returns the thread’s suspend count.
|
||||
//!
|
||||
//! A suspend count of `0` denotes a schedulable (not suspended) thread.
|
||||
|
@ -21,13 +21,16 @@
|
||||
|
||||
#include "base/notreached.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "snapshot/win/cpu_context_win.h"
|
||||
#include "util/misc/capture_context.h"
|
||||
#include "util/misc/time.h"
|
||||
#include "util/win/get_function.h"
|
||||
#include "util/win/nt_internals.h"
|
||||
#include "util/win/ntstatus_logging.h"
|
||||
#include "util/win/process_structs.h"
|
||||
#include "util/win/scoped_handle.h"
|
||||
#include "util/win/scoped_local_alloc.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
@ -232,12 +235,9 @@ bool ProcessReaderWin::ThreadContext::InitializeWow64(HANDLE thread_handle) {
|
||||
bool ProcessReaderWin::ThreadContext::InitializeXState(
|
||||
HANDLE thread_handle,
|
||||
ULONG64 XStateCompactionMask) {
|
||||
static auto initialize_context_2 = []() {
|
||||
// InitializeContext2 needs Windows 10 build 20348.
|
||||
HINSTANCE kernel32 = GetModuleHandle(L"Kernel32.dll");
|
||||
return reinterpret_cast<decltype(InitializeContext2)*>(
|
||||
GetProcAddress(kernel32, "InitializeContext2"));
|
||||
}();
|
||||
// InitializeContext2 needs Windows 10 build 20348.
|
||||
static const auto initialize_context_2 =
|
||||
GET_FUNCTION(L"kernel32.dll", ::InitializeContext2);
|
||||
if (!initialize_context_2)
|
||||
return false;
|
||||
// We want CET_U xstate to get the ssp, only possible when supported.
|
||||
@ -276,6 +276,7 @@ bool ProcessReaderWin::ThreadContext::InitializeXState(
|
||||
|
||||
ProcessReaderWin::Thread::Thread()
|
||||
: context(),
|
||||
name(),
|
||||
id(0),
|
||||
teb_address(0),
|
||||
teb_size(0),
|
||||
@ -460,6 +461,21 @@ void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) {
|
||||
thread.stack_region_size = base - limit;
|
||||
}
|
||||
}
|
||||
// On Windows 10 build 1607 and later, read the thread name.
|
||||
static const auto get_thread_description =
|
||||
GET_FUNCTION(L"kernel32.dll", ::GetThreadDescription);
|
||||
if (get_thread_description) {
|
||||
wchar_t* thread_description;
|
||||
HRESULT hr =
|
||||
get_thread_description(thread_handle.get(), &thread_description);
|
||||
if (SUCCEEDED(hr)) {
|
||||
ScopedLocalAlloc thread_description_owner(thread_description);
|
||||
thread.name = base::WideToUTF8(thread_description);
|
||||
} else {
|
||||
LOG(WARNING) << "GetThreadDescription: "
|
||||
<< logging::SystemErrorCodeToString(hr);
|
||||
}
|
||||
}
|
||||
threads_.push_back(thread);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <windows.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "build/build_config.h"
|
||||
@ -77,6 +78,7 @@ class ProcessReaderWin {
|
||||
~Thread() {}
|
||||
|
||||
ThreadContext context;
|
||||
std::string name;
|
||||
uint64_t id;
|
||||
WinVMAddress teb_address;
|
||||
WinVMSize teb_size;
|
||||
|
@ -17,9 +17,17 @@
|
||||
#include <windows.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
#include "test/win/win_multiprocess.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/synchronization/semaphore.h"
|
||||
@ -31,6 +39,8 @@ namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
using ::testing::IsSupersetOf;
|
||||
|
||||
TEST(ProcessReaderWin, SelfBasic) {
|
||||
ProcessReaderWin process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(),
|
||||
@ -98,6 +108,7 @@ TEST(ProcessReaderWin, ChildBasic) {
|
||||
}
|
||||
|
||||
TEST(ProcessReaderWin, SelfOneThread) {
|
||||
const ScopedSetThreadName scoped_set_thread_name("SelfBasic");
|
||||
ProcessReaderWin process_reader;
|
||||
ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(),
|
||||
ProcessSuspensionState::kRunning));
|
||||
@ -111,6 +122,7 @@ TEST(ProcessReaderWin, SelfOneThread) {
|
||||
ASSERT_GE(threads.size(), 1u);
|
||||
|
||||
EXPECT_EQ(threads[0].id, GetCurrentThreadId());
|
||||
EXPECT_EQ(threads[0].name, "SelfBasic");
|
||||
EXPECT_NE(ProgramCounterFromCONTEXT(threads[0].context.context<CONTEXT>()),
|
||||
nullptr);
|
||||
EXPECT_EQ(threads[0].suspend_count, 0u);
|
||||
@ -132,18 +144,21 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess {
|
||||
|
||||
class SleepingThread : public Thread {
|
||||
public:
|
||||
SleepingThread() : done_(nullptr) {}
|
||||
SleepingThread(const std::string& thread_name)
|
||||
: done_(nullptr), thread_name_(thread_name) {}
|
||||
|
||||
void SetHandle(Semaphore* done) {
|
||||
done_= done;
|
||||
}
|
||||
|
||||
void ThreadMain() override {
|
||||
const ScopedSetThreadName scoped_set_thread_name(thread_name_);
|
||||
done_->Wait();
|
||||
}
|
||||
|
||||
private:
|
||||
Semaphore* done_;
|
||||
const std::string thread_name_;
|
||||
};
|
||||
|
||||
void WinMultiprocessParent() override {
|
||||
@ -158,8 +173,28 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess {
|
||||
|
||||
const auto& threads = process_reader.Threads();
|
||||
ASSERT_GE(threads.size(), kCreatedThreads + 1);
|
||||
for (const auto& thread : threads)
|
||||
EXPECT_EQ(threads[0].name, "WinMultiprocessChild-Main");
|
||||
|
||||
const std::set<std::string> expected_thread_names = {
|
||||
"WinMultiprocessChild-1",
|
||||
"WinMultiprocessChild-2",
|
||||
"WinMultiprocessChild-3",
|
||||
};
|
||||
// Windows can create threads besides the ones created in
|
||||
// WinMultiprocessChild(), so keep track of the (non-main) thread names
|
||||
// and make sure all the expected names are present.
|
||||
std::set<std::string> thread_names;
|
||||
for (size_t i = 1; i < threads.size(); i++) {
|
||||
if (!threads[i].name.empty()) {
|
||||
thread_names.emplace(threads[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_THAT(thread_names, IsSupersetOf(expected_thread_names));
|
||||
|
||||
for (const auto& thread : threads) {
|
||||
EXPECT_EQ(thread.suspend_count, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@ -173,15 +208,23 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess {
|
||||
// suspended.
|
||||
const auto& threads = process_reader.Threads();
|
||||
ASSERT_GE(threads.size(), kCreatedThreads + 1);
|
||||
for (const auto& thread : threads)
|
||||
for (const auto& thread : threads) {
|
||||
EXPECT_EQ(thread.suspend_count, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WinMultiprocessChild() override {
|
||||
const ScopedSetThreadName scoped_set_thread_name(
|
||||
"WinMultiprocessChild-Main");
|
||||
|
||||
// Create three dummy threads so we can confirm we read successfully read
|
||||
// more than just the main thread.
|
||||
SleepingThread threads[kCreatedThreads];
|
||||
std::array<SleepingThread, kCreatedThreads> threads = {
|
||||
"WinMultiprocessChild-1",
|
||||
"WinMultiprocessChild-2",
|
||||
"WinMultiprocessChild-3",
|
||||
};
|
||||
Semaphore done(0);
|
||||
for (auto& thread : threads)
|
||||
thread.SetHandle(&done);
|
||||
|
@ -45,8 +45,7 @@ ProcessSnapshotWin::ProcessSnapshotWin()
|
||||
annotations_simple_map_(),
|
||||
snapshot_time_(),
|
||||
options_(),
|
||||
initialized_() {
|
||||
}
|
||||
initialized_() {}
|
||||
|
||||
ProcessSnapshotWin::~ProcessSnapshotWin() {
|
||||
}
|
||||
|
@ -165,6 +165,11 @@ uint64_t ThreadSnapshotWin::ThreadID() const {
|
||||
return thread_.id;
|
||||
}
|
||||
|
||||
std::string ThreadSnapshotWin::ThreadName() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_.name;
|
||||
}
|
||||
|
||||
int ThreadSnapshotWin::SuspendCount() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return thread_.suspend_count;
|
||||
|
@ -68,6 +68,7 @@ class ThreadSnapshotWin final : public ThreadSnapshot {
|
||||
const CPUContext* Context() const override;
|
||||
const MemorySnapshot* Stack() const override;
|
||||
uint64_t ThreadID() const override;
|
||||
std::string ThreadName() const override;
|
||||
int SuspendCount() const override;
|
||||
int Priority() const override;
|
||||
uint64_t ThreadSpecificDataAddress() const override;
|
||||
|
@ -37,6 +37,7 @@ static_library("test") {
|
||||
"scoped_guarded_page.h",
|
||||
"scoped_module_handle.cc",
|
||||
"scoped_module_handle.h",
|
||||
"scoped_set_thread_name.h",
|
||||
"scoped_temp_dir.cc",
|
||||
"scoped_temp_dir.h",
|
||||
"test_paths.cc",
|
||||
@ -57,6 +58,14 @@ static_library("test") {
|
||||
}
|
||||
}
|
||||
|
||||
# TODO(crbug.com/812974): Remove !crashpad_is_fuchsia when Fuchsia is no
|
||||
# longer treated as a posix platform.
|
||||
if (crashpad_is_posix && !crashpad_is_fuchsia) {
|
||||
sources += [
|
||||
"scoped_set_thread_name_posix.cc",
|
||||
]
|
||||
}
|
||||
|
||||
if (crashpad_is_mac || crashpad_is_ios) {
|
||||
sources += [
|
||||
"mac/mach_errors.cc",
|
||||
@ -96,6 +105,7 @@ static_library("test") {
|
||||
sources += [
|
||||
"multiprocess_exec_win.cc",
|
||||
"scoped_guarded_page_win.cc",
|
||||
"scoped_set_thread_name_win.cc",
|
||||
"scoped_temp_dir_win.cc",
|
||||
"win/child_launcher.cc",
|
||||
"win/child_launcher.h",
|
||||
@ -109,7 +119,10 @@ static_library("test") {
|
||||
}
|
||||
|
||||
if (crashpad_is_fuchsia) {
|
||||
sources += [ "multiprocess_exec_fuchsia.cc" ]
|
||||
sources += [
|
||||
"multiprocess_exec_fuchsia.cc",
|
||||
"scoped_set_thread_name_fuchsia.cc",
|
||||
]
|
||||
}
|
||||
|
||||
public_configs = [ "..:crashpad_config" ]
|
||||
|
46
test/scoped_set_thread_name.h
Normal file
46
test/scoped_set_thread_name.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2022 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
|
||||
#define CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
//! Sets the name of the current thread for the lifetime of this object.
|
||||
class ScopedSetThreadName final {
|
||||
public:
|
||||
explicit ScopedSetThreadName(const std::string& new_thread_name);
|
||||
|
||||
ScopedSetThreadName(const ScopedSetThreadName&) = delete;
|
||||
ScopedSetThreadName& operator=(const ScopedSetThreadName&) = delete;
|
||||
|
||||
~ScopedSetThreadName();
|
||||
|
||||
private:
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const std::wstring original_name_;
|
||||
#else
|
||||
const std::string original_name_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
|
61
test/scoped_set_thread_name_fuchsia.cc
Normal file
61
test/scoped_set_thread_name_fuchsia.cc
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2022 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <lib/zx/thread.h>
|
||||
#include <zircon/syscalls/object.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include "base/check_op.h"
|
||||
#include "base/fuchsia/fuchsia_logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetCurrentThreadName() {
|
||||
std::string result(ZX_MAX_NAME_LEN, '\0');
|
||||
const zx_status_t status = zx::thread::self()->get_property(
|
||||
ZX_PROP_NAME, result.data(), result.length());
|
||||
ZX_CHECK(status == ZX_OK, status) << "get_property(ZX_PROP_NAME)";
|
||||
const auto result_nul_idx = result.find('\0');
|
||||
CHECK_NE(result_nul_idx, std::string::npos)
|
||||
<< "get_property() did not NUL terminate";
|
||||
result.resize(result_nul_idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
|
||||
: original_name_(GetCurrentThreadName()) {
|
||||
// Fuchsia silently truncates the thread name if it's too long.
|
||||
CHECK_LT(new_thread_name.length(), ZX_MAX_NAME_LEN);
|
||||
const zx_status_t status = zx::thread::self()->set_property(
|
||||
ZX_PROP_NAME, new_thread_name.c_str(), new_thread_name.length());
|
||||
ZX_CHECK(status == ZX_OK, status) << "set_property(ZX_PROP_NAME)";
|
||||
}
|
||||
|
||||
ScopedSetThreadName::~ScopedSetThreadName() {
|
||||
const zx_status_t status = zx::thread::self()->set_property(
|
||||
ZX_PROP_NAME, original_name_.c_str(), original_name_.length());
|
||||
ZX_CHECK(status == ZX_OK, status) << "set_property(ZX_PROP_NAME)";
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
80
test/scoped_set_thread_name_posix.cc
Normal file
80
test/scoped_set_thread_name_posix.cc
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2022 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
#include <mach/thread_info.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// The kernel headers define this in linux/sched.h as TASK_COMM_LEN, but the
|
||||
// userspace copy of that header does not define it.
|
||||
constexpr size_t kPthreadNameMaxLen = 16;
|
||||
#elif BUILDFLAG(IS_APPLE)
|
||||
constexpr size_t kPthreadNameMaxLen = MAXTHREADNAMESIZE;
|
||||
#else
|
||||
#error Port to your platform
|
||||
#endif
|
||||
|
||||
void SetCurrentThreadName(const std::string& thread_name) {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
PCHECK((errno = pthread_setname_np(pthread_self(), thread_name.c_str())) == 0)
|
||||
<< "pthread_setname_np";
|
||||
#elif BUILDFLAG(IS_APPLE)
|
||||
// Apple's pthread_setname_np() sets errno instead of returning it.
|
||||
PCHECK(pthread_setname_np(thread_name.c_str()) == 0) << "pthread_setname_np";
|
||||
#else
|
||||
#error Port to your platform
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetCurrentThreadName() {
|
||||
std::string result(kPthreadNameMaxLen, '\0');
|
||||
PCHECK((errno = pthread_getname_np(
|
||||
pthread_self(), result.data(), result.length())) == 0)
|
||||
<< "pthread_getname_np";
|
||||
const auto result_nul_idx = result.find('\0');
|
||||
CHECK(result_nul_idx != std::string::npos)
|
||||
<< "pthread_getname_np did not NUL terminate";
|
||||
result.resize(result_nul_idx);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
|
||||
: original_name_(GetCurrentThreadName()) {
|
||||
SetCurrentThreadName(new_thread_name);
|
||||
}
|
||||
|
||||
ScopedSetThreadName::~ScopedSetThreadName() {
|
||||
SetCurrentThreadName(original_name_);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
56
test/scoped_set_thread_name_win.cc
Normal file
56
test/scoped_set_thread_name_win.cc
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2022 The Crashpad Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/scoped_set_thread_name.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "util/win/scoped_local_alloc.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
std::wstring GetCurrentThreadName() {
|
||||
wchar_t* thread_description;
|
||||
HRESULT hr = GetThreadDescription(GetCurrentThread(), &thread_description);
|
||||
CHECK(SUCCEEDED(hr)) << "GetThreadDescription: "
|
||||
<< logging::SystemErrorCodeToString(hr);
|
||||
ScopedLocalAlloc thread_description_owner(thread_description);
|
||||
return std::wstring(thread_description);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
|
||||
: original_name_(GetCurrentThreadName()) {
|
||||
const std::wstring wnew_thread_name = base::UTF8ToWide(new_thread_name);
|
||||
HRESULT hr =
|
||||
SetThreadDescription(GetCurrentThread(), wnew_thread_name.c_str());
|
||||
CHECK(SUCCEEDED(hr)) << "SetThreadDescription: "
|
||||
<< logging::SystemErrorCodeToString(hr);
|
||||
}
|
||||
|
||||
ScopedSetThreadName::~ScopedSetThreadName() {
|
||||
HRESULT hr = SetThreadDescription(GetCurrentThread(), original_name_.c_str());
|
||||
CHECK(SUCCEEDED(hr)) << "SetThreadDescription: "
|
||||
<< logging::SystemErrorCodeToString(hr);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -99,6 +99,7 @@ namespace internal {
|
||||
TD(kThreadContextMemoryRegions, 6011) \
|
||||
TD(kThreadContextMemoryRegionAddress, 6012) \
|
||||
TD(kThreadContextMemoryRegionData, 6013) \
|
||||
TD(kThreadName, 6014) \
|
||||
TD(kMaxValue, 65535) \
|
||||
// clang-format on
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user