mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-17 16:43:53 +00:00
fuchsia: Add some thread reading to ProcessReader and a test
This fills out Threads() in ProcessReader, gathering some information for which there's system calls, and adds some basic tests for ProcessReader on Fuchsia. Bug: crashpad:196 Change-Id: I0738e77121c90a8b883267c1df0fcfc6621674d7 Reviewed-on: https://chromium-review.googlesource.com/929350 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
3030ae5417
commit
61f1013ee4
@ -341,6 +341,10 @@ source_set("snapshot_test") {
|
|||||||
sources += [ "posix/timezone_test.cc" ]
|
sources += [ "posix/timezone_test.cc" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (crashpad_is_fuchsia) {
|
||||||
|
sources += [ "fuchsia/process_reader_test.cc" ]
|
||||||
|
}
|
||||||
|
|
||||||
# public_configs isn’t quite right. snapshot_test_link sets ldflags, and
|
# public_configs isn’t quite right. snapshot_test_link sets ldflags, and
|
||||||
# what’s really needed is a way to push ldflags to dependent targets that
|
# what’s really needed is a way to push ldflags to dependent targets that
|
||||||
# produce linker output. Luckily in this case, all dependents do produce
|
# produce linker output. Luckily in this case, all dependents do produce
|
||||||
|
@ -17,12 +17,20 @@
|
|||||||
#include <link.h>
|
#include <link.h>
|
||||||
#include <zircon/syscalls.h>
|
#include <zircon/syscalls.h>
|
||||||
|
|
||||||
|
#include "base/fuchsia/fuchsia_logging.h"
|
||||||
|
#include "base/fuchsia/scoped_zx_handle.h"
|
||||||
|
#include "base/logging.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
ProcessReader::Module::Module() = default;
|
ProcessReader::Module::Module() = default;
|
||||||
|
|
||||||
ProcessReader::Module::~Module() = default;
|
ProcessReader::Module::~Module() = default;
|
||||||
|
|
||||||
|
ProcessReader::Thread::Thread() = default;
|
||||||
|
|
||||||
|
ProcessReader::Thread::~Thread() = default;
|
||||||
|
|
||||||
ProcessReader::ProcessReader() = default;
|
ProcessReader::ProcessReader() = default;
|
||||||
|
|
||||||
ProcessReader::~ProcessReader() = default;
|
ProcessReader::~ProcessReader() = default;
|
||||||
@ -49,6 +57,16 @@ const std::vector<ProcessReader::Module>& ProcessReader::Modules() {
|
|||||||
return modules_;
|
return modules_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<ProcessReader::Thread>& ProcessReader::Threads() {
|
||||||
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||||
|
|
||||||
|
if (!initialized_threads_) {
|
||||||
|
InitializeThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
return threads_;
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessReader::InitializeModules() {
|
void ProcessReader::InitializeModules() {
|
||||||
DCHECK(!initialized_modules_);
|
DCHECK(!initialized_modules_);
|
||||||
DCHECK(modules_.empty());
|
DCHECK(modules_.empty());
|
||||||
@ -163,4 +181,84 @@ void ProcessReader::InitializeModules() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProcessReader::InitializeThreads() {
|
||||||
|
DCHECK(!initialized_threads_);
|
||||||
|
DCHECK(threads_.empty());
|
||||||
|
|
||||||
|
initialized_threads_ = true;
|
||||||
|
|
||||||
|
// Retrieve the thread koids. This is racy; better if the process is suspended
|
||||||
|
// itself, but threads could still be externally created. As there's no
|
||||||
|
// maximum, this needs to be retried in a loop until the actual threads
|
||||||
|
// retrieved is equal to the available threads.
|
||||||
|
|
||||||
|
std::vector<zx_koid_t> threads(100);
|
||||||
|
size_t actual_num_threads, available_num_threads;
|
||||||
|
for (;;) {
|
||||||
|
zx_status_t status = zx_object_get_info(process_,
|
||||||
|
ZX_INFO_PROCESS_THREADS,
|
||||||
|
&threads[0],
|
||||||
|
sizeof(threads[0]) * threads.size(),
|
||||||
|
&actual_num_threads,
|
||||||
|
&available_num_threads);
|
||||||
|
// If the buffer is too small (even zero), the result is still ZX_OK, not
|
||||||
|
// ZX_ERR_BUFFER_TOO_SMALL.
|
||||||
|
if (status != ZX_OK) {
|
||||||
|
ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_THREADS";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (actual_num_threads == available_num_threads) {
|
||||||
|
threads.resize(actual_num_threads);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to the expected number next time with a bit extra to attempt to
|
||||||
|
// handle the race between here and the next request.
|
||||||
|
threads.resize(available_num_threads + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const zx_koid_t thread_koid : threads) {
|
||||||
|
zx_handle_t raw_handle;
|
||||||
|
zx_status_t status = zx_object_get_child(
|
||||||
|
process_, thread_koid, ZX_RIGHT_SAME_RIGHTS, &raw_handle);
|
||||||
|
if (status != ZX_OK) {
|
||||||
|
ZX_LOG(ERROR, status) << "zx_object_get_child";
|
||||||
|
// TODO(scottmg): Decide if it's worthwhile adding a mostly-empty Thread
|
||||||
|
// here, consisting only of the koid, but no other information. The only
|
||||||
|
// time this is expected to happen is when there's a race between getting
|
||||||
|
// the koid above, and requesting the handle here.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::ScopedZxHandle thread_handle(raw_handle);
|
||||||
|
|
||||||
|
Thread thread;
|
||||||
|
thread.id = thread_koid;
|
||||||
|
|
||||||
|
char name[ZX_MAX_NAME_LEN] = {0};
|
||||||
|
status = zx_object_get_property(
|
||||||
|
thread_handle.get(), ZX_PROP_NAME, &name, sizeof(name));
|
||||||
|
if (status != ZX_OK) {
|
||||||
|
ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME";
|
||||||
|
} else {
|
||||||
|
thread.name.assign(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
zx_info_thread_t thread_info;
|
||||||
|
status = zx_object_get_info(thread_handle.get(),
|
||||||
|
ZX_INFO_THREAD,
|
||||||
|
&thread_info,
|
||||||
|
sizeof(thread_info),
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
if (status != ZX_OK) {
|
||||||
|
ZX_LOG(WARNING, status) << "zx_object_get_info ZX_INFO_THREAD";
|
||||||
|
} else {
|
||||||
|
thread.state = thread_info.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
threads_.push_back(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace crashpad
|
} // namespace crashpad
|
||||||
|
@ -54,6 +54,22 @@ class ProcessReader {
|
|||||||
ModuleSnapshot::ModuleType type = ModuleSnapshot::kModuleTypeUnknown;
|
ModuleSnapshot::ModuleType type = ModuleSnapshot::kModuleTypeUnknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! \brief Contains information about a thread that belongs to a process.
|
||||||
|
struct Thread {
|
||||||
|
Thread();
|
||||||
|
~Thread();
|
||||||
|
|
||||||
|
//! \brief The kernel identifier for the thread.
|
||||||
|
zx_koid_t id = ZX_KOID_INVALID;
|
||||||
|
|
||||||
|
//! \brief The state of the thread, the `ZX_THREAD_STATE_*` value or `-1` if
|
||||||
|
//! the value could not be retrieved.
|
||||||
|
uint32_t state = -1;
|
||||||
|
|
||||||
|
//! \brief The `ZX_PROP_NAME` property of the thread. This may be empty.
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
ProcessReader();
|
ProcessReader();
|
||||||
~ProcessReader();
|
~ProcessReader();
|
||||||
|
|
||||||
@ -72,15 +88,29 @@ class ProcessReader {
|
|||||||
//! `0`) corresponds to the main executable.
|
//! `0`) corresponds to the main executable.
|
||||||
const std::vector<Module>& Modules();
|
const std::vector<Module>& Modules();
|
||||||
|
|
||||||
|
//! \return The threads that are in the process.
|
||||||
|
const std::vector<Thread>& Threads();
|
||||||
|
|
||||||
|
//! \brief Return a memory reader for the target process.
|
||||||
|
ProcessMemory* Memory() { return process_memory_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
//! Performs lazy initialization of the \a modules_ vector on behalf of
|
||||||
|
//! Modules().
|
||||||
void InitializeModules();
|
void InitializeModules();
|
||||||
|
|
||||||
|
//! Performs lazy initialization of the \a threads_ vector on behalf of
|
||||||
|
//! Threads().
|
||||||
|
void InitializeThreads();
|
||||||
|
|
||||||
std::vector<Module> modules_;
|
std::vector<Module> modules_;
|
||||||
|
std::vector<Thread> threads_;
|
||||||
std::vector<std::unique_ptr<ElfImageReader>> module_readers_;
|
std::vector<std::unique_ptr<ElfImageReader>> module_readers_;
|
||||||
std::vector<std::unique_ptr<ProcessMemoryRange>> process_memory_ranges_;
|
std::vector<std::unique_ptr<ProcessMemoryRange>> process_memory_ranges_;
|
||||||
std::unique_ptr<ProcessMemoryFuchsia> process_memory_;
|
std::unique_ptr<ProcessMemoryFuchsia> process_memory_;
|
||||||
zx_handle_t process_;
|
zx_handle_t process_;
|
||||||
bool initialized_modules_ = false;
|
bool initialized_modules_ = false;
|
||||||
|
bool initialized_threads_ = false;
|
||||||
InitializationStateDcheck initialized_;
|
InitializationStateDcheck initialized_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ProcessReader);
|
DISALLOW_COPY_AND_ASSIGN(ProcessReader);
|
||||||
|
95
snapshot/fuchsia/process_reader_test.cc
Normal file
95
snapshot/fuchsia/process_reader_test.cc
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2018 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 "snapshot/fuchsia/process_reader.h"
|
||||||
|
|
||||||
|
#include <zircon/process.h>
|
||||||
|
#include <zircon/syscalls.h>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/multiprocess_exec.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(ProcessReader, SelfBasic) {
|
||||||
|
ProcessReader process_reader;
|
||||||
|
ASSERT_TRUE(process_reader.Initialize(zx_process_self()));
|
||||||
|
|
||||||
|
static constexpr char kTestMemory[] = "Some test memory";
|
||||||
|
char buffer[arraysize(kTestMemory)];
|
||||||
|
ASSERT_TRUE(process_reader.Memory()->Read(
|
||||||
|
reinterpret_cast<zx_vaddr_t>(kTestMemory), sizeof(kTestMemory), &buffer));
|
||||||
|
EXPECT_STREQ(kTestMemory, buffer);
|
||||||
|
|
||||||
|
const auto& modules = process_reader.Modules();
|
||||||
|
EXPECT_GT(modules.size(), 0u);
|
||||||
|
for (const auto& module : modules) {
|
||||||
|
EXPECT_FALSE(module.name.empty());
|
||||||
|
EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& threads = process_reader.Threads();
|
||||||
|
EXPECT_GT(threads.size(), 0u);
|
||||||
|
|
||||||
|
zx_info_handle_basic_t info;
|
||||||
|
ASSERT_EQ(zx_object_get_info(zx_thread_self(),
|
||||||
|
ZX_INFO_HANDLE_BASIC,
|
||||||
|
&info,
|
||||||
|
sizeof(info),
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr char kTestMemory[] = "Read me from another process";
|
||||||
|
|
||||||
|
CRASHPAD_CHILD_TEST_MAIN(ProcessReaderBasicChildTestMain) {
|
||||||
|
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BasicChildTest : public MultiprocessExec {
|
||||||
|
public:
|
||||||
|
BasicChildTest() : MultiprocessExec() {
|
||||||
|
SetChildTestMainFunction("ProcessReaderBasicChildTestMain");
|
||||||
|
}
|
||||||
|
~BasicChildTest() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void MultiprocessParent() override {
|
||||||
|
ProcessReader process_reader;
|
||||||
|
ASSERT_TRUE(process_reader.Initialize(zx_process_self()));
|
||||||
|
|
||||||
|
std::string read_string;
|
||||||
|
ASSERT_TRUE(process_reader.Memory()->ReadCString(
|
||||||
|
reinterpret_cast<zx_vaddr_t>(kTestMemory), &read_string));
|
||||||
|
EXPECT_EQ(read_string, kTestMemory);
|
||||||
|
}
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(BasicChildTest);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ProcessReader, ChildBasic) {
|
||||||
|
BasicChildTest test;
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user