1 /*
2 * Copyright (c) 2016 Catalysts GmbH
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 #include <fstream>
17 #include <sstream>
18
19 #include "common.h"
20 #include "bcc_libbpf_inc.h"
21 #include "vendor/optional.hpp"
22 #include "vendor/tinyformat.hpp"
23
24 namespace ebpf {
25
26 using std::experimental::optional;
27
28 // Get enum value from BTF, since the enum may be anonymous, like:
29 // [608] ENUM '(anon)' size=4 vlen=1
30 // 'TASK_COMM_LEN' val=16
31 // we have to traverse the whole BTF.
32 // Though there is a BTF_KIND_ENUM64, but it is unlikely that it will
33 // be used as array size, we don't handle it here.
get_enum_val_from_btf(const char * name)34 static optional<int32_t> get_enum_val_from_btf(const char *name) {
35 optional<int32_t> val;
36
37 auto btf = btf__load_vmlinux_btf();
38 if (libbpf_get_error(btf))
39 return {};
40
41 for (size_t i = 1; i < btf__type_cnt(btf); i++) {
42 auto t = btf__type_by_id(btf, i);
43 if (btf_kind(t) != BTF_KIND_ENUM)
44 continue;
45
46 auto m = btf_enum(t);
47 for (int j = 0, n = btf_vlen(t); j < n; j++, m++) {
48 if (!strcmp(btf__name_by_offset(btf, m->name_off), name)) {
49 val = m->val;
50 break;
51 }
52 }
53
54 if (val)
55 break;
56 }
57
58 btf__free(btf);
59 return val;
60 }
61
read_cpu_range(std::string path)62 std::vector<int> read_cpu_range(std::string path) {
63 std::ifstream cpus_range_stream { path };
64 std::vector<int> cpus;
65 std::string cpu_range;
66
67 while (std::getline(cpus_range_stream, cpu_range, ',')) {
68 std::size_t rangeop = cpu_range.find('-');
69 if (rangeop == std::string::npos) {
70 cpus.push_back(std::stoi(cpu_range));
71 }
72 else {
73 int start = std::stoi(cpu_range.substr(0, rangeop));
74 int end = std::stoi(cpu_range.substr(rangeop + 1));
75 for (int i = start; i <= end; i++)
76 cpus.push_back(i);
77 }
78 }
79 return cpus;
80 }
81
get_online_cpus()82 std::vector<int> get_online_cpus() {
83 return read_cpu_range("/sys/devices/system/cpu/online");
84 }
85
get_possible_cpus()86 std::vector<int> get_possible_cpus() {
87 return read_cpu_range("/sys/devices/system/cpu/possible");
88 }
89
get_pid_exe(pid_t pid)90 std::string get_pid_exe(pid_t pid) {
91 char exe_path[4096];
92 int res;
93
94 std::string exe_link = tfm::format("/proc/%d/exe", pid);
95 res = readlink(exe_link.c_str(), exe_path, sizeof(exe_path));
96 if (res == -1)
97 return "";
98 if (res >= static_cast<int>(sizeof(exe_path)))
99 res = sizeof(exe_path) - 1;
100 exe_path[res] = '\0';
101 return std::string(exe_path);
102 }
103
104 enum class field_kind_t {
105 common,
106 data_loc,
107 regular,
108 invalid,
109 pad,
110 };
111
_get_field_kind(std::string const & line,std::string & field_type,std::string & field_name,int * last_offset)112 static inline field_kind_t _get_field_kind(std::string const& line,
113 std::string& field_type,
114 std::string& field_name,
115 int *last_offset) {
116 auto field_pos = line.find("field:");
117 if (field_pos == std::string::npos)
118 return field_kind_t::invalid;
119
120 auto field_semi_pos = line.find(';', field_pos);
121 if (field_semi_pos == std::string::npos)
122 return field_kind_t::invalid;
123
124 auto offset_pos = line.find("offset:", field_semi_pos);
125 if (offset_pos == std::string::npos)
126 return field_kind_t::invalid;
127
128 auto semi_pos = line.find(';', offset_pos);
129 if (semi_pos == std::string::npos)
130 return field_kind_t::invalid;
131
132 auto offset_str = line.substr(offset_pos + 7,
133 semi_pos - offset_pos - 7);
134 int offset = std::stoi(offset_str, nullptr);
135
136 auto size_pos = line.find("size:", semi_pos);
137 if (size_pos == std::string::npos)
138 return field_kind_t::invalid;
139
140 semi_pos = line.find(';', size_pos);
141 if (semi_pos == std::string::npos)
142 return field_kind_t::invalid;
143
144 auto size_str = line.substr(size_pos + 5,
145 semi_pos - size_pos - 5);
146 int size = std::stoi(size_str, nullptr);
147
148 if (*last_offset < offset) {
149 *last_offset += 1;
150 return field_kind_t::pad;
151 }
152
153 *last_offset = offset + size;
154
155 auto field = line.substr(field_pos + 6/*"field:"*/,
156 field_semi_pos - field_pos - 6);
157 auto pos = field.find_last_of("\t ");
158 if (pos == std::string::npos)
159 return field_kind_t::invalid;
160
161 field_type = field.substr(0, pos);
162 field_name = field.substr(pos + 1);
163 if (field_type.find("__data_loc") != std::string::npos)
164 return field_kind_t::data_loc;
165 if (field_name.find("common_") == 0)
166 return field_kind_t::common;
167
168 // We may have `char comm[TASK_COMM_LEN];` on kernel v5.18+
169 // Let's replace `TASK_COMM_LEN` with value extracted from BTF
170 if (field_name.find("[") != std::string::npos) {
171 auto pos1 = field_name.find("[");
172 auto pos2 = field_name.find("]");
173 auto dim = field_name.substr(pos1 + 1, pos2 - pos1 - 1);
174 if (!dim.empty() && !isdigit(dim[0])) {
175 auto v = get_enum_val_from_btf(dim.c_str());
176 if (v)
177 dim = std::to_string(*v);
178 field_name.replace(pos1 + 1, pos2 - pos1 - 1, dim, 0);
179 }
180 return field_kind_t::regular;
181 }
182
183 // adjust the field_type based on the size of field
184 // otherwise, incorrect value may be retrieved for big endian
185 // and the field may have incorrect structure offset.
186 if (size == 2) {
187 if (field_type == "char" || field_type == "int8_t")
188 field_type = "s16";
189 if (field_type == "unsigned char" || field_type == "uint8_t")
190 field_type = "u16";
191 } else if (size == 4) {
192 if (field_type == "char" || field_type == "short" ||
193 field_type == "int8_t" || field_type == "int16_t")
194 field_type = "s32";
195 if (field_type == "unsigned char" || field_type == "unsigned short" ||
196 field_type == "uint8_t" || field_type == "uint16_t")
197 field_type = "u32";
198 } else if (size == 8) {
199 if (field_type == "char" || field_type == "short" || field_type == "int" ||
200 field_type == "int8_t" || field_type == "int16_t" ||
201 field_type == "int32_t" || field_type == "pid_t")
202 field_type = "s64";
203 if (field_type == "unsigned char" || field_type == "unsigned short" ||
204 field_type == "unsigned int" || field_type == "uint8_t" ||
205 field_type == "uint16_t" || field_type == "uint32_t" ||
206 field_type == "unsigned" || field_type == "u32" ||
207 field_type == "uid_t" || field_type == "gid_t")
208 field_type = "u64";
209 }
210
211 return field_kind_t::regular;
212 }
213
214 #define DEBUGFS_TRACEFS "/sys/kernel/debug/tracing"
215 #define TRACEFS "/sys/kernel/tracing"
216
tracefs_path()217 std::string tracefs_path() {
218 static bool use_debugfs = access(DEBUGFS_TRACEFS, F_OK) == 0;
219 return use_debugfs ? DEBUGFS_TRACEFS : TRACEFS;
220 }
221
tracepoint_format_file(std::string const & category,std::string const & event)222 std::string tracepoint_format_file(std::string const& category,
223 std::string const& event) {
224 return tracefs_path() + "/events/" + category + "/" + event + "/format";
225 }
226
parse_tracepoint(std::istream & input,std::string const & category,std::string const & event)227 std::string parse_tracepoint(std::istream &input, std::string const& category,
228 std::string const& event) {
229 std::string tp_struct = "struct tracepoint__" + category + "__" + event + " {\n";
230 tp_struct += "\tu64 __do_not_use__;\n";
231 int last_offset = 0, common_offset = 8;
232 for (std::string line; getline(input, line); ) {
233 std::string field_type, field_name;
234 field_kind_t kind;
235
236 do {
237 kind = _get_field_kind(line, field_type, field_name, &last_offset);
238
239 switch (kind) {
240 case field_kind_t::invalid:
241 continue;
242 case field_kind_t::common:
243 for (;common_offset < last_offset; common_offset++)
244 {
245 tp_struct += "\tchar __do_not_use__" + std::to_string(common_offset) + ";\n";
246 }
247 continue;
248 case field_kind_t::data_loc:
249 tp_struct += "\tint data_loc_" + field_name + ";\n";
250 break;
251 case field_kind_t::regular:
252 tp_struct += "\t" + field_type + " " + field_name + ";\n";
253 break;
254 case field_kind_t::pad:
255 tp_struct += "\tchar __pad_" + std::to_string(last_offset - 1) + ";\n";
256 break;
257 }
258 } while (kind == field_kind_t::pad);
259 }
260
261 tp_struct += "};\n";
262 return tp_struct;
263 }
264 } // namespace ebpf
265