1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/metrics/histogram_shared_memory.h"
6
7 #include <string_view>
8
9 #include "base/base_switches.h"
10 #include "base/debug/crash_logging.h"
11 #include "base/memory/shared_memory_mapping.h"
12 #include "base/memory/shared_memory_switch.h"
13 #include "base/memory/writable_shared_memory_region.h"
14 #include "base/metrics/histogram_macros_local.h"
15 #include "base/metrics/persistent_histogram_allocator.h"
16 #include "base/metrics/persistent_memory_allocator.h"
17 #include "base/process/launch.h"
18 #include "base/process/process_handle.h"
19 #include "base/process/process_info.h"
20 #include "base/strings/strcat.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/unguessable_token.h"
24
25 // On Apple platforms, the shared memory handle is shared using a Mach port
26 // rendezvous key.
27 #if BUILDFLAG(IS_APPLE)
28 #include "base/mac/mach_port_rendezvous.h"
29 #endif
30
31 // On POSIX, the shared memory handle is a file_descriptor mapped in the
32 // GlobalDescriptors table.
33 #if BUILDFLAG(IS_POSIX)
34 #include "base/posix/global_descriptors.h"
35 #endif
36
37 #if BUILDFLAG(IS_WIN)
38 #include <windows.h>
39
40 #include "base/win/win_util.h"
41 #endif
42
43 #if BUILDFLAG(IS_FUCHSIA)
44 #include <lib/zx/vmo.h>
45 #include <zircon/process.h>
46
47 #include "base/fuchsia/fuchsia_logging.h"
48 #endif
49
50 // This file supports passing a read/write histogram shared memory region
51 // between a parent process and child process. The information about the
52 // shared memory region is encoded into a command-line switch value.
53 //
54 // Format: "handle,[irp],guid-high,guid-low,size".
55 //
56 // The switch value is composed of 5 segments, separated by commas:
57 //
58 // 1. The platform-specific handle id for the shared memory as a string.
59 // 2. [irp] to indicate whether the handle is inherited (i, most platforms),
60 // sent via rendezvous (r, MacOS), or should be queried from the parent
61 // (p, Windows).
62 // 3. The high 64 bits of the shared memory block GUID.
63 // 4. The low 64 bits of the shared memory block GUID.
64 // 5. The size of the shared memory segment as a string.
65 //
66 // TODO(crbug.com/1028263): Refactor the common logic here and in
67 // base/metrics/field_trial.cc
68 namespace base {
69
70 BASE_FEATURE(kPassHistogramSharedMemoryOnLaunch,
71 "PassHistogramSharedMemoryOnLaunch",
72 FEATURE_DISABLED_BY_DEFAULT);
73
74 #if BUILDFLAG(IS_APPLE)
75 const MachPortsForRendezvous::key_type HistogramSharedMemory::kRendezvousKey =
76 'hsmr';
77 #endif
78
SharedMemory(UnsafeSharedMemoryRegion r,std::unique_ptr<PersistentMemoryAllocator> a)79 HistogramSharedMemory::SharedMemory::SharedMemory(
80 UnsafeSharedMemoryRegion r,
81 std::unique_ptr<PersistentMemoryAllocator> a)
82 : region(std::move(r)), allocator(std::move(a)) {
83 CHECK(region.IsValid());
84 CHECK(allocator);
85 }
86
87 HistogramSharedMemory::SharedMemory::~SharedMemory() = default;
88
89 HistogramSharedMemory::SharedMemory::SharedMemory(
90 HistogramSharedMemory::SharedMemory&&) = default;
91
92 HistogramSharedMemory::SharedMemory&
93 HistogramSharedMemory::SharedMemory::operator=(
94 HistogramSharedMemory::SharedMemory&&) = default;
95
96 // static
97 std::optional<HistogramSharedMemory::SharedMemory>
Create(int process_id,const HistogramSharedMemory::Config & config)98 HistogramSharedMemory::Create(int process_id,
99 const HistogramSharedMemory::Config& config) {
100 auto region = UnsafeSharedMemoryRegion::Create(config.memory_size_bytes);
101 if (!region.IsValid()) {
102 DVLOG(1) << "Failed to create shared memory region.";
103 return std::nullopt;
104 }
105 auto mapping = region.Map();
106 if (!mapping.IsValid()) {
107 DVLOG(1) << "Failed to create shared memory mapping.";
108 return std::nullopt;
109 }
110
111 return SharedMemory{std::move(region),
112 std::make_unique<WritableSharedPersistentMemoryAllocator>(
113 std::move(mapping), static_cast<uint64_t>(process_id),
114 config.allocator_name)};
115 }
116
117 // static
PassOnCommandLineIsEnabled(std::string_view process_type)118 bool HistogramSharedMemory::PassOnCommandLineIsEnabled(
119 std::string_view process_type) {
120 // On ChromeOS and for "utility" processes on other platforms there seems to
121 // be one or more mechanisms on startup which walk through all inherited
122 // shared memory regions and take a read-only handle to them. When we later
123 // attempt to deserialize the handle info and take a writable handle we
124 // find that the handle is already owned in read-only mode, triggering
125 // a crash due to "FD ownership violation".
126 //
127 // Example: The call to OpenSymbolFiles() in base/debug/stack_trace_posix.cc
128 // grabs a read-only handle to the shmem region for some process types.
129 //
130 // TODO(crbug.com/1028263): Fix ChromeOS and utility processes.
131 return (FeatureList::IsEnabled(kPassHistogramSharedMemoryOnLaunch)
132 #if BUILDFLAG(IS_CHROMEOS)
133 && process_type != "gpu-process"
134 #elif BUILDFLAG(IS_ANDROID)
135 && process_type != "utility"
136 #endif
137 );
138 }
139
140 // static
AddToLaunchParameters(UnsafeSharedMemoryRegion histogram_shmem_region,GlobalDescriptors::Key descriptor_key,ScopedFD & descriptor_to_share,CommandLine * command_line,LaunchOptions * launch_options)141 void HistogramSharedMemory::AddToLaunchParameters(
142 UnsafeSharedMemoryRegion histogram_shmem_region,
143 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
144 GlobalDescriptors::Key descriptor_key,
145 ScopedFD& descriptor_to_share,
146 #endif
147 CommandLine* command_line,
148 LaunchOptions* launch_options) {
149 CHECK(command_line);
150
151 const std::string process_type = command_line->GetSwitchValueASCII("type");
152 const bool enabled = PassOnCommandLineIsEnabled(process_type);
153
154 DVLOG(1) << (enabled ? "A" : "Not a")
155 << "dding histogram shared memory launch parameters for "
156 << process_type << " process.";
157
158 if (!enabled) {
159 return;
160 }
161
162 shared_memory::AddToLaunchParameters(::switches::kMetricsSharedMemoryHandle,
163 std::move(histogram_shmem_region),
164 #if BUILDFLAG(IS_APPLE)
165 kRendezvousKey,
166 #elif BUILDFLAG(IS_POSIX)
167 descriptor_key, descriptor_to_share,
168 #endif
169 command_line, launch_options);
170 }
171
172 // static
InitFromLaunchParameters(const CommandLine & command_line)173 void HistogramSharedMemory::InitFromLaunchParameters(
174 const CommandLine& command_line) {
175 // TODO(crbug.com/1028263): Clean up once fully launched.
176 if (!command_line.HasSwitch(switches::kMetricsSharedMemoryHandle)) {
177 return;
178 }
179 CHECK(!GlobalHistogramAllocator::Get());
180 DVLOG(1) << "Initializing histogram shared memory from command line for "
181 << command_line.GetSwitchValueASCII("type");
182
183 auto shmem_region = shared_memory::UnsafeSharedMemoryRegionFrom(
184 command_line.GetSwitchValueASCII(switches::kMetricsSharedMemoryHandle));
185
186 SCOPED_CRASH_KEY_NUMBER(
187 "HistogramAllocator", "SharedMemError",
188 static_cast<int>(shmem_region.has_value()
189 ? shared_memory::SharedMemoryError::kNoError
190 : shmem_region.error()));
191
192 CHECK(shmem_region.has_value() && shmem_region.value().IsValid())
193 << "Invald memory region passed on command line.";
194
195 GlobalHistogramAllocator::CreateWithSharedMemoryRegion(shmem_region.value());
196
197 auto* global_allocator = GlobalHistogramAllocator::Get();
198 CHECK(global_allocator);
199 global_allocator->CreateTrackingHistograms(global_allocator->Name());
200 }
201
202 } // namespace base
203