0

[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:
Kramer Ge
2025-01-23 09:19:53 -08:00
committed by Chromium LUCI CQ
parent a6770bb4a7
commit c80f84e4c9
20 changed files with 378 additions and 11 deletions

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