• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alan-Jowett / libbtf / 18665245266

20 Oct 2025 09:20PM UTC coverage: 96.12% (+0.8%) from 95.331%
18665245266

Pull #173

github

web-flow
Merge fe0c58d28 into b6741487e
Pull Request #173: Add support for correctly handling BTF data with cycles

868 of 911 new or added lines in 6 files covered. (95.28%)

11 existing lines in 3 files now uncovered.

2180 of 2268 relevant lines covered (96.12%)

1549.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.85
/libbtf/btf_map.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3

4
#include "btf_map.h"
5

6
#include "btf.h"
7
#include "btf_json.h"
8
#include "btf_parse.h"
9
#include "btf_type_data.h"
10
#include "btf_write.h"
11
#include "cycle_detector.h"
12

13
#include <algorithm>
14
#include <stdexcept>
15

16
namespace libbtf {
17
static uint32_t _value_from_BTF__uint(const btf_type_data &btf_types,
84✔
18
                                      btf_type_id type_id) {
19
  // The __uint macro is defined as follows:
20
  // #define __uint(name, val) int (*name)[val]
21
  // So, we need to get the value of val from the BTF type.
22

23
  // Top level should be a pointer. Dereference it.
24
  type_id = btf_types.dereference_pointer(type_id);
84✔
25

26
  // Value is encoded in the count of elements.
27
  return btf_types.get_kind_type<btf_kind_array>(type_id).count_of_elements;
84✔
28
}
29

30
/**
31
 * @brief Walk the type chain removing typedefs, const, and volatile until any
32
 * other type is found.
33
 *
34
 * @param[in] btf_types The BTF types object.
35
 * @param[in] type_id The type ID to unwrap.
36
 * @return The unwrapped type ID.
37
 */
38
static btf_type_id _unwrap_type(const btf_type_data &btf_types,
46✔
39
                                btf_type_id type_id) {
40
  libbtf::cycle_detector detector;
23✔
41

42
  for (;;) {
43
    // Check for cycles using the detector
44
    if (detector.would_create_cycle(type_id)) {
54✔
45
      // Cycle detected - return current type_id to break the cycle
NEW
46
      return type_id;
×
47
    }
48

49
    // Mark as visited (no need to unmark since we don't recurse)
50
    detector.mark_visited(type_id);
54✔
51

52
    switch (btf_types.get_kind_index(type_id)) {
54✔
53
    case BTF_KIND_TYPEDEF:
8✔
54
      type_id = btf_types.get_kind_type<btf_kind_typedef>(type_id).type;
8✔
55
      break;
8✔
56
    case BTF_KIND_CONST:
×
57
      type_id = btf_types.get_kind_type<btf_kind_const>(type_id).type;
×
58
      break;
×
59
    case BTF_KIND_VOLATILE:
×
60
      type_id = btf_types.get_kind_type<btf_kind_volatile>(type_id).type;
×
61
      break;
×
NEW
UNCOV
62
    case BTF_KIND_RESTRICT:
×
NEW
UNCOV
63
      type_id = btf_types.get_kind_type<btf_kind_restrict>(type_id).type;
×
NEW
UNCOV
64
      break;
×
65
    default:
23✔
66
      return type_id;
23✔
67
    }
68
  }
69
}
23✔
70

71
/**
72
 * @brief Check if the given type is a map. This is done by checking if the type
73
 * is a struct with the following members:
74
 * - type
75
 * - max_entries
76
 *
77
 * @param btf_types The BTF types object.
78
 * @param map_type_id The type id of the type to check.
79
 * @return true This is a map type.
80
 * @return false This is not a map type.
81
 */
82
static bool _is_map_type(const btf_type_data &btf_types,
14✔
83
                         btf_type_id map_type_id) {
84
  if (btf_types.get_kind_index(map_type_id) != BTF_KIND_STRUCT) {
14✔
85
    return false;
1✔
86
  }
87

88
  auto map_struct = btf_types.get_kind_type<btf_kind_struct>(map_type_id);
12✔
89
  bool has_type = false;
6✔
90
  bool has_max_entries = false;
6✔
91

92
  for (const auto &member : map_struct.members) {
56✔
93
    if (member.name == "type") {
22✔
94
      has_type = true;
6✔
95
    } else if (member.name == "max_entries") {
16✔
96
      has_max_entries = true;
6✔
97
    }
98
  }
99

100
  return has_type && has_max_entries;
12✔
101
}
6✔
102

103
/**
104
 * @brief Accept a BTF type ID for a map and return a BTF map definition.
105
 *
106
 * @param[in] btf_types The BTF types object.
107
 * @param[in] name The name of the map or empty string if the name is not
108
 * available.
109
 * @param[in] map_type_id The ID of the struct type for the map.
110
 * @return btf_map_definition
111
 */
112
static btf_map_definition
113
_get_map_definition_from_btf(const btf_type_data &btf_types,
32✔
114
                             const std::string &name, btf_type_id map_type_id) {
115
  btf_type_id type = 0;
16✔
116
  btf_type_id max_entries = 0;
16✔
117
  btf_type_id key = 0;
16✔
118
  btf_type_id key_size = 0;
16✔
119
  btf_type_id value = 0;
16✔
120
  btf_type_id value_size = 0;
16✔
121
  btf_type_id values = 0;
16✔
122

123
  map_type_id = _unwrap_type(btf_types, map_type_id);
32✔
124

125
  auto map_struct = btf_types.get_kind_type<btf_kind_struct>(map_type_id);
32✔
126

127
  for (const auto &member : map_struct.members) {
154✔
128
    if (member.name == "type") {
61✔
129
      type = member.type;
32✔
130
    } else if (member.name == "max_entries") {
45✔
131
      max_entries = member.type;
32✔
132
    } else if (member.name == "key") {
29✔
133
      key = btf_types.dereference_pointer(member.type);
18✔
134
    } else if (member.name == "value") {
20✔
135
      value = btf_types.dereference_pointer(member.type);
6✔
136
    } else if (member.name == "key_size") {
17✔
137
      key_size = member.type;
10✔
138
    } else if (member.name == "value_size") {
12✔
139
      value_size = member.type;
10✔
140
    } else if (member.name == "values") {
7✔
141
      values = member.type;
14✔
142
    }
143
  }
144

145
  if (type == 0) {
32✔
UNCOV
146
    throw std::runtime_error("invalid map type");
×
147
  }
148

149
  btf_map_definition map_definition = {};
32✔
150
  map_definition.name = name;
16✔
151

152
  // Required fields.
153
  map_definition.type_id = map_type_id;
32✔
154
  map_definition.map_type = _value_from_BTF__uint(btf_types, type);
32✔
155

156
  // Optional fields.
157
  if (max_entries) {
32✔
158
    map_definition.max_entries = _value_from_BTF__uint(btf_types, max_entries);
32✔
159
  }
160

161
  if (key) {
32✔
162
    size_t key_size_in_bytes = btf_types.get_size(key);
18✔
163
    if (key_size > UINT32_MAX) {
164
      throw std::runtime_error("key size too large");
165
    }
166
    map_definition.key_size = static_cast<uint32_t>(key_size_in_bytes);
18✔
167
  } else if (key_size) {
14✔
168
    map_definition.key_size = _value_from_BTF__uint(btf_types, key_size);
10✔
169
  }
170

171
  if (value) {
32✔
172
    size_t value_size_in_bytes = btf_types.get_size(value);
6✔
173
    if (value_size > UINT32_MAX) {
174
      throw std::runtime_error("value size too large");
175
    }
176
    map_definition.value_size = static_cast<uint32_t>(value_size_in_bytes);
6✔
177
  } else if (value_size) {
26✔
178
    map_definition.value_size = _value_from_BTF__uint(btf_types, value_size);
10✔
179
  }
180

181
  if (values) {
32✔
182
    // Values is an array of pointers to BTF map definitions.
183
    auto values_array = btf_types.get_kind_type<btf_kind_array>(values);
14✔
184
    auto ptr = btf_types.get_kind_type<btf_kind_ptr>(values_array.element_type);
14✔
185

186
    auto inner_map_type_id = _unwrap_type(btf_types, ptr.type);
14✔
187

188
    if (_is_map_type(btf_types, inner_map_type_id)) {
14✔
189
      // Value is a map.
190
      // Store the inner map type ID and set value size to 4 bytes (the size of
191
      // a map id).
192
      map_definition.inner_map_type_id = static_cast<int>(inner_map_type_id);
12✔
193
      map_definition.value_size = sizeof(uint32_t);
12✔
194
    } else if (btf_types.get_kind_index(inner_map_type_id) ==
2✔
195
               BTF_KIND_FUNCTION_PROTOTYPE) {
196
      // Value is a BPF program.
197
      // Set the value size to 4 bytes (the size of a program id).
198
      map_definition.value_size = sizeof(uint32_t);
2✔
199
    } else {
UNCOV
200
      throw std::runtime_error("invalid type for values");
×
201
    }
202
  }
203

204
  return map_definition;
48✔
205
}
16✔
206

207
std::vector<btf_map_definition>
208
parse_btf_map_section(const btf_type_data &btf_data) {
22✔
209
  std::multimap<btf_type_id, btf_map_definition> map_definitions;
11✔
210

211
  if (btf_data.get_id(".maps") != 0) {
33✔
212
    std::set<btf_type_id> inner_map_type_ids;
10✔
213

214
    // Get the .maps data section.
215
    auto maps_section =
216
        btf_data.get_kind_type<btf_kind_data_section>(btf_data.get_id(".maps"));
30✔
217

218
    // Helper function to add a map definition to the map definitions and add
219
    // the inner map type ID to the list of inner map type IDs if it is present.
220
    auto handle_map_type_id = [&](const std::string &name,
32✔
221
                                  btf_type_id map_type_id) {
222
      auto map_definition =
223
          _get_map_definition_from_btf(btf_data, name, map_type_id);
32✔
224
      map_definitions.insert({map_definition.type_id, map_definition});
32✔
225
      // Add the inner map type ID to the list of inner map type IDs if it is
226
      // present.
227
      if (map_definition.inner_map_type_id != 0) {
32✔
228
        inner_map_type_ids.insert(map_definition.inner_map_type_id);
12✔
229
      }
230
    };
42✔
231

232
    // Add all maps in the .maps data section.
233
    for (const auto &var : maps_section.members) {
48✔
234
      auto map_var = btf_data.get_kind_type<btf_kind_var>(var.type);
28✔
235
      handle_map_type_id(map_var.name, map_var.type);
28✔
236
    }
14✔
237

238
    // Recursively add all inner maps. Assume that there are at most two levels
239
    // of inner maps. This is the current limit imposed by the BPF verifier on
240
    // Linux.
241
    for (size_t inner_map_recursion_level = 0; inner_map_recursion_level < 2;
60✔
242
         inner_map_recursion_level++) {
243
      // Add all maps that are not in the .maps data section.
244
      for (const auto &map_type_id : inner_map_type_ids) {
60✔
245
        // Skip if the map is already present.
246
        if (map_definitions.find(map_type_id) != map_definitions.end()) {
20✔
247
          continue;
16✔
248
        }
249
        handle_map_type_id("", map_type_id);
6✔
250
      }
251
    }
252
  }
20✔
253

254
  // Add an array map for this data section.
255
  auto handle_data_section = [&](btf_type_id data_section_id) {
6✔
256
    auto data_section =
257
        btf_data.get_kind_type<btf_kind_data_section>(data_section_id);
6✔
258
    if (data_section.members.empty()) { // Skip empty data sections.
6✔
259
      return;
260
    }
261
    btf_map_definition map_definition = {};
6✔
262
    map_definition.name = data_section.name;
3✔
263
    map_definition.type_id = data_section_id;
6✔
264
    map_definition.key_size = sizeof(uint32_t);
6✔
265
    map_definition.value_size =
6✔
266
        data_section.members.back().offset + data_section.members.back().size;
6✔
267
    map_definition.max_entries = 1;
6✔
268
    map_definitions.insert({map_definition.type_id, map_definition});
9✔
269
  };
6✔
270

271
  // Create a map for .bss, if it exists.
272
  if (btf_data.get_id(".bss") != 0) {
33✔
273
    handle_data_section(btf_data.get_id(".bss"));
3✔
274
  }
275

276
  // Create a map for .data, if it exists.
277
  if (btf_data.get_id(".data") != 0) {
33✔
278
    handle_data_section(btf_data.get_id(".data"));
3✔
279
  }
280

281
  // Create a map for .rodata, if it exists.
282
  if (btf_data.get_id(".rodata") != 0) {
33✔
283
    handle_data_section(btf_data.get_id(".rodata"));
3✔
284
  }
285

286
  std::vector<btf_map_definition> map_definitions_vector;
11✔
287
  for (const auto &map_definition : map_definitions) {
60✔
288
    map_definitions_vector.push_back(map_definition.second);
38✔
289
  }
290
  return map_definitions_vector;
33✔
291
}
11✔
292

293
btf_type_id btf_uint_from_value(btf_type_data &btf_data, uint32_t value) {
40✔
294
  btf_type_id int_id = btf_data.get_id("int");
40✔
295
  if (int_id == 0) {
40✔
296
    int_id = btf_data.append(btf_kind_int{
16✔
297
        .name = "int", .size_in_bytes = 4, .field_width_in_bits = 32});
298
  }
299

300
  btf_type_id array_size_id = btf_data.get_id("__ARRAY_SIZE_TYPE__");
40✔
301
  if (array_size_id == 0) {
40✔
302
    array_size_id = btf_data.append(btf_kind_int{
16✔
303
        .name = "__ARRAY_SIZE_TYPE__",
304
        .size_in_bytes = 4,
305
        .field_width_in_bits = 32,
306
    });
307
  }
308

309
  btf_type_id array = btf_data.append(btf_kind_array{
60✔
310
      .element_type = int_id,
311
      .index_type = array_size_id,
312
      .count_of_elements = value,
313
  });
314

315
  return btf_data.append(btf_kind_ptr{.type = array});
60✔
316
}
317

318
btf_type_id build_btf_map(btf_type_data &btf_data,
10✔
319
                          const btf_map_definition &map_definition) {
320
  uint32_t offset_in_bits = 0;
5✔
321
  btf_kind_struct map{
10✔
322
      .members =
323
          {
324
              {
325
                  .name = "type",
326
                  .type =
327
                      btf_uint_from_value(btf_data, map_definition.map_type),
10✔
328
              },
329
              {
330
                  .name = "max_entries",
331
                  .type =
332
                      btf_uint_from_value(btf_data, map_definition.max_entries),
10✔
333
              },
334
          },
335
  };
50✔
336
  if (map_definition.key_size) {
10✔
337
    map.members.push_back({
25✔
338
        .name = "key_size",
339
        .type = btf_uint_from_value(btf_data, map_definition.key_size),
10✔
340
    });
341
  }
342
  if (map_definition.value_size) {
10✔
343
    map.members.push_back({
25✔
344
        .name = "value_size",
345
        .type = btf_uint_from_value(btf_data, map_definition.value_size),
10✔
346
    });
347
  }
348

349
  if (map_definition.inner_map_type_id != 0) {
10✔
350
    map.members.push_back(
5✔
351
        {.name = "values",
352
         .type = btf_data.append({btf_kind_array{
4✔
353
             .element_type = btf_data.append(
3✔
354
                 btf_kind_ptr{.type = map_definition.inner_map_type_id}),
3✔
355
             .index_type = btf_data.get_id("__ARRAY_SIZE_TYPE__"),
4✔
356
         }})});
357
  }
358

359
  for (auto &member : map.members) {
52✔
360
    member.offset_from_start_in_bits = offset_in_bits;
42✔
361
    offset_in_bits += static_cast<uint32_t>(btf_data.get_size(member.type) * 8);
42✔
362
  }
363

364
  map.size_in_bytes = offset_in_bits / 8;
10✔
365

366
  return btf_data.append(btf_kind_var{
25✔
367
      .name = map_definition.name,
5✔
368
      .type = btf_data.append(map),
20✔
369
      .linkage = BTF_LINKAGE_STATIC,
370
  });
15✔
371
}
5✔
372

373
void build_btf_map_section(
8✔
374
    const std::vector<btf_map_definition> &map_definitions,
375
    btf_type_data &btf_data) {
376
  btf_kind_data_section maps_section{.name = ".maps"};
8✔
377
  std::map<btf_type_id, btf_type_id> old_id_to_new_id;
4✔
378

379
  for (auto &map_definition : map_definitions) {
18✔
380
    btf_type_id var_id = build_btf_map(btf_data, map_definition);
10✔
381
    btf_type_id map_id = btf_data.get_kind_type<btf_kind_var>(var_id).type;
10✔
382
    maps_section.members.push_back(btf_kind_data_member{
5✔
383
        .type = var_id,
384
        .size = static_cast<uint32_t>(btf_data.get_size(map_id)),
10✔
385
    });
386
    old_id_to_new_id[map_definition.type_id] = map_id;
10✔
387
  }
388

389
  // Update the map inner_map_type_id in the BTF types.
390
  for (auto &map_definition : map_definitions) {
18✔
391
    if (map_definition.inner_map_type_id == 0) {
10✔
392
      continue;
8✔
393
    }
394
    auto new_id = old_id_to_new_id[map_definition.type_id];
2✔
395
    auto ptr_id = btf_data
396
                      .get_kind_type<btf_kind_array>(
2✔
397
                          btf_data.get_kind_type<btf_kind_struct>(new_id)
2✔
398
                              .members.back()
1✔
399
                              .type)
400
                      .element_type;
2✔
401

402
    auto ptr = btf_data.get_kind_type<btf_kind_ptr>(ptr_id);
2✔
403
    ptr.type = old_id_to_new_id[ptr.type];
2✔
404

405
    btf_data.replace(ptr_id, ptr);
3✔
406
  }
407

408
  btf_data.append(maps_section);
12✔
409
  return;
12✔
410
}
8✔
411

412
} // namespace libbtf
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc