[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:
Ben Hamilton 2022-06-13 14:44:24 -06:00 committed by Crashpad LUCI CQ
parent 0a14d52dad
commit ed8cfeb2cd
44 changed files with 974 additions and 79 deletions

View File

@ -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;

View File

@ -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) {

View File

@ -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>();

View File

@ -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 doesnt 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 doesnt 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);

View File

@ -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 objects 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

View File

@ -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>();

View File

@ -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));
}
}
};

View File

@ -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.

View File

@ -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_;

View File

@ -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++);

View File

@ -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_;

View File

@ -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_;

View File

@ -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;

View File

@ -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;

View File

@ -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(),
&current_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());

View File

@ -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;

View File

@ -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_;

View File

@ -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;

View File

@ -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;

View File

@ -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 threads 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 threads 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 its 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 cant 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 cant 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 isnt 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,
&current_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 its 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();
}

View File

@ -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_;

View File

@ -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_;

View File

@ -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()) {

View File

@ -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_;

View File

@ -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);

View File

@ -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;

View File

@ -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 threads 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_;

View File

@ -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();
}

View File

@ -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;

View File

@ -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_;
}

View File

@ -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_;

View File

@ -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 threads suspend count.
//!
//! A suspend count of `0` denotes a schedulable (not suspended) thread.

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);

View File

@ -45,8 +45,7 @@ ProcessSnapshotWin::ProcessSnapshotWin()
annotations_simple_map_(),
snapshot_time_(),
options_(),
initialized_() {
}
initialized_() {}
ProcessSnapshotWin::~ProcessSnapshotWin() {
}

View File

@ -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;

View File

@ -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;

View File

@ -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" ]

View 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_

View 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

View 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

View 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

View File

@ -99,6 +99,7 @@ namespace internal {
TD(kThreadContextMemoryRegions, 6011) \
TD(kThreadContextMemoryRegionAddress, 6012) \
TD(kThreadContextMemoryRegionData, 6013) \
TD(kThreadName, 6014) \
TD(kMaxValue, 65535) \
// clang-format on