1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/tracing/ipc/service/producer_ipc_service.h"
18
19 #include <cinttypes>
20
21 #include "perfetto/base/logging.h"
22 #include "perfetto/base/task_runner.h"
23 #include "perfetto/ext/ipc/host.h"
24 #include "perfetto/ext/ipc/service.h"
25 #include "perfetto/ext/tracing/core/commit_data_request.h"
26 #include "perfetto/ext/tracing/core/tracing_service.h"
27 #include "perfetto/tracing/core/data_source_config.h"
28 #include "perfetto/tracing/core/data_source_descriptor.h"
29
30 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
31 #include "src/tracing/ipc/shared_memory_windows.h"
32 #else
33 #include "src/tracing/ipc/posix_shared_memory.h"
34 #endif
35
36 // The remote Producer(s) are not trusted. All the methods from the ProducerPort
37 // IPC layer (e.g. RegisterDataSource()) must assume that the remote Producer is
38 // compromised.
39
40 namespace perfetto {
41
ProducerIPCService(TracingService * core_service)42 ProducerIPCService::ProducerIPCService(TracingService* core_service)
43 : core_service_(core_service), weak_ptr_factory_(this) {}
44
45 ProducerIPCService::~ProducerIPCService() = default;
46
47 ProducerIPCService::RemoteProducer*
GetProducerForCurrentRequest()48 ProducerIPCService::GetProducerForCurrentRequest() {
49 const ipc::ClientID ipc_client_id = ipc::Service::client_info().client_id();
50 PERFETTO_CHECK(ipc_client_id);
51 auto it = producers_.find(ipc_client_id);
52 if (it == producers_.end())
53 return nullptr;
54 return it->second.get();
55 }
56
57 // Called by the remote Producer through the IPC channel soon after connecting.
InitializeConnection(const protos::gen::InitializeConnectionRequest & req,DeferredInitializeConnectionResponse response)58 void ProducerIPCService::InitializeConnection(
59 const protos::gen::InitializeConnectionRequest& req,
60 DeferredInitializeConnectionResponse response) {
61 const auto& client_info = ipc::Service::client_info();
62 const ipc::ClientID ipc_client_id = client_info.client_id();
63 PERFETTO_CHECK(ipc_client_id);
64
65 if (producers_.count(ipc_client_id) > 0) {
66 PERFETTO_DLOG(
67 "The remote Producer is trying to re-initialize the connection");
68 return response.Reject();
69 }
70
71 // Create a new entry.
72 std::unique_ptr<RemoteProducer> producer(new RemoteProducer());
73
74 TracingService::ProducerSMBScrapingMode smb_scraping_mode =
75 TracingService::ProducerSMBScrapingMode::kDefault;
76 switch (req.smb_scraping_mode()) {
77 case protos::gen::InitializeConnectionRequest::SMB_SCRAPING_UNSPECIFIED:
78 break;
79 case protos::gen::InitializeConnectionRequest::SMB_SCRAPING_DISABLED:
80 smb_scraping_mode = TracingService::ProducerSMBScrapingMode::kDisabled;
81 break;
82 case protos::gen::InitializeConnectionRequest::SMB_SCRAPING_ENABLED:
83 smb_scraping_mode = TracingService::ProducerSMBScrapingMode::kEnabled;
84 break;
85 }
86
87 // If the producer provided an SMB, tell the service to attempt to adopt it.
88 std::unique_ptr<SharedMemory> shmem;
89 if (req.producer_provided_shmem()) {
90 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
91 if (!req.has_shm_key_windows() || req.shm_key_windows().empty()) {
92 PERFETTO_ELOG(
93 "shm_key_windows must be non-empty when "
94 "producer_provided_shmem = true");
95 } else {
96 shmem = SharedMemoryWindows::Attach(req.shm_key_windows());
97 // Attach() does error logging if something fails, no need to extra ELOGs.
98 }
99 #else
100 base::ScopedFile shmem_fd = ipc::Service::TakeReceivedFD();
101
102 if (shmem_fd) {
103 shmem = PosixSharedMemory::AttachToFd(
104 std::move(shmem_fd), /*require_seals_if_supported=*/true);
105 if (!shmem) {
106 PERFETTO_ELOG(
107 "Couldn't map producer-provided SMB, falling back to "
108 "service-provided SMB");
109 }
110 } else {
111 PERFETTO_DLOG(
112 "InitializeConnectionRequest's producer_provided_shmem flag is set "
113 "but the producer didn't provide an FD");
114 }
115 #endif
116 }
117
118 // ConnectProducer will call OnConnect() on the next task.
119 producer->service_endpoint = core_service_->ConnectProducer(
120 producer.get(), client_info.uid(), client_info.pid(), req.producer_name(),
121 req.shared_memory_size_hint_bytes(),
122 /*in_process=*/false, smb_scraping_mode,
123 req.shared_memory_page_size_hint_bytes(), std::move(shmem),
124 req.sdk_version());
125
126 // Could happen if the service has too many producers connected.
127 if (!producer->service_endpoint) {
128 response.Reject();
129 return;
130 }
131
132 bool using_producer_shmem =
133 producer->service_endpoint->IsShmemProvidedByProducer();
134
135 producers_.emplace(ipc_client_id, std::move(producer));
136 // Because of the std::move() |producer| is invalid after this point.
137
138 auto async_res =
139 ipc::AsyncResult<protos::gen::InitializeConnectionResponse>::Create();
140 async_res->set_using_shmem_provided_by_producer(using_producer_shmem);
141 async_res->set_direct_smb_patching_supported(true);
142 response.Resolve(std::move(async_res));
143 }
144
145 // Called by the remote Producer through the IPC channel.
RegisterDataSource(const protos::gen::RegisterDataSourceRequest & req,DeferredRegisterDataSourceResponse response)146 void ProducerIPCService::RegisterDataSource(
147 const protos::gen::RegisterDataSourceRequest& req,
148 DeferredRegisterDataSourceResponse response) {
149 RemoteProducer* producer = GetProducerForCurrentRequest();
150 if (!producer) {
151 PERFETTO_DLOG(
152 "Producer invoked RegisterDataSource() before InitializeConnection()");
153 if (response.IsBound())
154 response.Reject();
155 return;
156 }
157
158 const DataSourceDescriptor& dsd = req.data_source_descriptor();
159 GetProducerForCurrentRequest()->service_endpoint->RegisterDataSource(dsd);
160
161 // RegisterDataSource doesn't expect any meaningful response.
162 if (response.IsBound()) {
163 response.Resolve(
164 ipc::AsyncResult<protos::gen::RegisterDataSourceResponse>::Create());
165 }
166 }
167
168 // Called by the remote Producer through the IPC channel.
UpdateDataSource(const protos::gen::UpdateDataSourceRequest & req,DeferredUpdateDataSourceResponse response)169 void ProducerIPCService::UpdateDataSource(
170 const protos::gen::UpdateDataSourceRequest& req,
171 DeferredUpdateDataSourceResponse response) {
172 RemoteProducer* producer = GetProducerForCurrentRequest();
173 if (!producer) {
174 PERFETTO_DLOG(
175 "Producer invoked UpdateDataSource() before InitializeConnection()");
176 if (response.IsBound())
177 response.Reject();
178 return;
179 }
180
181 const DataSourceDescriptor& dsd = req.data_source_descriptor();
182 GetProducerForCurrentRequest()->service_endpoint->UpdateDataSource(dsd);
183
184 // UpdateDataSource doesn't expect any meaningful response.
185 if (response.IsBound()) {
186 response.Resolve(
187 ipc::AsyncResult<protos::gen::UpdateDataSourceResponse>::Create());
188 }
189 }
190
191 // Called by the IPC layer.
OnClientDisconnected()192 void ProducerIPCService::OnClientDisconnected() {
193 ipc::ClientID client_id = ipc::Service::client_info().client_id();
194 PERFETTO_DLOG("Client %" PRIu64 " disconnected", client_id);
195 producers_.erase(client_id);
196 }
197
198 // TODO(fmayer): test what happens if we receive the following tasks, in order:
199 // RegisterDataSource, UnregisterDataSource, OnDataSourceRegistered.
200 // which essentially means that the client posted back to back a
201 // ReqisterDataSource and UnregisterDataSource speculating on the next id.
202 // Called by the remote Service through the IPC channel.
UnregisterDataSource(const protos::gen::UnregisterDataSourceRequest & req,DeferredUnregisterDataSourceResponse response)203 void ProducerIPCService::UnregisterDataSource(
204 const protos::gen::UnregisterDataSourceRequest& req,
205 DeferredUnregisterDataSourceResponse response) {
206 RemoteProducer* producer = GetProducerForCurrentRequest();
207 if (!producer) {
208 PERFETTO_DLOG(
209 "Producer invoked UnregisterDataSource() before "
210 "InitializeConnection()");
211 if (response.IsBound())
212 response.Reject();
213 return;
214 }
215 producer->service_endpoint->UnregisterDataSource(req.data_source_name());
216
217 // UnregisterDataSource doesn't expect any meaningful response.
218 if (response.IsBound()) {
219 response.Resolve(
220 ipc::AsyncResult<protos::gen::UnregisterDataSourceResponse>::Create());
221 }
222 }
223
RegisterTraceWriter(const protos::gen::RegisterTraceWriterRequest & req,DeferredRegisterTraceWriterResponse response)224 void ProducerIPCService::RegisterTraceWriter(
225 const protos::gen::RegisterTraceWriterRequest& req,
226 DeferredRegisterTraceWriterResponse response) {
227 RemoteProducer* producer = GetProducerForCurrentRequest();
228 if (!producer) {
229 PERFETTO_DLOG(
230 "Producer invoked RegisterTraceWriter() before "
231 "InitializeConnection()");
232 if (response.IsBound())
233 response.Reject();
234 return;
235 }
236 producer->service_endpoint->RegisterTraceWriter(req.trace_writer_id(),
237 req.target_buffer());
238
239 // RegisterTraceWriter doesn't expect any meaningful response.
240 if (response.IsBound()) {
241 response.Resolve(
242 ipc::AsyncResult<protos::gen::RegisterTraceWriterResponse>::Create());
243 }
244 }
245
UnregisterTraceWriter(const protos::gen::UnregisterTraceWriterRequest & req,DeferredUnregisterTraceWriterResponse response)246 void ProducerIPCService::UnregisterTraceWriter(
247 const protos::gen::UnregisterTraceWriterRequest& req,
248 DeferredUnregisterTraceWriterResponse response) {
249 RemoteProducer* producer = GetProducerForCurrentRequest();
250 if (!producer) {
251 PERFETTO_DLOG(
252 "Producer invoked UnregisterTraceWriter() before "
253 "InitializeConnection()");
254 if (response.IsBound())
255 response.Reject();
256 return;
257 }
258 producer->service_endpoint->UnregisterTraceWriter(req.trace_writer_id());
259
260 // UnregisterTraceWriter doesn't expect any meaningful response.
261 if (response.IsBound()) {
262 response.Resolve(
263 ipc::AsyncResult<protos::gen::UnregisterTraceWriterResponse>::Create());
264 }
265 }
266
CommitData(const protos::gen::CommitDataRequest & req,DeferredCommitDataResponse resp)267 void ProducerIPCService::CommitData(const protos::gen::CommitDataRequest& req,
268 DeferredCommitDataResponse resp) {
269 RemoteProducer* producer = GetProducerForCurrentRequest();
270 if (!producer) {
271 PERFETTO_DLOG(
272 "Producer invoked CommitData() before InitializeConnection()");
273 if (resp.IsBound())
274 resp.Reject();
275 return;
276 }
277
278 // We don't want to send a response if the client didn't attach a callback to
279 // the original request. Doing so would generate unnecessary wakeups and
280 // context switches.
281 std::function<void()> callback;
282 if (resp.IsBound()) {
283 // Capturing |resp| by reference here speculates on the fact that
284 // CommitData() in tracing_service_impl.cc invokes the passed callback
285 // inline, without posting it. If that assumption changes this code needs to
286 // wrap the response in a shared_ptr (C+11 lambdas don't support move) and
287 // use a weak ptr in the caller.
288 callback = [&resp] {
289 resp.Resolve(ipc::AsyncResult<protos::gen::CommitDataResponse>::Create());
290 };
291 }
292 producer->service_endpoint->CommitData(req, callback);
293 }
294
NotifyDataSourceStarted(const protos::gen::NotifyDataSourceStartedRequest & request,DeferredNotifyDataSourceStartedResponse response)295 void ProducerIPCService::NotifyDataSourceStarted(
296 const protos::gen::NotifyDataSourceStartedRequest& request,
297 DeferredNotifyDataSourceStartedResponse response) {
298 RemoteProducer* producer = GetProducerForCurrentRequest();
299 if (!producer) {
300 PERFETTO_DLOG(
301 "Producer invoked NotifyDataSourceStarted() before "
302 "InitializeConnection()");
303 if (response.IsBound())
304 response.Reject();
305 return;
306 }
307 producer->service_endpoint->NotifyDataSourceStarted(request.data_source_id());
308
309 // NotifyDataSourceStopped shouldn't expect any meaningful response, avoid
310 // a useless IPC in that case.
311 if (response.IsBound()) {
312 response.Resolve(ipc::AsyncResult<
313 protos::gen::NotifyDataSourceStartedResponse>::Create());
314 }
315 }
316
NotifyDataSourceStopped(const protos::gen::NotifyDataSourceStoppedRequest & request,DeferredNotifyDataSourceStoppedResponse response)317 void ProducerIPCService::NotifyDataSourceStopped(
318 const protos::gen::NotifyDataSourceStoppedRequest& request,
319 DeferredNotifyDataSourceStoppedResponse response) {
320 RemoteProducer* producer = GetProducerForCurrentRequest();
321 if (!producer) {
322 PERFETTO_DLOG(
323 "Producer invoked NotifyDataSourceStopped() before "
324 "InitializeConnection()");
325 if (response.IsBound())
326 response.Reject();
327 return;
328 }
329 producer->service_endpoint->NotifyDataSourceStopped(request.data_source_id());
330
331 // NotifyDataSourceStopped shouldn't expect any meaningful response, avoid
332 // a useless IPC in that case.
333 if (response.IsBound()) {
334 response.Resolve(ipc::AsyncResult<
335 protos::gen::NotifyDataSourceStoppedResponse>::Create());
336 }
337 }
338
ActivateTriggers(const protos::gen::ActivateTriggersRequest & proto_req,DeferredActivateTriggersResponse resp)339 void ProducerIPCService::ActivateTriggers(
340 const protos::gen::ActivateTriggersRequest& proto_req,
341 DeferredActivateTriggersResponse resp) {
342 RemoteProducer* producer = GetProducerForCurrentRequest();
343 if (!producer) {
344 PERFETTO_DLOG(
345 "Producer invoked ActivateTriggers() before InitializeConnection()");
346 if (resp.IsBound())
347 resp.Reject();
348 return;
349 }
350 std::vector<std::string> triggers;
351 for (const auto& name : proto_req.trigger_names()) {
352 triggers.push_back(name);
353 }
354 producer->service_endpoint->ActivateTriggers(triggers);
355 // ActivateTriggers shouldn't expect any meaningful response, avoid
356 // a useless IPC in that case.
357 if (resp.IsBound()) {
358 resp.Resolve(
359 ipc::AsyncResult<protos::gen::ActivateTriggersResponse>::Create());
360 }
361 }
362
GetAsyncCommand(const protos::gen::GetAsyncCommandRequest &,DeferredGetAsyncCommandResponse response)363 void ProducerIPCService::GetAsyncCommand(
364 const protos::gen::GetAsyncCommandRequest&,
365 DeferredGetAsyncCommandResponse response) {
366 RemoteProducer* producer = GetProducerForCurrentRequest();
367 if (!producer) {
368 PERFETTO_DLOG(
369 "Producer invoked GetAsyncCommand() before "
370 "InitializeConnection()");
371 return response.Reject();
372 }
373 // Keep the back channel open, without ever resolving the ipc::Deferred fully,
374 // to send async commands to the RemoteProducer (e.g., starting/stopping a
375 // data source).
376 producer->async_producer_commands = std::move(response);
377
378 // Service may already have issued the OnTracingSetup() event, in which case
379 // we should forward it to the producer now.
380 if (producer->send_setup_tracing_on_async_commands_bound)
381 producer->SendSetupTracing();
382 }
383
Sync(const protos::gen::SyncRequest &,DeferredSyncResponse resp)384 void ProducerIPCService::Sync(const protos::gen::SyncRequest&,
385 DeferredSyncResponse resp) {
386 RemoteProducer* producer = GetProducerForCurrentRequest();
387 if (!producer) {
388 PERFETTO_DLOG("Producer invoked Sync() before InitializeConnection()");
389 return resp.Reject();
390 }
391 auto weak_this = weak_ptr_factory_.GetWeakPtr();
392 auto resp_it = pending_syncs_.insert(pending_syncs_.end(), std::move(resp));
393 auto callback = [weak_this, resp_it]() {
394 if (!weak_this)
395 return;
396 auto pending_resp = std::move(*resp_it);
397 weak_this->pending_syncs_.erase(resp_it);
398 pending_resp.Resolve(ipc::AsyncResult<protos::gen::SyncResponse>::Create());
399 };
400 producer->service_endpoint->Sync(callback);
401 }
402
403 ////////////////////////////////////////////////////////////////////////////////
404 // RemoteProducer methods
405 ////////////////////////////////////////////////////////////////////////////////
406
407 ProducerIPCService::RemoteProducer::RemoteProducer() = default;
408 ProducerIPCService::RemoteProducer::~RemoteProducer() = default;
409
410 // Invoked by the |core_service_| business logic after the ConnectProducer()
411 // call. There is nothing to do here, we really expected the ConnectProducer()
412 // to just work in the local case.
OnConnect()413 void ProducerIPCService::RemoteProducer::OnConnect() {}
414
415 // Invoked by the |core_service_| business logic after we destroy the
416 // |service_endpoint| (in the RemoteProducer dtor).
OnDisconnect()417 void ProducerIPCService::RemoteProducer::OnDisconnect() {}
418
419 // Invoked by the |core_service_| business logic when it wants to create a new
420 // data source.
SetupDataSource(DataSourceInstanceID dsid,const DataSourceConfig & cfg)421 void ProducerIPCService::RemoteProducer::SetupDataSource(
422 DataSourceInstanceID dsid,
423 const DataSourceConfig& cfg) {
424 if (!async_producer_commands.IsBound()) {
425 PERFETTO_DLOG(
426 "The Service tried to create a new data source but the remote Producer "
427 "has not yet initialized the connection");
428 return;
429 }
430 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
431 cmd.set_has_more(true);
432 cmd->mutable_setup_data_source()->set_new_instance_id(dsid);
433 *cmd->mutable_setup_data_source()->mutable_config() = cfg;
434 async_producer_commands.Resolve(std::move(cmd));
435 }
436
437 // Invoked by the |core_service_| business logic when it wants to start a new
438 // data source.
StartDataSource(DataSourceInstanceID dsid,const DataSourceConfig & cfg)439 void ProducerIPCService::RemoteProducer::StartDataSource(
440 DataSourceInstanceID dsid,
441 const DataSourceConfig& cfg) {
442 if (!async_producer_commands.IsBound()) {
443 PERFETTO_DLOG(
444 "The Service tried to start a new data source but the remote Producer "
445 "has not yet initialized the connection");
446 return;
447 }
448 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
449 cmd.set_has_more(true);
450 cmd->mutable_start_data_source()->set_new_instance_id(dsid);
451 *cmd->mutable_start_data_source()->mutable_config() = cfg;
452 async_producer_commands.Resolve(std::move(cmd));
453 }
454
StopDataSource(DataSourceInstanceID dsid)455 void ProducerIPCService::RemoteProducer::StopDataSource(
456 DataSourceInstanceID dsid) {
457 if (!async_producer_commands.IsBound()) {
458 PERFETTO_DLOG(
459 "The Service tried to stop a data source but the remote Producer "
460 "has not yet initialized the connection");
461 return;
462 }
463 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
464 cmd.set_has_more(true);
465 cmd->mutable_stop_data_source()->set_instance_id(dsid);
466 async_producer_commands.Resolve(std::move(cmd));
467 }
468
OnTracingSetup()469 void ProducerIPCService::RemoteProducer::OnTracingSetup() {
470 if (!async_producer_commands.IsBound()) {
471 // Service may call this before the producer issued GetAsyncCommand.
472 send_setup_tracing_on_async_commands_bound = true;
473 return;
474 }
475 SendSetupTracing();
476 }
477
SendSetupTracing()478 void ProducerIPCService::RemoteProducer::SendSetupTracing() {
479 PERFETTO_CHECK(async_producer_commands.IsBound());
480 PERFETTO_CHECK(service_endpoint->shared_memory());
481 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
482 cmd.set_has_more(true);
483 auto setup_tracing = cmd->mutable_setup_tracing();
484 if (!service_endpoint->IsShmemProvidedByProducer()) {
485 // Nominal case (% Chrome): service provides SMB.
486 setup_tracing->set_shared_buffer_page_size_kb(
487 static_cast<uint32_t>(service_endpoint->shared_buffer_page_size_kb()));
488 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
489 const std::string& shm_key =
490 static_cast<SharedMemoryWindows*>(service_endpoint->shared_memory())
491 ->key();
492 setup_tracing->set_shm_key_windows(shm_key);
493 #else
494 const int shm_fd =
495 static_cast<PosixSharedMemory*>(service_endpoint->shared_memory())
496 ->fd();
497 cmd.set_fd(shm_fd);
498 #endif
499 }
500 async_producer_commands.Resolve(std::move(cmd));
501 }
502
Flush(FlushRequestID flush_request_id,const DataSourceInstanceID * data_source_ids,size_t num_data_sources)503 void ProducerIPCService::RemoteProducer::Flush(
504 FlushRequestID flush_request_id,
505 const DataSourceInstanceID* data_source_ids,
506 size_t num_data_sources) {
507 if (!async_producer_commands.IsBound()) {
508 PERFETTO_DLOG(
509 "The Service tried to request a flush but the remote Producer has not "
510 "yet initialized the connection");
511 return;
512 }
513 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
514 cmd.set_has_more(true);
515 for (size_t i = 0; i < num_data_sources; i++)
516 cmd->mutable_flush()->add_data_source_ids(data_source_ids[i]);
517 cmd->mutable_flush()->set_request_id(flush_request_id);
518 async_producer_commands.Resolve(std::move(cmd));
519 }
520
ClearIncrementalState(const DataSourceInstanceID * data_source_ids,size_t num_data_sources)521 void ProducerIPCService::RemoteProducer::ClearIncrementalState(
522 const DataSourceInstanceID* data_source_ids,
523 size_t num_data_sources) {
524 if (!async_producer_commands.IsBound()) {
525 PERFETTO_DLOG(
526 "The Service tried to request an incremental state invalidation, but "
527 "the remote Producer has not yet initialized the connection");
528 return;
529 }
530 auto cmd = ipc::AsyncResult<protos::gen::GetAsyncCommandResponse>::Create();
531 cmd.set_has_more(true);
532 for (size_t i = 0; i < num_data_sources; i++)
533 cmd->mutable_clear_incremental_state()->add_data_source_ids(
534 data_source_ids[i]);
535 async_producer_commands.Resolve(std::move(cmd));
536 }
537
538 } // namespace perfetto
539