/* * Copyright (c) 2024, Red Hat, Inc. and/or its affiliates. * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #include "procMapsParser.hpp" #include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" static bool is_lowercase_hex(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); } static size_t max_mapping_line_len() { return 100 + // everything but the file name os::vm_page_size() // the file name (kernel limits /proc/pid/cmdline to one page ; } ProcSmapsParser::ProcSmapsParser(FILE* f) : _f(f), _linelen(max_mapping_line_len()), _line(nullptr) { assert(_f != nullptr, "Invalid file handle given"); _line = NEW_C_HEAP_ARRAY(char, max_mapping_line_len(), mtInternal); _line[0] = '\0'; } ProcSmapsParser::~ProcSmapsParser() { FREE_C_HEAP_ARRAY(char, _line); } bool ProcSmapsParser::read_line() { _line[0] = '\0'; if (::fgets(_line, _linelen, _f) == nullptr) { // On error or EOF, ensure deterministic empty buffer _line[0] = '\0'; return false; } else { return true; } } bool ProcSmapsParser::is_header_line() { // e.g. ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] return is_lowercase_hex(_line[0]); // All non-header lines in /proc/pid/smaps start with upper-case letters. } void ProcSmapsParser::scan_header_line(ProcSmapsInfo& out) { const int items_read = ::sscanf(_line, "%p-%p %20s %*s %*s %*s %1024s", &out.from, &out.to, out.prot, out.filename); assert(items_read >= 2, "Expected header_line"); } void ProcSmapsParser::scan_additional_line(ProcSmapsInfo& out) { #define SCAN(key, var) \ if (::sscanf(_line, key ": %zu kB", &var) == 1) { \ var *= K; \ return; \ } SCAN("KernelPageSize", out.kernelpagesize); SCAN("Rss", out.rss); SCAN("AnonHugePages", out.anonhugepages); SCAN("Private_Hugetlb", out.private_hugetlb); SCAN("Shared_Hugetlb", out.shared_hugetlb); SCAN("Swap", out.swap); #undef SCAN // scan THPeligible into a bool int thpel = 0; if (::sscanf(_line, "THPeligible: %d", &thpel) == 1) { assert(thpel == 1 || thpel == 0, "Unexpected value %d", thpel); out.thpeligible = (thpel == 1); return; } // scan some flags too if (strncmp(_line, "VmFlags:", 8) == 0) { #define SCAN(flag) { out.flag = (::strstr(_line + 8, " " #flag) != nullptr); } SCAN(rd); SCAN(wr); SCAN(ex); SCAN(nr); SCAN(sh); SCAN(hg); SCAN(ht); SCAN(nh); #undef SCAN } } bool ProcSmapsParser::parse_next(ProcSmapsInfo& out) { // Information about a single mapping reaches across several lines. out.reset(); // Read header line, unless we already read it if (_line[0] == '\0') { if (!read_line()) { return false; } } assert(is_header_line(), "Not a header line: \"%s\".", _line); scan_header_line(out); while (true) { bool ok = read_line(); if (!ok || is_header_line()) { break; // EOF or next header } scan_additional_line(out); } return true; // always return true if a mapping was parsed }