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: %u kB < %u)", pid,
81 *rss_and_swap, min_size_kb);
82 it = pids->erase(it);
83 } else {
84 ++it;
85 }
86 }
87 }
88
GetUids(const std::string & status)89 std::optional<Uids> GetUids(const std::string& status) {
90 auto entry_idx = status.find("Uid:");
91 if (entry_idx == std::string::npos)
92 return std::nullopt;
93
94 Uids uids;
95 const char* str = &status[entry_idx + 4];
96 char* endptr;
97
98 uids.real = strtoull(str, &endptr, 10);
99 if (*endptr != ' ' && *endptr != '\t')
100 return std::nullopt;
101
102 str = endptr;
103 uids.effective = strtoull(str, &endptr, 10);
104 if (*endptr != ' ' && *endptr != '\t')
105 return std::nullopt;
106
107 str = endptr;
108 uids.saved_set = strtoull(str, &endptr, 10);
109 if (*endptr != ' ' && *endptr != '\t')
110 return std::nullopt;
111
112 str = endptr;
113 uids.filesystem = strtoull(str, &endptr, 10);
114 if (*endptr != '\n' && *endptr != '\0')
115 return std::nullopt;
116 return uids;
117 }
118
119 // Normalize cmdline in place. Stores new beginning of string in *cmdline_ptr.
120 // Returns new size of string (from new beginning).
121 // Modifies string in *cmdline_ptr.
NormalizeCmdLine(char ** cmdline_ptr,size_t size)122 ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size) {
123 char* cmdline = *cmdline_ptr;
124 char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
125 if (first_arg == nullptr) {
126 errno = EOVERFLOW;
127 return -1;
128 }
129 // For consistency with what we do with Java app cmdlines, trim everything
130 // after the @ sign of the first arg.
131 char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
132 if (first_at != nullptr && first_at < first_arg) {
133 *first_at = '\0';
134 first_arg = first_at;
135 }
136 char* start = static_cast<char*>(
137 memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
138 if (start == nullptr) {
139 start = cmdline;
140 } else {
141 // Skip the /.
142 start++;
143 }
144 *cmdline_ptr = start;
145 return first_arg - start;
146 }
147
NormalizeCmdlines(const std::vector<std::string> & cmdlines)148 std::optional<std::vector<std::string>> NormalizeCmdlines(
149 const std::vector<std::string>& cmdlines) {
150 std::vector<std::string> normalized_cmdlines;
151 normalized_cmdlines.reserve(cmdlines.size());
152
153 for (size_t i = 0; i < cmdlines.size(); i++) {
154 std::string cmdline = cmdlines[i]; // mutable copy
155 // Add nullbyte to make sure it's a C string.
156 cmdline.resize(cmdline.size() + 1, '\0');
157 char* cmdline_cstr = &(cmdline[0]);
158 ssize_t size = NormalizeCmdLine(&cmdline_cstr, cmdline.size());
159 if (size == -1) {
160 PERFETTO_PLOG("Failed to normalize cmdline %s. Stopping the parse.",
161 cmdlines[i].c_str());
162 return std::nullopt;
163 }
164 normalized_cmdlines.emplace_back(cmdline_cstr, static_cast<size_t>(size));
165 }
166 return std::make_optional(normalized_cmdlines);
167 }
168
169 // This is mostly the same as GetHeapprofdProgramProperty in
170 // https://android.googlesource.com/platform/bionic/+/main/libc/bionic/malloc_common.cpp
171 // This should give the same result as GetHeapprofdProgramProperty.
GetCmdlineForPID(pid_t pid,std::string * name)172 bool GetCmdlineForPID(pid_t pid, std::string* name) {
173 std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
174 base::ScopedFile fd(base::OpenFile(filename, O_RDONLY | O_CLOEXEC));
175 if (!fd) {
176 PERFETTO_DPLOG("Failed to open %s", filename.c_str());
177 return false;
178 }
179 char cmdline[512];
180 const size_t max_read_size = sizeof(cmdline) - 1;
181 ssize_t rd = read(*fd, cmdline, max_read_size);
182 if (rd == -1) {
183 PERFETTO_DPLOG("Failed to read %s", filename.c_str());
184 return false;
185 }
186
187 if (rd == 0) {
188 PERFETTO_DLOG("Empty cmdline for %" PRIdMAX ". Skipping.",
189 static_cast<intmax_t>(pid));
190 return false;
191 }
192
193 // In some buggy kernels (before http://bit.ly/37R7qwL) /proc/pid/cmdline is
194 // not NUL-terminated (see b/147438623). If we read < max_read_size bytes
195 // assume we are hitting the aforementioned kernel bug and terminate anyways.
196 const size_t rd_u = static_cast<size_t>(rd);
197 if (rd_u >= max_read_size && memchr(cmdline, '\0', rd_u) == nullptr) {
198 // We did not manage to read the first argument.
199 PERFETTO_DLOG("Overflow reading cmdline for %" PRIdMAX,
200 static_cast<intmax_t>(pid));
201 errno = EOVERFLOW;
202 return false;
203 }
204
205 cmdline[rd] = '\0';
206 char* cmdline_start = cmdline;
207 ssize_t size = NormalizeCmdLine(&cmdline_start, rd_u);
208 if (size == -1)
209 return false;
210 name->assign(cmdline_start, static_cast<size_t>(size));
211 return true;
212 }
213
FindAllProfilablePids(std::set<pid_t> * pids)214 void FindAllProfilablePids(std::set<pid_t>* pids) {
215 ForEachPid([pids](pid_t pid) {
216 if (pid == getpid())
217 return;
218
219 char filename_buf[128];
220 snprintf(filename_buf, sizeof(filename_buf), "/proc/%d/%s", pid, "cmdline");
221 struct stat statbuf;
222 // Check if we have permission to the process.
223 if (stat(filename_buf, &statbuf) == 0)
224 pids->emplace(pid);
225 });
226 }
227
FindPidsForCmdlines(const std::vector<std::string> & cmdlines,std::set<pid_t> * pids)228 void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
229 std::set<pid_t>* pids) {
230 ForEachPid([&cmdlines, pids](pid_t pid) {
231 if (pid == getpid())
232 return;
233 std::string process_cmdline;
234 process_cmdline.reserve(512);
235 GetCmdlineForPID(pid, &process_cmdline);
236 for (const std::string& cmdline : cmdlines) {
237 if (process_cmdline == cmdline)
238 pids->emplace(static_cast<pid_t>(pid));
239 }
240 });
241 }
242
243 namespace glob_aware {
FindPidsForCmdlinePatterns(const std::vector<std::string> & patterns,std::set<pid_t> * pids)244 void FindPidsForCmdlinePatterns(const std::vector<std::string>& patterns,
245 std::set<pid_t>* pids) {
246 ForEachPid([&patterns, pids](pid_t pid) {
247 if (pid == getpid())
248 return;
249 std::string cmdline;
250 if (!glob_aware::ReadProcCmdlineForPID(pid, &cmdline))
251 return;
252 const char* binname =
253 glob_aware::FindBinaryName(cmdline.c_str(), cmdline.size());
254
255 for (const std::string& pattern : patterns) {
256 if (glob_aware::MatchGlobPattern(pattern.c_str(), cmdline.c_str(),
257 binname)) {
258 pids->insert(pid);
259 }
260 }
261 });
262 }
263 } // namespace glob_aware
264
265 } // namespace profiling
266 } // namespace perfetto
267