/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "src/profiling/common/proc_cmdline.h" #include #include #include #include #include #include #include "perfetto/ext/base/file_utils.h" namespace perfetto { namespace profiling { namespace glob_aware { // Edge cases: the raw cmdline as read out of the kernel can have several // shapes, the process can rewrite the contents to be arbitrary, and overly long // cmdlines can be truncated as we use a 511 byte limit. Some examples to // consider for the implementation: // * "echo\0hello\0" // * "/bin/top\0\0\0\0\0\0\0" // * "arbitrary string as rewritten by the process\0" // * "some_bugged_kernels_forget_final_nul_terminator" // // The approach when performing the read->derive->match is to minimize early // return codepaths for the caller. So even if we read a non-conforming cmdline // (e.g. just a single nul byte), it can still be fed through FindBinaryName and // MatchGlobPattern. It'll just make the intermediate strings be empty (so // starting with a nul byte, but never nullptr). // // NB: bionic/libc/bionic/malloc_heapprofd will require a parallel // implementation of these functions (to avoid a bionic->perfetto dependency). // Keep them as STL-free as possible to allow for both implementations to be // close to verbatim copies. // TODO(rsavitski): consider changing to Optional<> return type. bool ReadProcCmdlineForPID(pid_t pid, std::string* cmdline_out) { std::string filename = "/proc/" + std::to_string(pid) + "/cmdline"; base::ScopedFile fd(base::OpenFile(filename, O_RDONLY)); if (!fd) { PERFETTO_DPLOG("Failed to open %s", filename.c_str()); return false; } // buf is 511 bytes to match an implementation that adds a null terminator to // the back of a 512 byte buffer. char buf[511]; ssize_t rd = PERFETTO_EINTR(read(*fd, buf, sizeof(buf))); if (rd < 0) { PERFETTO_DPLOG("Failed to read %s", filename.c_str()); return false; } cmdline_out->assign(buf, static_cast(rd)); return true; } // Returns a pointer into |cmdline| corresponding to the argv0 without any // leading directories if the binary path is absolute. |cmdline_len| corresponds // to the length of the cmdline string as read out of procfs as a C string - // length doesn't include the final nul terminator, but it must be present at // cmdline[cmdline_len]. Note that normally the string itself will contain nul // bytes, as that's what the kernel uses to separate arguments. // // Function output examples: // * /system/bin/adb\0--flag -> adb // * adb -> adb // * com.example.app -> com.example.app const char* FindBinaryName(const char* cmdline, size_t cmdline_len) { // Find the first nul byte that signifies the end of argv0. We might not find // one if the process rewrote its cmdline without nul separators, and/or the // cmdline didn't fully fit into our read buffer. In such cases, proceed with // the full string to do best-effort matching. const char* argv0_end = static_cast(memchr(cmdline, '\0', cmdline_len)); if (argv0_end == nullptr) { argv0_end = cmdline + cmdline_len; // set to final nul terminator } // Find the last path separator of argv0, if it exists. const char* name_start = static_cast( memrchr(cmdline, '/', static_cast(argv0_end - cmdline))); if (name_start == nullptr) { name_start = cmdline; } else { name_start++; // skip the separator } return name_start; } // All inputs must be non-nullptr, but can start with a nul byte. bool MatchGlobPattern(const char* pattern, const char* cmdline, const char* binname) { if (pattern[0] == '/') { return fnmatch(pattern, cmdline, FNM_NOESCAPE) == 0; } return fnmatch(pattern, binname, FNM_NOESCAPE) == 0; } } // namespace glob_aware } // namespace profiling } // namespace perfetto