1 /*
2  * Copyright (C) 2023 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 "berberis/runtime_primitives/profiler_interface.h"
18 
19 #include <fcntl.h>   // open
20 #include <unistd.h>  // write
21 
22 #include <array>
23 #include <cstring>  // str*
24 
25 #include "berberis/base/config_globals.h"
26 #include "berberis/base/format_buffer.h"
27 #include "berberis/base/gettid.h"
28 #include "berberis/base/maps_snapshot.h"
29 #include "berberis/base/scoped_errno.h"
30 #include "berberis/base/tracing.h"
31 #include "berberis/guest_state/guest_addr.h"
32 
33 namespace berberis {
34 
35 namespace {
36 
ProfilerOpenLogFile()37 int ProfilerOpenLogFile() {
38   auto env = GetProfilingConfig();
39   if (!env) {
40     TRACE("Profiling: None");
41     return -1;
42   }
43 
44   auto app = GetAppPackageName();
45 
46   if (strcmp(env, "1") == 0) {
47     // Special case - profile everything.
48   } else if (app) {
49     // Running an app - must match package name.
50     if (strcmp(app, env) != 0) {
51       TRACE("Profiling: Skipping: app %s doesn't match filter %s", app, env);
52       return -1;
53     }
54   } else if (auto exe = GetMainExecutableRealPath()) {
55     // Running a standalone program - must somehow match main executable path.
56     if (!strstr(exe, env)) {
57       TRACE("Profiling: Skipping: executable %s doesn't match filter %s", exe, env);
58       return -1;
59     }
60   } else {
61     // Running a unit test, or some other non-app, non-executable case.
62     return -1;
63   }
64 
65   ScopedErrno scoped_errno;
66 
67   char buf[160];
68 
69   int pid = GetpidSyscall();
70   if (app) {
71     FormatBuffer(buf, sizeof(buf), "/data/data/%s/perf-%u.map", app, pid);
72   } else {
73     FormatBuffer(buf, sizeof(buf), "/data/local/tmp/perf-%u.map", pid);
74   }
75 
76   int fd = open(buf, O_WRONLY | O_CREAT | O_CLOEXEC, S_IWUSR);
77   if (fd == -1) {
78     TRACE("Profiling Error: Failed to open map file %s", buf);
79   } else {
80     TRACE("Probfiling to %s", buf);
81   }
82   return fd;
83 }
84 
85 constexpr size_t kMaxMappedNameLen = 16;
86 // Name c-string + terminating null + underscore.
87 using MappedNameBuffer = std::array<char, kMaxMappedNameLen + 2>;
88 
89 // Malloc-free implementation.
ConstructMappedNameBuffer(GuestAddr guest_addr)90 MappedNameBuffer ConstructMappedNameBuffer(GuestAddr guest_addr) {
91   MappedNameBuffer buf;
92   auto* maps_snapshot = MapsSnapshot::GetInstance();
93 
94   auto mapped_name = maps_snapshot->FindMappedObjectName(guest_addr);
95   if (!mapped_name.has_value()) {
96     // If no mapping is found renew the snapshot and try again.
97     maps_snapshot->Update();
98     auto updated_mapped_name = maps_snapshot->FindMappedObjectName(guest_addr);
99     if (!updated_mapped_name.has_value()) {
100       TRACE("Guest addr %p not found in /proc/self/maps", ToHostAddr<void>(guest_addr));
101       buf[0] = '\0';
102       return buf;
103     }
104     mapped_name.emplace(std::move(updated_mapped_name.value()));
105   }
106 
107   // We can use more clever logic here and try to extract the basename, but the parent directory
108   // name may also be interesting (e.g. <guest_arch>/libc.so) so we just take the last
109   // kMaxMappedNameLen symbols for simplicity until it's proven we need something more advanced.
110   // An added benefit of this approach is that symbols look well aligned in the profile.
111   auto& result = mapped_name.value();
112   size_t terminator_pos;
113   if (result.length() > kMaxMappedNameLen) {
114     // In this case it should be safe to call strcpy, but we still use strncpy to be extra careful.
115     strncpy(buf.data(), result.c_str() + result.length() - kMaxMappedNameLen, kMaxMappedNameLen);
116     terminator_pos = kMaxMappedNameLen;
117   } else {
118     strncpy(buf.data(), result.c_str(), kMaxMappedNameLen);
119     terminator_pos = result.length();
120   }
121   buf[terminator_pos] = '_';
122   buf[terminator_pos + 1] = '\0';
123 
124   return buf;
125 }
126 
127 }  // namespace
128 
ProfilerLogGeneratedCode(const void * start,size_t size,GuestAddr guest_start,size_t guest_size,const char * jit_suffix)129 void ProfilerLogGeneratedCode(const void* start,
130                               size_t size,
131                               GuestAddr guest_start,
132                               size_t guest_size,
133                               const char* jit_suffix) {
134   static int fd = ProfilerOpenLogFile();
135   if (fd == -1) {
136     return;
137   }
138 
139   MappedNameBuffer mapped_name_buf = ConstructMappedNameBuffer(guest_start);
140 
141   char buf[128];
142   // start size symbol-name
143   size_t n = FormatBuffer(buf,
144                           sizeof(buf),
145                           "%p 0x%zx %s%s_0x%lx+%zu\n",
146                           start,
147                           size,
148                           mapped_name_buf.data(),
149                           jit_suffix,
150                           guest_start,
151                           guest_size);
152   UNUSED(write(fd, buf, n));
153 }
154 
155 }  // namespace berberis
156