[tracing] Add trace-buffer-handle to child process launch
Add child process launch parameter --trace-buffer-handle to facilitate writing tracing data before the child process is sandboxed. This is [1/3] CL of enabling tracing prior to sandboxing. Bug: 380411640 Change-Id: Ifadc5435c61cb2662ad3ee5b575ff477fccdb788 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6159045 Reviewed-by: Eric Seckler <eseckler@chromium.org> Reviewed-by: Alexander Timin <altimin@chromium.org> Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> Commit-Queue: Kramer Ge <fangzhoug@chromium.org> Cr-Commit-Position: refs/heads/main@{#1410401}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a6770bb4a7
commit
c80f84e4c9
base/apple
components/tracing/common
content
app
browser
browser_child_process_host_impl.ccbrowser_child_process_host_impl.hchild_process_launcher.ccchild_process_launcher.hchild_process_launcher_helper.ccchild_process_launcher_helper.h
renderer_host
public
common
services/tracing
@ -94,6 +94,7 @@ source_set("tests") {
|
||||
"public/cpp/perfetto/trace_packet_tokenizer_unittest.cc",
|
||||
"public/cpp/perfetto/traced_value_proto_writer_unittest.cc",
|
||||
"public/cpp/stack_sampling/tracing_sampler_profiler_unittest.cc",
|
||||
"public/cpp/trace_startup_shared_memory_unittest.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
|
@ -5,15 +5,18 @@
|
||||
#include "services/tracing/public/cpp/perfetto/perfetto_tracing_backend.h"
|
||||
|
||||
#include "base/auto_reset.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/containers/flat_set.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/shared_memory_switch.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/tracing/tracing_tls.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/tracing/common/tracing_switches.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
#include "mojo/public/cpp/system/data_pipe_drainer.h"
|
||||
#include "services/tracing/public/cpp/perfetto/shared_memory.h"
|
||||
@ -45,9 +48,6 @@ constexpr size_t kDefaultSMBPageSizeBytes = 4 * 1024;
|
||||
constexpr size_t kDefaultSMBPageSizeBytes = 32 * 1024;
|
||||
#endif
|
||||
|
||||
// TODO(crbug.com/40574594): Figure out a good buffer size.
|
||||
constexpr size_t kDefaultSMBSizeBytes = 4 * 1024 * 1024;
|
||||
|
||||
constexpr char kErrorTracingFailed[] = "Tracing failed";
|
||||
|
||||
} // namespace
|
||||
@ -683,12 +683,25 @@ PerfettoTracingBackend::ConnectProducer(const ConnectProducerArgs& args) {
|
||||
uint32_t shmem_size_hint = args.shmem_size_hint_bytes;
|
||||
uint32_t shmem_page_size_hint = args.shmem_page_size_hint_bytes;
|
||||
if (shmem_size_hint == 0)
|
||||
shmem_size_hint = kDefaultSMBSizeBytes;
|
||||
shmem_size_hint = kDefaultSharedMemorySize;
|
||||
if (shmem_page_size_hint == 0)
|
||||
shmem_page_size_hint = kDefaultSMBPageSizeBytes;
|
||||
|
||||
if (args.use_producer_provided_smb) {
|
||||
shm = std::make_unique<ChromeBaseSharedMemory>(shmem_size_hint);
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
base::UnsafeSharedMemoryRegion unsafe_shm;
|
||||
if (command_line->HasSwitch(switches::kTraceBufferHandle)) {
|
||||
auto shmem_region = base::shared_memory::UnsafeSharedMemoryRegionFrom(
|
||||
command_line->GetSwitchValueASCII(switches::kTraceBufferHandle));
|
||||
if (shmem_region->IsValid()) {
|
||||
DCHECK_EQ(shmem_size_hint, shmem_region->GetSize());
|
||||
unsafe_shm = std::move(shmem_region.value());
|
||||
}
|
||||
}
|
||||
if (!unsafe_shm.IsValid()) {
|
||||
unsafe_shm = base::UnsafeSharedMemoryRegion::Create(shmem_size_hint);
|
||||
}
|
||||
shm = std::make_unique<ChromeBaseSharedMemory>(std::move(unsafe_shm));
|
||||
arbiter = perfetto::SharedMemoryArbiter::CreateUnboundInstance(
|
||||
shm.get(), shmem_page_size_hint, ShmemMode::kDefault);
|
||||
}
|
||||
|
@ -14,6 +14,9 @@
|
||||
|
||||
namespace tracing {
|
||||
|
||||
// TODO(crbug.com/40574594): Figure out a good buffer size.
|
||||
inline constexpr size_t kDefaultSharedMemorySize = 4 * 1024 * 1024; // 4 KB
|
||||
|
||||
// This wraps //base's shmem implementation for Perfetto to consume.
|
||||
class COMPONENT_EXPORT(TRACING_CPP) ChromeBaseSharedMemory
|
||||
: public perfetto::SharedMemory {
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "components/tracing/common/tracing_switches.h"
|
||||
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
|
||||
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
|
||||
#include "services/tracing/public/cpp/perfetto/shared_memory.h"
|
||||
#include "services/tracing/public/cpp/perfetto/traced_value_proto_writer.h"
|
||||
#include "services/tracing/public/cpp/trace_event_args_allowlist.h"
|
||||
#include "services/tracing/public/cpp/trace_startup_config.h"
|
||||
@ -34,6 +35,8 @@ namespace {
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
constexpr base::MachPortsForRendezvous::key_type kTraceConfigRendezvousKey =
|
||||
'trcc';
|
||||
constexpr base::MachPortsForRendezvous::key_type kTraceBufferRendezvousKey =
|
||||
'trbc';
|
||||
#endif
|
||||
|
||||
constexpr uint32_t kStartupTracingTimeoutMs = 30 * 1000; // 30 sec
|
||||
@ -145,6 +148,32 @@ base::ReadOnlySharedMemoryRegion CreateTracingConfigSharedMemory() {
|
||||
return std::move(shm.region);
|
||||
}
|
||||
|
||||
base::UnsafeSharedMemoryRegion CreateTracingOutputSharedMemory() {
|
||||
#if DCHECK_IS_ON()
|
||||
// This should not be called if tracing config shm was not created beforehand.
|
||||
base::trace_event::TraceLog* trace_log =
|
||||
base::trace_event::TraceLog::GetInstance();
|
||||
const auto& startup_config = TraceStartupConfig::GetInstance();
|
||||
DCHECK(startup_config.IsEnabled() || trace_log->IsEnabled());
|
||||
|
||||
if (!startup_config.IsEnabled()) {
|
||||
bool has_relevant_config = std::any_of(
|
||||
trace_log->GetTrackEventSessions().begin(),
|
||||
trace_log->GetTrackEventSessions().end(), [](const auto& session) {
|
||||
return session.backend_type == perfetto::kCustomBackend &&
|
||||
!session.config.has_interceptor_config();
|
||||
});
|
||||
DCHECK(has_relevant_config);
|
||||
}
|
||||
#endif // DCHECK_IS_ON()
|
||||
|
||||
auto shm = base::UnsafeSharedMemoryRegion::Create(kDefaultSharedMemorySize);
|
||||
if (!shm.IsValid()) {
|
||||
return base::UnsafeSharedMemoryRegion();
|
||||
}
|
||||
return shm;
|
||||
}
|
||||
|
||||
void COMPONENT_EXPORT(TRACING_CPP) AddTraceConfigToLaunchParameters(
|
||||
const base::ReadOnlySharedMemoryRegion& read_only_memory_region,
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
@ -164,4 +193,23 @@ void COMPONENT_EXPORT(TRACING_CPP) AddTraceConfigToLaunchParameters(
|
||||
command_line, launch_options);
|
||||
}
|
||||
|
||||
void COMPONENT_EXPORT(TRACING_CPP) AddTraceOutputToLaunchParameters(
|
||||
const base::UnsafeSharedMemoryRegion& unsafe_memory_region,
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
base::GlobalDescriptors::Key descriptor_key,
|
||||
base::ScopedFD& out_descriptor_to_share,
|
||||
#endif
|
||||
base::CommandLine* command_line,
|
||||
base::LaunchOptions* launch_options) {
|
||||
base::shared_memory::AddToLaunchParameters(switches::kTraceBufferHandle,
|
||||
unsafe_memory_region,
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
kTraceBufferRendezvousKey,
|
||||
#elif BUILDFLAG(IS_POSIX)
|
||||
descriptor_key,
|
||||
out_descriptor_to_share,
|
||||
#endif
|
||||
command_line, launch_options);
|
||||
}
|
||||
|
||||
} // namespace tracing
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "base/component_export.h"
|
||||
#include "base/memory/read_only_shared_memory_region.h"
|
||||
#include "base/memory/unsafe_shared_memory_region.h"
|
||||
#include "base/process/launch.h"
|
||||
#include "build/build_config.h"
|
||||
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
|
||||
@ -61,6 +62,11 @@ void COMPONENT_EXPORT(TRACING_CPP)
|
||||
base::ReadOnlySharedMemoryRegion COMPONENT_EXPORT(TRACING_CPP)
|
||||
CreateTracingConfigSharedMemory();
|
||||
|
||||
// If tracing is enabled, returns a writeable SMB as destination of tracing
|
||||
// data, to be forwarded at child process creation.
|
||||
base::UnsafeSharedMemoryRegion COMPONENT_EXPORT(TRACING_CPP)
|
||||
CreateTracingOutputSharedMemory();
|
||||
|
||||
// Tells the child process to begin tracing right away via command line
|
||||
// flags and launch options, given a SMB config obtained with
|
||||
// CreateTracingConfigSharedMemory().
|
||||
@ -73,6 +79,16 @@ void COMPONENT_EXPORT(TRACING_CPP) AddTraceConfigToLaunchParameters(
|
||||
base::CommandLine* command_line,
|
||||
base::LaunchOptions* launch_options);
|
||||
|
||||
// Tells the child process to write tracing data to this SMB.
|
||||
void COMPONENT_EXPORT(TRACING_CPP) AddTraceOutputToLaunchParameters(
|
||||
const base::UnsafeSharedMemoryRegion& unsafe_memory_region,
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
base::GlobalDescriptors::Key descriptor_key,
|
||||
base::ScopedFD& out_descriptor_to_share,
|
||||
#endif
|
||||
base::CommandLine* command_line,
|
||||
base::LaunchOptions* launch_options);
|
||||
|
||||
} // namespace tracing
|
||||
|
||||
#endif // SERVICES_TRACING_PUBLIC_CPP_TRACE_STARTUP_H_
|
||||
|
@ -23,6 +23,7 @@ class BackgroundStartupTracingTest;
|
||||
|
||||
namespace tracing {
|
||||
class TraceStartupConfigTest;
|
||||
class TraceStartupSharedMemoryTest;
|
||||
|
||||
// TraceStartupConfig is a singleton that contains the configurations of startup
|
||||
// tracing. One can use --trace-startup flag or, for more complicated
|
||||
@ -146,6 +147,7 @@ class COMPONENT_EXPORT(TRACING_CPP) TraceStartupConfig {
|
||||
friend class content::CommandlineStartupTracingTest;
|
||||
friend class content::BackgroundStartupTracingTest;
|
||||
friend class ::tracing::TraceStartupConfigTest;
|
||||
friend class ::tracing::TraceStartupSharedMemoryTest;
|
||||
|
||||
constexpr static int kDefaultStartupDurationInSeconds = 5;
|
||||
|
||||
|
@ -0,0 +1,183 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/shared_memory_switch.h"
|
||||
#include "base/memory/unsafe_shared_memory_region.h"
|
||||
#include "base/process/launch.h"
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "base/task/thread_pool/thread_pool_instance.h"
|
||||
#include "base/test/multiprocess_test.h"
|
||||
#include "base/test/test_timeouts.h"
|
||||
#include "base/trace_event/trace_log.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/tracing/common/tracing_switches.h"
|
||||
#include "mojo/core/embedder/embedder.h"
|
||||
#include "services/tracing/public/cpp/perfetto/shared_memory.h"
|
||||
#include "services/tracing/public/cpp/trace_startup.h"
|
||||
#include "services/tracing/public/cpp/trace_startup_config.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/multiprocess_func_list.h"
|
||||
|
||||
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
|
||||
#include "base/posix/global_descriptors.h"
|
||||
#endif
|
||||
|
||||
namespace tracing {
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
constexpr base::GlobalDescriptors::Key kArbitraryDescriptorKey = 42;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(TraceStartupSharedMemoryTest, Create) {
|
||||
base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kTraceStartup);
|
||||
auto shared_memory = CreateTracingOutputSharedMemory();
|
||||
|
||||
ASSERT_TRUE(shared_memory.IsValid());
|
||||
EXPECT_EQ(kDefaultSharedMemorySize, shared_memory.GetSize());
|
||||
}
|
||||
|
||||
MULTIPROCESS_TEST_MAIN(InitFromLaunchParameters) {
|
||||
// On POSIX we generally use the descriptor map to look up inherited handles.
|
||||
// On most POSIX platforms we have to manually make sure the mapping is updated,
|
||||
// for the purposes of this test.
|
||||
//
|
||||
// Note:
|
||||
// - This doesn't apply on Apple platforms (which use Rendezvous Keys)
|
||||
// - On Android the global descriptor table is managed by the launcher
|
||||
// service, so we don't have to manually update the mapping here.
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID)
|
||||
base::GlobalDescriptors::GetInstance()->Set(
|
||||
kArbitraryDescriptorKey,
|
||||
kArbitraryDescriptorKey + base::GlobalDescriptors::kBaseDescriptor);
|
||||
#endif
|
||||
|
||||
EXPECT_FALSE(IsTracingInitialized());
|
||||
|
||||
// On Windows and Fuchsia getting shmem handle from --trace-buffer-handle can
|
||||
// only be done once and subsequent calls `UnsafeSharedMemoryRegionFrom()`
|
||||
// calls will not get a valid `shmem_region`. So we skip tracing init, to
|
||||
// avoid `ConnectProducer()` grabbing the shmem first.
|
||||
#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_FUCHSIA)
|
||||
base::FeatureList::InitInstance("", "");
|
||||
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("StartupTraceTest");
|
||||
tracing::InitTracingPostThreadPoolStartAndFeatureList(
|
||||
/*enable_consumer=*/false);
|
||||
|
||||
// Simulate launching with the serialized parameters.
|
||||
EnableStartupTracingIfNeeded();
|
||||
EXPECT_TRUE(IsTracingInitialized());
|
||||
EXPECT_TRUE(base::trace_event::TraceLog::GetInstance()->IsEnabled());
|
||||
#endif // !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_FUCHSIA)
|
||||
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
base::UnsafeSharedMemoryRegion unsafe_shm;
|
||||
EXPECT_TRUE(command_line->HasSwitch(switches::kTraceBufferHandle));
|
||||
auto shmem_region = base::shared_memory::UnsafeSharedMemoryRegionFrom(
|
||||
command_line->GetSwitchValueASCII(switches::kTraceBufferHandle));
|
||||
EXPECT_TRUE(shmem_region->IsValid());
|
||||
EXPECT_EQ(kDefaultSharedMemorySize, shmem_region->GetSize());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class TraceStartupSharedMemoryTest : public ::testing::TestWithParam<bool> {
|
||||
protected:
|
||||
void Initialize() {
|
||||
startup_config_ = base::WrapUnique(new TraceStartupConfig());
|
||||
}
|
||||
|
||||
std::unique_ptr<TraceStartupConfig> startup_config_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
TraceStartupSharedMemoryTest,
|
||||
::testing::Values(/*launch_options.elevated=*/false
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
,
|
||||
/*launch_options.elevated=*/true
|
||||
#endif
|
||||
));
|
||||
|
||||
TEST_P(TraceStartupSharedMemoryTest, PassSharedMemoryRegion) {
|
||||
base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kTraceStartup);
|
||||
|
||||
Initialize();
|
||||
ASSERT_TRUE(startup_config_->IsEnabled());
|
||||
|
||||
auto shm = CreateTracingOutputSharedMemory();
|
||||
ASSERT_TRUE(shm.IsValid());
|
||||
|
||||
// Initialize the command line and launch options.
|
||||
base::CommandLine command_line =
|
||||
base::GetMultiProcessTestChildBaseCommandLine();
|
||||
command_line.AppendSwitchASCII("type", "test-child");
|
||||
base::LaunchOptions launch_options;
|
||||
|
||||
// On windows, check both the elevated and non-elevated launches.
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
launch_options.start_hidden = true;
|
||||
launch_options.elevated = GetParam();
|
||||
#elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
base::ScopedFD descriptor_to_share;
|
||||
#endif
|
||||
|
||||
// Update the launch parameters.
|
||||
AddTraceOutputToLaunchParameters(shm,
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
kArbitraryDescriptorKey, descriptor_to_share,
|
||||
#endif
|
||||
&command_line, &launch_options);
|
||||
|
||||
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
// On posix, AddToLaunchParameters() ignores the launch options and instead
|
||||
// returns the descriptor to be shared. This is because the browser child
|
||||
// launcher helper manages a separate list of files to share via the zygote,
|
||||
// if available. If, like in this test scenario, there's ultimately no zygote
|
||||
// to use, launch helper updates the launch options to share the descriptor
|
||||
// mapping relative to a base descriptor.
|
||||
launch_options.fds_to_remap.emplace_back(descriptor_to_share.get(),
|
||||
kArbitraryDescriptorKey);
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
for (auto& pair : launch_options.fds_to_remap) {
|
||||
pair.second += base::GlobalDescriptors::kBaseDescriptor;
|
||||
}
|
||||
#endif // !BUILDFLAG(IS_ANDROID)
|
||||
#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
|
||||
|
||||
base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
||||
base::WaitableEvent::InitialState::NOT_SIGNALED);
|
||||
|
||||
// services/test/run_all_unittests.cc sets up ipc_thread, and android's
|
||||
// MultiprocessTestClientLauncher.launchClient asserts that child_process
|
||||
// cannot be called from main thread, so send this task to the io task runner.
|
||||
bool success = mojo::core::GetIOTaskRunner()->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](base::WaitableEvent* wait, const base::CommandLine& command_line,
|
||||
const base::LaunchOptions& launch_options) {
|
||||
// Launch the child process.
|
||||
base::Process process = base::SpawnMultiProcessTestChild(
|
||||
"InitFromLaunchParameters", command_line, launch_options);
|
||||
|
||||
// The child process returns non-zero if it could not open the
|
||||
// shared memory region based on the launch parameters.
|
||||
int exit_code = -1;
|
||||
EXPECT_TRUE(WaitForMultiprocessTestChildExit(
|
||||
process, TestTimeouts::action_timeout(), &exit_code));
|
||||
EXPECT_EQ(0, exit_code);
|
||||
},
|
||||
&wait, command_line, launch_options));
|
||||
|
||||
EXPECT_TRUE(success);
|
||||
wait.TimedWait(TestTimeouts::action_timeout());
|
||||
}
|
||||
|
||||
} // namespace tracing
|
Reference in New Issue
Block a user