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