• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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/profiling/common/proc_utils.h"
18 
19 #include <sys/stat.h>
20 #include <unistd.h>
21 
22 #include <cinttypes>
23 #include <optional>
24 
25 #include "perfetto/ext/base/file_utils.h"
26 #include "perfetto/ext/base/string_utils.h"
27 #include "src/profiling/common/proc_cmdline.h"
28 
29 namespace perfetto {
30 namespace profiling {
31 namespace {
32 
ParseProcStatusSize(const std::string & status,const std::string & key)33 std::optional<uint32_t> ParseProcStatusSize(const std::string& status,
34                                             const std::string& key) {
35   auto entry_idx = status.find(key);
36   if (entry_idx == std::string::npos)
37     return {};
38   entry_idx = status.find_first_not_of(" \t", entry_idx + key.size());
39   if (entry_idx == std::string::npos)
40     return {};
41   int32_t val = atoi(status.c_str() + entry_idx);
42   if (val < 0) {
43     PERFETTO_ELOG("Unexpected value reading %s", key.c_str());
44     return {};
45   }
46   return static_cast<uint32_t>(val);
47 }
48 }  // namespace
49 
ReadStatus(pid_t pid)50 std::optional<std::string> ReadStatus(pid_t pid) {
51   std::string path = "/proc/" + std::to_string(pid) + "/status";
52   std::string status;
53   bool read_proc = base::ReadFile(path, &status);
54   if (!read_proc) {
55     PERFETTO_ELOG("Failed to read %s", path.c_str());
56     return std::nullopt;
57   }
58   return std::optional<std::string>(status);
59 }
60 
GetRssAnonAndSwap(const std::string & status)61 std::optional<uint32_t> GetRssAnonAndSwap(const std::string& status) {
62   auto anon_rss = ParseProcStatusSize(status, "RssAnon:");
63   auto swap = ParseProcStatusSize(status, "VmSwap:");
64   if (anon_rss.has_value() && swap.has_value()) {
65     return *anon_rss + *swap;
66   }
67   return std::nullopt;
68 }
69 
RemoveUnderAnonThreshold(uint32_t min_size_kb,std::set<pid_t> * pids)70 void RemoveUnderAnonThreshold(uint32_t min_size_kb, std::set<pid_t>* pids) {
71   for (auto it = pids->begin(); it != pids->end();) {
72     const pid_t pid = *it;
73 
74     std::optional<std::string> status = ReadStatus(pid);
75     std::optional<uint32_t> rss_and_swap;
76     if (status)
77       rss_and_swap = GetRssAnonAndSwap(*status);
78 
79     if (rss_and_swap && rss_and_swap < min_size_kb) {
80       PERFETTO_LOG("Removing pid %d from profiled set (anon: %d kB < %" PRIu32
81                    ")",
82                    pid, *rss_and_swap, min_size_kb);
83       it = pids->erase(it);
84     } else {
85       ++it;
86     }
87   }
88 }
89 
GetUids(const std::string & status)90 std::optional<Uids> GetUids(const std::string& status) {
91   auto entry_idx = status.find("Uid:");
92   if (entry_idx == std::string::npos)
93     return std::nullopt;
94 
95   Uids uids;
96   const char* str = &status[entry_idx + 4];
97   char* endptr;
98 
99   uids.real = strtoull(str, &endptr, 10);
100   if (*endptr != ' ' && *endptr != '\t')
101     return std::nullopt;
102 
103   str = endptr;
104   uids.effective = strtoull(str, &endptr, 10);
105   if (*endptr != ' ' && *endptr != '\t')
106     return std::nullopt;
107 
108   str = endptr;
109   uids.saved_set = strtoull(str, &endptr, 10);
110   if (*endptr != ' ' && *endptr != '\t')
111     return std::nullopt;
112 
113   str = endptr;
114   uids.filesystem = strtoull(str, &endptr, 10);
115   if (*endptr != '\n' && *endptr != '\0')
116     return std::nullopt;
117   return uids;
118 }
119 
120 // Normalize cmdline in place. Stores new beginning of string in *cmdline_ptr.
121 // Returns new size of string (from new beginning).
122 // Modifies string in *cmdline_ptr.
NormalizeCmdLine(char ** cmdline_ptr,size_t size)123 ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size) {
124   char* cmdline = *cmdline_ptr;
125   char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
126   if (first_arg == nullptr) {
127     errno = EOVERFLOW;
128     return -1;
129   }
130   // For consistency with what we do with Java app cmdlines, trim everything
131   // after the @ sign of the first arg.
132   char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
133   if (first_at != nullptr && first_at < first_arg) {
134     *first_at = '\0';
135     first_arg = first_at;
136   }
137   char* start = static_cast<char*>(
138       memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
139   if (start == nullptr) {
140     start = cmdline;
141   } else {
142     // Skip the /.
143     start++;
144   }
145   *cmdline_ptr = start;
146   return first_arg - start;
147 }
148 
NormalizeCmdlines(const std::vector<std::string> & cmdlines)149 std::optional<std::vector<std::string>> NormalizeCmdlines(
150     const std::vector<std::string>& cmdlines) {
151   std::vector<std::string> normalized_cmdlines;
152   normalized_cmdlines.reserve(cmdlines.size());
153 
154   for (size_t i = 0; i < cmdlines.size(); i++) {
155     std::string cmdline = cmdlines[i];  // mutable copy
156     // Add nullbyte to make sure it's a C string.
157     cmdline.resize(cmdline.size() + 1, '\0');
158     char* cmdline_cstr = &(cmdline[0]);
159     ssize_t size = NormalizeCmdLine(&cmdline_cstr, cmdline.size());
160     if (size == -1) {
161       PERFETTO_PLOG("Failed to normalize cmdline %s. Stopping the parse.",
162                     cmdlines[i].c_str());
163       return std::nullopt;
164     }
165     normalized_cmdlines.emplace_back(cmdline_cstr, static_cast<size_t>(size));
166   }
167   return std::make_optional(normalized_cmdlines);
168 }
169 
170 // This is mostly the same as GetHeapprofdProgramProperty in
171 // https://android.googlesource.com/platform/bionic/+/master/libc/bionic/malloc_common.cpp
172 // This should give the same result as GetHeapprofdProgramProperty.
GetCmdlineForPID(pid_t pid,std::string * name)173 bool GetCmdlineForPID(pid_t pid, std::string* name) {
174   std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
175   base::ScopedFile fd(base::OpenFile(filename, O_RDONLY | O_CLOEXEC));
176   if (!fd) {
177     PERFETTO_DPLOG("Failed to open %s", filename.c_str());
178     return false;
179   }
180   char cmdline[512];
181   const size_t max_read_size = sizeof(cmdline) - 1;
182   ssize_t rd = read(*fd, cmdline, max_read_size);
183   if (rd == -1) {
184     PERFETTO_DPLOG("Failed to read %s", filename.c_str());
185     return false;
186   }
187 
188   if (rd == 0) {
189     PERFETTO_DLOG("Empty cmdline for %" PRIdMAX ". Skipping.",
190                   static_cast<intmax_t>(pid));
191     return false;
192   }
193 
194   // In some buggy kernels (before http://bit.ly/37R7qwL) /proc/pid/cmdline is
195   // not NUL-terminated (see b/147438623). If we read < max_read_size bytes
196   // assume we are hitting the aforementioned kernel bug and terminate anyways.
197   const size_t rd_u = static_cast<size_t>(rd);
198   if (rd_u >= max_read_size && memchr(cmdline, '\0', rd_u) == nullptr) {
199     // We did not manage to read the first argument.
200     PERFETTO_DLOG("Overflow reading cmdline for %" PRIdMAX,
201                   static_cast<intmax_t>(pid));
202     errno = EOVERFLOW;
203     return false;
204   }
205 
206   cmdline[rd] = '\0';
207   char* cmdline_start = cmdline;
208   ssize_t size = NormalizeCmdLine(&cmdline_start, rd_u);
209   if (size == -1)
210     return false;
211   name->assign(cmdline_start, static_cast<size_t>(size));
212   return true;
213 }
214 
FindAllProfilablePids(std::set<pid_t> * pids)215 void FindAllProfilablePids(std::set<pid_t>* pids) {
216   ForEachPid([pids](pid_t pid) {
217     if (pid == getpid())
218       return;
219 
220     char filename_buf[128];
221     snprintf(filename_buf, sizeof(filename_buf), "/proc/%d/%s", pid, "cmdline");
222     struct stat statbuf;
223     // Check if we have permission to the process.
224     if (stat(filename_buf, &statbuf) == 0)
225       pids->emplace(pid);
226   });
227 }
228 
FindPidsForCmdlines(const std::vector<std::string> & cmdlines,std::set<pid_t> * pids)229 void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
230                          std::set<pid_t>* pids) {
231   ForEachPid([&cmdlines, pids](pid_t pid) {
232     if (pid == getpid())
233       return;
234     std::string process_cmdline;
235     process_cmdline.reserve(512);
236     GetCmdlineForPID(pid, &process_cmdline);
237     for (const std::string& cmdline : cmdlines) {
238       if (process_cmdline == cmdline)
239         pids->emplace(static_cast<pid_t>(pid));
240     }
241   });
242 }
243 
244 namespace glob_aware {
FindPidsForCmdlinePatterns(const std::vector<std::string> & patterns,std::set<pid_t> * pids)245 void FindPidsForCmdlinePatterns(const std::vector<std::string>& patterns,
246                                 std::set<pid_t>* pids) {
247   ForEachPid([&patterns, pids](pid_t pid) {
248     if (pid == getpid())
249       return;
250     std::string cmdline;
251     if (!glob_aware::ReadProcCmdlineForPID(pid, &cmdline))
252       return;
253     const char* binname =
254         glob_aware::FindBinaryName(cmdline.c_str(), cmdline.size());
255 
256     for (const std::string& pattern : patterns) {
257       if (glob_aware::MatchGlobPattern(pattern.c_str(), cmdline.c_str(),
258                                        binname)) {
259         pids->insert(pid);
260       }
261     }
262   });
263 }
264 }  // namespace glob_aware
265 
266 }  // namespace profiling
267 }  // namespace perfetto
268