| /* |
| * Copyright (c) 2016 Catalysts GmbH |
| * |
| * 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 <fstream> |
| #include <sstream> |
| |
| #include "common.h" |
| #include "bcc_libbpf_inc.h" |
| #include "vendor/optional.hpp" |
| #include "vendor/tinyformat.hpp" |
| |
| namespace ebpf { |
| |
| using std::experimental::optional; |
| |
| // Get enum value from BTF, since the enum may be anonymous, like: |
| // [608] ENUM '(anon)' size=4 vlen=1 |
| // 'TASK_COMM_LEN' val=16 |
| // we have to traverse the whole BTF. |
| // Though there is a BTF_KIND_ENUM64, but it is unlikely that it will |
| // be used as array size, we don't handle it here. |
| static optional<int32_t> get_enum_val_from_btf(const char *name) { |
| optional<int32_t> val; |
| |
| auto btf = btf__load_vmlinux_btf(); |
| if (libbpf_get_error(btf)) |
| return {}; |
| |
| for (size_t i = 1; i < btf__type_cnt(btf); i++) { |
| auto t = btf__type_by_id(btf, i); |
| if (btf_kind(t) != BTF_KIND_ENUM) |
| continue; |
| |
| auto m = btf_enum(t); |
| for (int j = 0, n = btf_vlen(t); j < n; j++, m++) { |
| if (!strcmp(btf__name_by_offset(btf, m->name_off), name)) { |
| val = m->val; |
| break; |
| } |
| } |
| |
| if (val) |
| break; |
| } |
| |
| btf__free(btf); |
| return val; |
| } |
| |
| std::vector<int> read_cpu_range(std::string path) { |
| std::ifstream cpus_range_stream { path }; |
| std::vector<int> cpus; |
| std::string cpu_range; |
| |
| while (std::getline(cpus_range_stream, cpu_range, ',')) { |
| std::size_t rangeop = cpu_range.find('-'); |
| if (rangeop == std::string::npos) { |
| cpus.push_back(std::stoi(cpu_range)); |
| } |
| else { |
| int start = std::stoi(cpu_range.substr(0, rangeop)); |
| int end = std::stoi(cpu_range.substr(rangeop + 1)); |
| for (int i = start; i <= end; i++) |
| cpus.push_back(i); |
| } |
| } |
| return cpus; |
| } |
| |
| std::vector<int> get_online_cpus() { |
| return read_cpu_range("/sys/devices/system/cpu/online"); |
| } |
| |
| std::vector<int> get_possible_cpus() { |
| return read_cpu_range("/sys/devices/system/cpu/possible"); |
| } |
| |
| std::string get_pid_exe(pid_t pid) { |
| char exe_path[4096]; |
| int res; |
| |
| std::string exe_link = tfm::format("/proc/%d/exe", pid); |
| res = readlink(exe_link.c_str(), exe_path, sizeof(exe_path)); |
| if (res == -1) |
| return ""; |
| if (res >= static_cast<int>(sizeof(exe_path))) |
| res = sizeof(exe_path) - 1; |
| exe_path[res] = '\0'; |
| return std::string(exe_path); |
| } |
| |
| enum class field_kind_t { |
| common, |
| data_loc, |
| regular, |
| invalid, |
| pad, |
| }; |
| |
| static inline field_kind_t _get_field_kind(std::string const& line, |
| std::string& field_type, |
| std::string& field_name, |
| int *last_offset) { |
| auto field_pos = line.find("field:"); |
| if (field_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| auto field_semi_pos = line.find(';', field_pos); |
| if (field_semi_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| auto offset_pos = line.find("offset:", field_semi_pos); |
| if (offset_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| auto semi_pos = line.find(';', offset_pos); |
| if (semi_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| auto offset_str = line.substr(offset_pos + 7, |
| semi_pos - offset_pos - 7); |
| int offset = std::stoi(offset_str, nullptr); |
| |
| auto size_pos = line.find("size:", semi_pos); |
| if (size_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| semi_pos = line.find(';', size_pos); |
| if (semi_pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| auto size_str = line.substr(size_pos + 5, |
| semi_pos - size_pos - 5); |
| int size = std::stoi(size_str, nullptr); |
| |
| if (*last_offset < offset) { |
| *last_offset += 1; |
| return field_kind_t::pad; |
| } |
| |
| *last_offset = offset + size; |
| |
| auto field = line.substr(field_pos + 6/*"field:"*/, |
| field_semi_pos - field_pos - 6); |
| auto pos = field.find_last_of("\t "); |
| if (pos == std::string::npos) |
| return field_kind_t::invalid; |
| |
| field_type = field.substr(0, pos); |
| field_name = field.substr(pos + 1); |
| if (field_type.find("__data_loc") != std::string::npos) |
| return field_kind_t::data_loc; |
| if (field_name.find("common_") == 0) |
| return field_kind_t::common; |
| |
| // We may have `char comm[TASK_COMM_LEN];` on kernel v5.18+ |
| // Let's replace `TASK_COMM_LEN` with value extracted from BTF |
| if (field_name.find("[") != std::string::npos) { |
| auto pos1 = field_name.find("["); |
| auto pos2 = field_name.find("]"); |
| auto dim = field_name.substr(pos1 + 1, pos2 - pos1 - 1); |
| if (!dim.empty() && !isdigit(dim[0])) { |
| auto v = get_enum_val_from_btf(dim.c_str()); |
| if (v) |
| dim = std::to_string(*v); |
| field_name.replace(pos1 + 1, pos2 - pos1 - 1, dim, 0); |
| } |
| return field_kind_t::regular; |
| } |
| |
| // adjust the field_type based on the size of field |
| // otherwise, incorrect value may be retrieved for big endian |
| // and the field may have incorrect structure offset. |
| if (size == 2) { |
| if (field_type == "char" || field_type == "int8_t") |
| field_type = "s16"; |
| if (field_type == "unsigned char" || field_type == "uint8_t") |
| field_type = "u16"; |
| } else if (size == 4) { |
| if (field_type == "char" || field_type == "short" || |
| field_type == "int8_t" || field_type == "int16_t") |
| field_type = "s32"; |
| if (field_type == "unsigned char" || field_type == "unsigned short" || |
| field_type == "uint8_t" || field_type == "uint16_t") |
| field_type = "u32"; |
| } else if (size == 8) { |
| if (field_type == "char" || field_type == "short" || field_type == "int" || |
| field_type == "int8_t" || field_type == "int16_t" || |
| field_type == "int32_t" || field_type == "pid_t") |
| field_type = "s64"; |
| if (field_type == "unsigned char" || field_type == "unsigned short" || |
| field_type == "unsigned int" || field_type == "uint8_t" || |
| field_type == "uint16_t" || field_type == "uint32_t" || |
| field_type == "unsigned" || field_type == "u32" || |
| field_type == "uid_t" || field_type == "gid_t") |
| field_type = "u64"; |
| } |
| |
| return field_kind_t::regular; |
| } |
| |
| #define DEBUGFS_TRACEFS "/sys/kernel/debug/tracing" |
| #define TRACEFS "/sys/kernel/tracing" |
| |
| std::string tracefs_path() { |
| static bool use_debugfs = access(DEBUGFS_TRACEFS, F_OK) == 0; |
| return use_debugfs ? DEBUGFS_TRACEFS : TRACEFS; |
| } |
| |
| std::string tracepoint_format_file(std::string const& category, |
| std::string const& event) { |
| return tracefs_path() + "/events/" + category + "/" + event + "/format"; |
| } |
| |
| std::string parse_tracepoint(std::istream &input, std::string const& category, |
| std::string const& event) { |
| std::string tp_struct = "struct tracepoint__" + category + "__" + event + " {\n"; |
| tp_struct += "\tu64 __do_not_use__;\n"; |
| int last_offset = 0, common_offset = 8; |
| for (std::string line; getline(input, line); ) { |
| std::string field_type, field_name; |
| field_kind_t kind; |
| |
| do { |
| kind = _get_field_kind(line, field_type, field_name, &last_offset); |
| |
| switch (kind) { |
| case field_kind_t::invalid: |
| continue; |
| case field_kind_t::common: |
| for (;common_offset < last_offset; common_offset++) |
| { |
| tp_struct += "\tchar __do_not_use__" + std::to_string(common_offset) + ";\n"; |
| } |
| continue; |
| case field_kind_t::data_loc: |
| tp_struct += "\tint data_loc_" + field_name + ";\n"; |
| break; |
| case field_kind_t::regular: |
| tp_struct += "\t" + field_type + " " + field_name + ";\n"; |
| break; |
| case field_kind_t::pad: |
| tp_struct += "\tchar __pad_" + std::to_string(last_offset - 1) + ";\n"; |
| break; |
| } |
| } while (kind == field_kind_t::pad); |
| } |
| |
| tp_struct += "};\n"; |
| return tp_struct; |
| } |
| } // namespace ebpf |