From 61f1013ee4ef6275c00622957aa4c4ff6bcf22dd Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Thu, 22 Feb 2018 11:06:35 -0800 Subject: [PATCH] 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 Reviewed-by: Mark Mentovai --- snapshot/BUILD.gn | 4 + snapshot/fuchsia/process_reader.cc | 98 +++++++++++++++++++++++++ snapshot/fuchsia/process_reader.h | 30 ++++++++ snapshot/fuchsia/process_reader_test.cc | 95 ++++++++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 snapshot/fuchsia/process_reader_test.cc diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index b613774d..53311b88 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -341,6 +341,10 @@ source_set("snapshot_test") { 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 # 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 diff --git a/snapshot/fuchsia/process_reader.cc b/snapshot/fuchsia/process_reader.cc index ffd81635..be9fa775 100644 --- a/snapshot/fuchsia/process_reader.cc +++ b/snapshot/fuchsia/process_reader.cc @@ -17,12 +17,20 @@ #include #include +#include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/scoped_zx_handle.h" +#include "base/logging.h" + namespace crashpad { ProcessReader::Module::Module() = default; ProcessReader::Module::~Module() = default; +ProcessReader::Thread::Thread() = default; + +ProcessReader::Thread::~Thread() = default; + ProcessReader::ProcessReader() = default; ProcessReader::~ProcessReader() = default; @@ -49,6 +57,16 @@ const std::vector& ProcessReader::Modules() { return modules_; } +const std::vector& ProcessReader::Threads() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (!initialized_threads_) { + InitializeThreads(); + } + + return threads_; +} + void ProcessReader::InitializeModules() { DCHECK(!initialized_modules_); 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 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 diff --git a/snapshot/fuchsia/process_reader.h b/snapshot/fuchsia/process_reader.h index b3b24e36..0e83fba7 100644 --- a/snapshot/fuchsia/process_reader.h +++ b/snapshot/fuchsia/process_reader.h @@ -54,6 +54,22 @@ class ProcessReader { 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(); @@ -72,15 +88,29 @@ class ProcessReader { //! `0`) corresponds to the main executable. const std::vector& Modules(); + //! \return The threads that are in the process. + const std::vector& Threads(); + + //! \brief Return a memory reader for the target process. + ProcessMemory* Memory() { return process_memory_.get(); } + private: + //! Performs lazy initialization of the \a modules_ vector on behalf of + //! Modules(). void InitializeModules(); + //! Performs lazy initialization of the \a threads_ vector on behalf of + //! Threads(). + void InitializeThreads(); + std::vector modules_; + std::vector threads_; std::vector> module_readers_; std::vector> process_memory_ranges_; std::unique_ptr process_memory_; zx_handle_t process_; bool initialized_modules_ = false; + bool initialized_threads_ = false; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessReader); diff --git a/snapshot/fuchsia/process_reader_test.cc b/snapshot/fuchsia/process_reader_test.cc new file mode 100644 index 00000000..8a89b97a --- /dev/null +++ b/snapshot/fuchsia/process_reader_test.cc @@ -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 +#include + +#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(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(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