• 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/memory/proc_utils.h"
18 
19 #include <inttypes.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include "perfetto/base/file_utils.h"
24 
25 namespace perfetto {
26 namespace profiling {
27 namespace {
28 
29 #if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
30 
FindChar(const char * s,char c,size_t n)31 const char* FindChar(const char* s, char c, size_t n) {
32   std::string str(s, n);
33   auto idx = str.rfind(c);
34   if (idx == std::string::npos)
35     return nullptr;
36   return s + n;
37 }
38 
memrchr(const void * s,int c,size_t n)39 void* memrchr(const void* s, int c, size_t n) {
40   return static_cast<void*>(const_cast<char*>(
41       FindChar(static_cast<const char*>(s), static_cast<char>(c), n)));
42 }
43 
44 #endif
45 
GetProcFile(pid_t pid,const char * file,char * filename_buf,size_t size)46 bool GetProcFile(pid_t pid, const char* file, char* filename_buf, size_t size) {
47   ssize_t written = snprintf(filename_buf, size, "/proc/%d/%s", pid, file);
48   if (written < 0 || static_cast<size_t>(written) >= size) {
49     if (written < 0)
50       PERFETTO_ELOG("Failed to concatenate cmdline file.");
51     else
52       PERFETTO_ELOG("Overflow when concatenating cmdline file.");
53     return false;
54   }
55   return true;
56 }
57 
58 }  // namespace
59 
NormalizeCmdLine(char * cmdline,size_t size,std::string * name)60 bool NormalizeCmdLine(char* cmdline, size_t size, std::string* name) {
61   char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
62   if (first_arg == nullptr) {
63     PERFETTO_DLOG("Overflow reading cmdline");
64     errno = EOVERFLOW;
65     return false;
66   }
67   // For consistency with what we do with Java app cmdlines, trim everything
68   // after the @ sign of the first arg.
69   char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
70   if (first_at != nullptr && first_at < first_arg) {
71     *first_at = '\0';
72     first_arg = first_at;
73   }
74   char* start = static_cast<char*>(
75       memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
76   if (start == first_arg) {
77     // The first argument ended in a slash.
78     PERFETTO_DLOG("cmdline ends in /");
79     errno = EINVAL;
80     return false;
81   } else if (start == nullptr) {
82     start = cmdline;
83   } else {
84     // Skip the /.
85     start++;
86   }
87   size_t name_size = static_cast<size_t>(first_arg - start);
88   name->assign(start, name_size);
89   return true;
90 }
91 
NormalizeCmdlines(const std::vector<std::string> & cmdlines)92 std::vector<std::string> NormalizeCmdlines(
93     const std::vector<std::string>& cmdlines) {
94   std::vector<std::string> normalized_cmdlines;
95   for (std::string cmdline : cmdlines) {
96     // Add nullbyte to make sure it's a C string.
97     cmdline.resize(cmdline.size() + 1, '\0');
98     std::string normalized;
99     if (!NormalizeCmdLine(&(cmdline[0]), cmdline.size(), &normalized)) {
100       PERFETTO_ELOG("Failed to normalize cmdline %s. Skipping.",
101                     cmdline.c_str());
102       continue;
103     }
104     normalized_cmdlines.emplace_back(std::move(normalized));
105   }
106   return normalized_cmdlines;
107 }
108 
109 // This is mostly the same as GetHeapprofdProgramProperty in
110 // https://android.googlesource.com/platform/bionic/+/master/libc/bionic/malloc_common.cpp
111 // This should give the same result as GetHeapprofdProgramProperty.
GetCmdlineForPID(pid_t pid,std::string * name)112 bool GetCmdlineForPID(pid_t pid, std::string* name) {
113   std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
114   base::ScopedFile fd(base::OpenFile(filename, O_RDONLY | O_CLOEXEC));
115   if (!fd) {
116     PERFETTO_DLOG("Failed to open %s", filename.c_str());
117     return false;
118   }
119   char cmdline[512];
120   ssize_t rd = read(*fd, cmdline, sizeof(cmdline) - 1);
121   if (rd == -1) {
122     PERFETTO_DLOG("Failed to read %s", filename.c_str());
123     return false;
124   }
125 
126   if (rd == 0) {
127     PERFETTO_DLOG("Empty cmdline for %" PRIdMAX ". Skipping.",
128                   static_cast<intmax_t>(pid));
129     return false;
130   }
131 
132   // We did not manage to read the first argument.
133   if (memchr(cmdline, '\0', static_cast<size_t>(rd)) == nullptr) {
134     PERFETTO_DLOG("Overflow reading cmdline for %" PRIdMAX,
135                   static_cast<intmax_t>(pid));
136     errno = EOVERFLOW;
137     return false;
138   }
139 
140   cmdline[rd] = '\0';
141   return NormalizeCmdLine(cmdline, static_cast<size_t>(rd), name);
142 }
143 
FindAllProfilablePids(std::set<pid_t> * pids)144 void FindAllProfilablePids(std::set<pid_t>* pids) {
145   ForEachPid([pids](pid_t pid) {
146     if (pid == getpid())
147       return;
148 
149     char filename_buf[128];
150     if (!GetProcFile(pid, "cmdline", filename_buf, sizeof(filename_buf)))
151       return;
152     struct stat statbuf;
153     // Check if we have permission to the process.
154     if (stat(filename_buf, &statbuf) == 0)
155       pids->emplace(pid);
156   });
157 }
158 
FindPidsForCmdlines(const std::vector<std::string> & cmdlines,std::set<pid_t> * pids)159 void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
160                          std::set<pid_t>* pids) {
161   ForEachPid([&cmdlines, pids](pid_t pid) {
162     if (pid == getpid())
163       return;
164     std::string process_cmdline;
165     process_cmdline.reserve(512);
166     GetCmdlineForPID(pid, &process_cmdline);
167     for (const std::string& cmdline : cmdlines) {
168       if (process_cmdline == cmdline)
169         pids->emplace(static_cast<pid_t>(pid));
170     }
171   });
172 }
173 
174 }  // namespace profiling
175 }  // namespace perfetto
176