// 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_fuchsia.h" #include #include #include #include #include #include "base/cxx17_backports.h" #include "gtest/gtest.h" #include "test/multiprocess_exec.h" #include "test/test_paths.h" #include "util/fuchsia/scoped_task_suspend.h" namespace crashpad { namespace test { namespace { TEST(ProcessReaderFuchsia, SelfBasic) { ProcessReaderFuchsia process_reader; ASSERT_TRUE(process_reader.Initialize(*zx::process::self())); static constexpr char kTestMemory[] = "Some test memory"; char buffer[base::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); const auto& modules = process_reader.Modules(); // The process should have at least one module, the executable, and then some // shared libraries, no loadable modules. EXPECT_GT(modules.size(), 0u); size_t num_executables = 0u; size_t num_shared_libraries = 0u; for (const auto& module : modules) { EXPECT_FALSE(module.name.empty()); EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); if (module.type == ModuleSnapshot::kModuleTypeExecutable) { EXPECT_EQ(module.name, "<_>"); num_executables++; } else if (module.type == ModuleSnapshot::kModuleTypeSharedLibrary) { EXPECT_NE(module.name, "<_>"); num_shared_libraries++; } } EXPECT_EQ(num_executables, 1u); EXPECT_EQ(num_shared_libraries, modules.size() - num_executables); 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) { zx_vaddr_t addr = reinterpret_cast(kTestMemory); CheckedWriteFile( StdioFileHandle(StdioStream::kStandardOutput), &addr, sizeof(addr)); CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); return 0; } class BasicChildTest : public MultiprocessExec { public: BasicChildTest() : MultiprocessExec() { SetChildTestMainFunction("ProcessReaderBasicChildTestMain"); } ~BasicChildTest() {} private: void MultiprocessParent() override { ProcessReaderFuchsia process_reader; ASSERT_TRUE(process_reader.Initialize(*ChildProcess())); zx_vaddr_t addr; ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &addr, sizeof(addr))); std::string read_string; ASSERT_TRUE(process_reader.Memory()->ReadCString(addr, &read_string)); EXPECT_EQ(read_string, kTestMemory); } DISALLOW_COPY_AND_ASSIGN(BasicChildTest); }; TEST(ProcessReaderFuchsia, ChildBasic) { BasicChildTest test; test.Run(); } void* SignalAndSleep(void* arg) { zx_port_packet_t packet = {}; packet.type = ZX_PKT_TYPE_USER; zx_port_queue(*reinterpret_cast(arg), &packet); zx_nanosleep(ZX_TIME_INFINITE); return nullptr; } CRASHPAD_CHILD_TEST_MAIN(ProcessReaderChildThreadsTestMain) { // 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; for (size_t i = 0; i < kNumThreads; ++i) { 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); } // Wait until all threads are ready. for (size_t i = 0; i < kNumThreads; ++i) { zx_port_packet_t packet; zx_port_wait(port, ZX_TIME_INFINITE, &packet); } char c = ' '; CheckedWriteFile( StdioFileHandle(StdioStream::kStandardOutput), &c, sizeof(c)); CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); return 0; } class ThreadsChildTest : public MultiprocessExec { public: ThreadsChildTest() : MultiprocessExec() { SetChildTestMainFunction("ProcessReaderChildThreadsTestMain"); } ~ThreadsChildTest() {} private: void MultiprocessParent() override { char c; ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &c, 1)); ASSERT_EQ(c, ' '); ScopedTaskSuspend suspend(*ChildProcess()); ProcessReaderFuchsia process_reader; ASSERT_TRUE(process_reader.Initialize(*ChildProcess())); const auto& threads = process_reader.Threads(); EXPECT_EQ(threads.size(), 6u); 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); } } DISALLOW_COPY_AND_ASSIGN(ThreadsChildTest); }; // TODO(scottmg): US-553. ScopedTaskSuspend fails sometimes, with a 50ms // timeout. Currently unclear how to make that more reliable, so disable the // test for now as otherwise it flakes. TEST(ProcessReaderFuchsia, DISABLED_ChildThreads) { ThreadsChildTest test; test.Run(); } } // namespace } // namespace test } // namespace crashpad