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