1 /*
2 * Copyright (C) 2022 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_cmdline.h"
18
19 #include <fcntl.h>
20 #include <fnmatch.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "perfetto/ext/base/file_utils.h"
27
28 namespace perfetto {
29 namespace profiling {
30 namespace glob_aware {
31
32 // Edge cases: the raw cmdline as read out of the kernel can have several
33 // shapes, the process can rewrite the contents to be arbitrary, and overly long
34 // cmdlines can be truncated as we use a 511 byte limit. Some examples to
35 // consider for the implementation:
36 // * "echo\0hello\0"
37 // * "/bin/top\0\0\0\0\0\0\0"
38 // * "arbitrary string as rewritten by the process\0"
39 // * "some_bugged_kernels_forget_final_nul_terminator"
40 //
41 // The approach when performing the read->derive->match is to minimize early
42 // return codepaths for the caller. So even if we read a non-conforming cmdline
43 // (e.g. just a single nul byte), it can still be fed through FindBinaryName and
44 // MatchGlobPattern. It'll just make the intermediate strings be empty (so
45 // starting with a nul byte, but never nullptr).
46 //
47 // NB: bionic/libc/bionic/malloc_heapprofd will require a parallel
48 // implementation of these functions (to avoid a bionic->perfetto dependency).
49 // Keep them as STL-free as possible to allow for both implementations to be
50 // close to verbatim copies.
51
52 // TODO(rsavitski): consider changing to std::optional<> return type.
ReadProcCmdlineForPID(pid_t pid,std::string * cmdline_out)53 bool ReadProcCmdlineForPID(pid_t pid, std::string* cmdline_out) {
54 std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
55 base::ScopedFile fd(base::OpenFile(filename, O_RDONLY));
56 if (!fd) {
57 PERFETTO_DPLOG("Failed to open %s", filename.c_str());
58 return false;
59 }
60
61 // buf is 511 bytes to match an implementation that adds a null terminator to
62 // the back of a 512 byte buffer.
63 char buf[511];
64 ssize_t rd = PERFETTO_EINTR(read(*fd, buf, sizeof(buf)));
65 if (rd < 0) {
66 PERFETTO_DPLOG("Failed to read %s", filename.c_str());
67 return false;
68 }
69
70 cmdline_out->assign(buf, static_cast<size_t>(rd));
71 return true;
72 }
73
74 // Returns a pointer into |cmdline| corresponding to the argv0 without any
75 // leading directories if the binary path is absolute. |cmdline_len| corresponds
76 // to the length of the cmdline string as read out of procfs as a C string -
77 // length doesn't include the final nul terminator, but it must be present at
78 // cmdline[cmdline_len]. Note that normally the string itself will contain nul
79 // bytes, as that's what the kernel uses to separate arguments.
80 //
81 // Function output examples:
82 // * /system/bin/adb\0--flag -> adb
83 // * adb -> adb
84 // * com.example.app -> com.example.app
FindBinaryName(const char * cmdline,size_t cmdline_len)85 const char* FindBinaryName(const char* cmdline, size_t cmdline_len) {
86 // Find the first nul byte that signifies the end of argv0. We might not find
87 // one if the process rewrote its cmdline without nul separators, and/or the
88 // cmdline didn't fully fit into our read buffer. In such cases, proceed with
89 // the full string to do best-effort matching.
90 const char* argv0_end =
91 static_cast<const char*>(memchr(cmdline, '\0', cmdline_len));
92 if (argv0_end == nullptr) {
93 argv0_end = cmdline + cmdline_len; // set to final nul terminator
94 }
95 // Find the last path separator of argv0, if it exists.
96 const char* name_start = static_cast<const char*>(
97 memrchr(cmdline, '/', static_cast<size_t>(argv0_end - cmdline)));
98 if (name_start == nullptr) {
99 name_start = cmdline;
100 } else {
101 name_start++; // skip the separator
102 }
103 return name_start;
104 }
105
106 // All inputs must be non-nullptr, but can start with a nul byte.
MatchGlobPattern(const char * pattern,const char * cmdline,const char * binname)107 bool MatchGlobPattern(const char* pattern,
108 const char* cmdline,
109 const char* binname) {
110 if (pattern[0] == '/') {
111 return fnmatch(pattern, cmdline, FNM_NOESCAPE) == 0;
112 }
113 return fnmatch(pattern, binname, FNM_NOESCAPE) == 0;
114 }
115
116 } // namespace glob_aware
117 } // namespace profiling
118 } // namespace perfetto
119