/* * Copyright (C) 2020 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/kallsyms/lazy_kernel_symbolizer.h" #include #include #include #include "perfetto/base/build_config.h" #include "perfetto/base/compiler.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/utils.h" #include "src/kallsyms/kernel_symbol_map.h" namespace perfetto { namespace { const char kKallsymsPath[] = "/proc/kallsyms"; const char kPtrRestrictPath[] = "/proc/sys/kernel/kptr_restrict"; const char kEnvName[] = "ANDROID_FILE__proc_kallsyms"; size_t ParseInheritedAndroidKallsyms(KernelSymbolMap* symbol_map) { const char* fd_str = getenv(kEnvName); auto inherited_fd = base::CStringToInt32(fd_str ? fd_str : ""); // Note: this is also the early exit for non-platform builds. if (!inherited_fd.has_value()) { PERFETTO_DLOG("Failed to parse %s (%s)", kEnvName, fd_str ? fd_str : "N/A"); return 0; } // We've inherited a special fd for kallsyms from init, but we might be // sharing the underlying open file description with a concurrent process. // Even if we use pread() for reading at absolute offsets, the underlying // kernel seqfile is stateful and remembers where the last read stopped. In // the worst case, two concurrent readers will cause a quadratic slowdown // since the kernel reconstructs the seqfile from the beginning whenever two // reads are not consequent. // The chosen approach is to use provisional file locks to coordinate access. // However we cannot use the special fd for locking, since the locks are based // on the underlying open file description (in other words, both sharers will // think they own the same lock). Therefore we open /proc/kallsyms again // purely for locking purposes. base::ScopedFile fd_for_lock = base::OpenFile(kKallsymsPath, O_RDONLY); if (!fd_for_lock) { PERFETTO_PLOG("Failed to open kallsyms for locking."); return 0; } // Blocking lock since the only possible contention is // traced_probes<->traced_perf, which will both lock only for the duration of // the parse. Worst case, the task watchdog will restart the process. // // Lock goes away when |fd_for_lock| gets closed at end of scope. if (flock(*fd_for_lock, LOCK_EX) != 0) { PERFETTO_PLOG("Unexpected error in flock(kallsyms)."); return 0; } return symbol_map->Parse(*inherited_fd); } // This class takes care of temporarily lowering the kptr_restrict sysctl. // Otherwise the symbol addresses in /proc/kallsyms will be zeroed out on most // Linux configurations. // // On Android platform builds, this is solved by inheriting a kallsyms fd from // init, with symbols being visible as that is evaluated at the time of the // initial open(). // // On Linux and standalone builds, we rely on this class in combination with // either: // - the sysctls (kptr_restrict, perf_event_paranoid) or this process' // capabilitied to be sufficient for addresses to be visible. // - this process to be running as root / CAP_SYS_ADMIN, in which case this // class will attempt to temporarily override kptr_restrict ourselves. class ScopedKptrUnrestrict { public: ScopedKptrUnrestrict(); // Lowers kptr_restrict if necessary. ~ScopedKptrUnrestrict(); // Restores the initial kptr_restrict. private: static void WriteKptrRestrict(const std::string&); std::string initial_value_; }; ScopedKptrUnrestrict::ScopedKptrUnrestrict() { if (LazyKernelSymbolizer::CanReadKernelSymbolAddresses()) { // Symbols already visible, don't touch anything. return; } bool read_res = base::ReadFile(kPtrRestrictPath, &initial_value_); if (!read_res) { PERFETTO_PLOG("Failed to read %s", kPtrRestrictPath); return; } // Progressively lower kptr_restrict until we can read kallsyms. for (int value = atoi(initial_value_.c_str()); value > 0; --value) { WriteKptrRestrict(std::to_string(value)); if (LazyKernelSymbolizer::CanReadKernelSymbolAddresses()) return; } } ScopedKptrUnrestrict::~ScopedKptrUnrestrict() { if (initial_value_.empty()) return; WriteKptrRestrict(initial_value_); } void ScopedKptrUnrestrict::WriteKptrRestrict(const std::string& value) { // Note: kptr_restrict requires O_WRONLY. O_RDWR won't work. PERFETTO_DCHECK(!value.empty()); base::ScopedFile fd = base::OpenFile(kPtrRestrictPath, O_WRONLY); auto wsize = write(*fd, value.c_str(), value.size()); if (wsize <= 0) { PERFETTO_PLOG("Failed to set %s to %s", kPtrRestrictPath, value.c_str()); } } } // namespace LazyKernelSymbolizer::LazyKernelSymbolizer() = default; LazyKernelSymbolizer::~LazyKernelSymbolizer() = default; KernelSymbolMap* LazyKernelSymbolizer::GetOrCreateKernelSymbolMap() { PERFETTO_DCHECK_THREAD(thread_checker_); if (symbol_map_) return symbol_map_.get(); symbol_map_ = std::make_unique(); // Android platform builds: we have an fd from init. size_t num_syms = ParseInheritedAndroidKallsyms(symbol_map_.get()); if (num_syms) { return symbol_map_.get(); } // Otherwise, try reading the file directly, temporarily lowering // kptr_restrict if we're running with sufficient privileges. ScopedKptrUnrestrict kptr_unrestrict; auto fd = base::OpenFile(kKallsymsPath, O_RDONLY); symbol_map_->Parse(*fd); return symbol_map_.get(); } void LazyKernelSymbolizer::Destroy() { PERFETTO_DCHECK_THREAD(thread_checker_); symbol_map_.reset(); base::MaybeReleaseAllocatorMemToOS(); // For Scudo, b/170217718. } // static bool LazyKernelSymbolizer::CanReadKernelSymbolAddresses( const char* ksyms_path_for_testing) { auto* path = ksyms_path_for_testing ? ksyms_path_for_testing : kKallsymsPath; base::ScopedFile fd = base::OpenFile(path, O_RDONLY); if (!fd) { PERFETTO_PLOG("open(%s) failed", kKallsymsPath); return false; } // Don't just use fscanf() as that might read the whole file (b/36473442). char buf[4096]; auto rsize_signed = base::Read(*fd, buf, sizeof(buf) - 1); if (rsize_signed <= 0) { PERFETTO_PLOG("read(%s) failed", kKallsymsPath); return false; } size_t rsize = static_cast(rsize_signed); buf[rsize] = '\0'; // Iterate over the first page of kallsyms. If we find any non-zero address // call it success. If all addresses are 0, pessimistically assume // kptr_restrict is still restricted. // We cannot look only at the first line because on some devices // /proc/kallsyms can look like this (note the zeros in the first two addrs): // 0000000000000000 A fixed_percpu_data // 0000000000000000 A __per_cpu_start // 0000000000001000 A cpu_debug_store bool reading_addr = true; bool addr_is_zero = true; for (size_t i = 0; i < rsize; i++) { const char c = buf[i]; if (reading_addr) { const bool is_hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); if (is_hex) { addr_is_zero = addr_is_zero && c == '0'; } else { if (!addr_is_zero) return true; reading_addr = false; // Will consume the rest of the line until \n. } } else if (c == '\n') { reading_addr = true; } // if (!reading_addr) } // for char in buf return false; } } // namespace perfetto