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