* [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs
@ 2026-09-18 10:20 Hui Su
2026-09-18 10:20 ` [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output Hui Su
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Hui Su @ 2026-09-18 10:20 UTC (permalink / raw)
To: qmo
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms, Hui Su
bpftool currently assumes that possible CPU IDs are dense. This is not true
when the possible CPU mask itself is sparse, such as 0,2-3. In that case,
per-CPU map output labels and prog profile event-array keys can refer to the
wrong logical CPUs.
The first patch keeps dense per-CPU buffer slots separate from logical CPU
IDs when printing map values, and propagates errors from the shared output
path to its callers.
The second patch applies the same distinction to prog profile: userspace
keeps a compact CPU count for result buffers, while BPF event-array keys use
the logical CPU ID and the logical ID span.
Changes in v2:
- Add explicit bpftool errors for possible-CPU mask parsing and allocation
failures.
- Rename the helper result variable to reflect that successful returns are
CPU counts, and remove the duplicate CPU-count consistency check.
- Use a dedicated `cpu_cnt` in `map_dump()` instead of overloading `err`.
- Propagate BTF/output errors from `print_key_value()` through both callers.
- Remove the unused BPF-side `num_cpu` rodata and use the userspace
`profile_cpu_cnt` for compact per-CPU buffers.
- Reset all profile CPU state on cleanup paths.
Testing:
- Built the final bpftool tree successfully.
- Booted an ARM64 QEMU guest from a DTS-built virt DTB with possible CPUs
0,2-3.
- Verified plain/BTF and JSON per-CPU map output reports CPUs 0,2,3 and
omits CPU 1.
- Ran `prog profile` with cycles and instructions; the profiler skeleton
reached perf-event setup. QEMU did not provide a usable instructions PMU
event, so no hardware profile counts are claimed.
- Confirmed the final v2 tree is code-identical to the runtime-tested tree;
only commit metadata changed afterward.
Hui Su (2):
bpftool: Fix CPU IDs in per-CPU map output
bpftool: Fix sparse CPU IDs in prog profile
tools/bpf/bpftool/common.c | 40 ++++++++++++
tools/bpf/bpftool/main.h | 1 +
tools/bpf/bpftool/map.c | 76 ++++++++++++++++-------
tools/bpf/bpftool/prog.c | 51 ++++++++++-----
tools/bpf/bpftool/skeleton/profiler.bpf.c | 8 +--
5 files changed, 131 insertions(+), 45 deletions(-)
base-commit: 238650ef6c7c7cca08e032527329424c9fbd70e5
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output
2026-09-18 10:20 [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Hui Su
@ 2026-09-18 10:20 ` Hui Su
2026-09-18 11:38 ` bot+bpf-ci
2026-09-18 10:20 ` [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile Hui Su
2026-09-18 11:26 ` [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Quentin Monnet
2 siblings, 1 reply; 6+ messages in thread
From: Hui Su @ 2026-09-18 10:20 UTC (permalink / raw)
To: qmo
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms, Hui Su
bpftool uses dense per-CPU value-buffer slots when printing per-CPU map
values. It also uses the slot index as the CPU ID, which produces
incorrect labels when the possible CPU mask is sparse, such as 0,2-3.
Parse the possible CPU mask and use the corresponding logical CPU ID in
plain, JSON, and BTF-formatted output. Keep the dense slot index for
accessing the per-CPU value buffer, and propagate CPU-ID lookup and map
output errors through the shared output path to its callers.
Tested:
- Built tools/bpf/bpftool successfully on the host.
- Booted an arm64 QEMU guest with a patched virt device tree reporting
possible=0,2-3, present=0,2-3, and online=0,2-3.
- Compared pre-fix and fixed plain and JSON map output in that guest:
labels changed from CPU 0,1,2 to CPU 0,2,3.
- Loaded a BTF-described per-CPU array and dumped it with the fixed
bpftool in plain BTF and JSON formats; both formats reported CPU 0,2,3
while preserving the three dense per-CPU values.
Fixes: 71bb428fe2c1 ("tools: bpf: add bpftool")
Link: https://lore.kernel.org/bpf/20260813155131.1022745-3-sh_def@163.com/
Signed-off-by: Hui Su <sh_def@163.com>
---
tools/bpf/bpftool/common.c | 40 ++++++++++++++++++++
tools/bpf/bpftool/main.h | 1 +
tools/bpf/bpftool/map.c | 76 ++++++++++++++++++++++++++------------
3 files changed, 93 insertions(+), 24 deletions(-)
diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c
index ef366ccc9650..ad10bfe8a0fc 100644
--- a/tools/bpf/bpftool/common.c
+++ b/tools/bpf/bpftool/common.c
@@ -31,6 +31,7 @@
#include <bpf/bpf.h>
#include <bpf/hashmap.h>
#include <bpf/libbpf.h> /* libbpf_num_possible_cpus */
+#include <bpf/libbpf_internal.h>
#include <bpf/btf.h>
#include <zlib.h>
@@ -655,6 +656,45 @@ unsigned int get_possible_cpus(void)
return cpus;
}
+int get_possible_cpu_ids(int **cpu_ids)
+{
+ const char *possible_cpus_file = "/sys/devices/system/cpu/possible";
+ bool *mask = NULL;
+ int mask_sz, nr_cpus = 0;
+ int *ids = NULL;
+ int i, res;
+
+ *cpu_ids = NULL;
+
+ res = parse_cpu_mask_file(possible_cpus_file, &mask, &mask_sz);
+ if (res) {
+ p_err("failed to parse possible CPU mask: %s", strerror(-res));
+ return res;
+ }
+
+ for (i = 0; i < mask_sz; i++)
+ nr_cpus += mask[i];
+
+ ids = calloc(nr_cpus, sizeof(*ids));
+ if (!ids) {
+ p_err("failed to allocate possible CPU IDs: %s", strerror(ENOMEM));
+ res = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0, nr_cpus = 0; i < mask_sz; i++) {
+ if (mask[i])
+ ids[nr_cpus++] = i;
+ }
+ *cpu_ids = ids;
+ ids = NULL;
+ res = nr_cpus;
+out:
+ free(ids);
+ free(mask);
+ return res;
+}
+
static char *
ifindex_to_name_ns(__u32 ifindex, __u32 ns_dev, __u32 ns_ino, char *buf)
{
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 78b6e0ebb85d..6540b632a3ac 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -216,6 +216,7 @@ void print_hex_data_json(uint8_t *data, size_t len);
unsigned int get_page_size(void);
unsigned int get_possible_cpus(void);
+int get_possible_cpu_ids(int **cpu_ids);
const char *
ifindex_to_arch(__u32 ifindex, __u64 ns_dev, __u64 ns_ino, const char **opt);
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index 684a8fb72414..586c9e076a48 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -70,7 +70,7 @@ static void *alloc_value(struct bpf_map_info *info)
static int do_dump_btf(const struct btf_dumper *d,
struct bpf_map_info *map_info, void *key,
- void *value)
+ void *value, const int *cpu_ids)
{
__u32 value_id;
int ret = 0;
@@ -101,7 +101,7 @@ static int do_dump_btf(const struct btf_dumper *d,
step = round_up(map_info->value_size, 8);
for (i = 0; i < n; i++) {
jsonw_start_object(d->jw);
- jsonw_int_field(d->jw, "cpu", i);
+ jsonw_int_field(d->jw, "cpu", cpu_ids[i]);
jsonw_name(d->jw, "value");
ret = btf_dumper_type(d, value_id, value + i * step);
jsonw_end_object(d->jw);
@@ -130,7 +130,8 @@ static json_writer_t *get_btf_writer(void)
}
static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
- unsigned char *value, struct btf *btf)
+ unsigned char *value, struct btf *btf,
+ const int *cpu_ids)
{
jsonw_start_object(json_wtr);
@@ -150,7 +151,7 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
};
jsonw_name(json_wtr, "formatted");
- do_dump_btf(&d, info, key, value);
+ do_dump_btf(&d, info, key, value, cpu_ids);
}
} else {
unsigned int i, n, step;
@@ -166,7 +167,7 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
for (i = 0; i < n; i++) {
jsonw_start_object(json_wtr);
- jsonw_int_field(json_wtr, "cpu", i);
+ jsonw_int_field(json_wtr, "cpu", cpu_ids[i]);
jsonw_name(json_wtr, "value");
print_hex_data_json(value + i * step,
@@ -183,7 +184,7 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
};
jsonw_name(json_wtr, "formatted");
- do_dump_btf(&d, info, key, value);
+ do_dump_btf(&d, info, key, value, cpu_ids);
}
}
@@ -245,7 +246,7 @@ print_entry_error(struct bpf_map_info *map_info, void *key, int lookup_errno)
}
static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
- unsigned char *value)
+ unsigned char *value, const int *cpu_ids)
{
if (!map_is_per_cpu(info->type)) {
bool single_line, break_names;
@@ -285,8 +286,8 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
}
if (info->value_size) {
for (i = 0; i < n; i++) {
- printf("value (CPU %02u):%c",
- i, info->value_size > 16 ? '\n' : ' ');
+ printf("value (CPU %02d):%c",
+ cpu_ids[i], info->value_size > 16 ? '\n' : ' ');
fprint_hex(stdout, value + i * step,
info->value_size, " ");
printf("\n");
@@ -742,7 +743,7 @@ static int do_show(int argc, char **argv)
static int dump_map_elem(int fd, void *key, void *value,
struct bpf_map_info *map_info, struct btf *btf,
- json_writer_t *btf_wtr)
+ json_writer_t *btf_wtr, const int *cpu_ids)
{
if (bpf_map_lookup_elem(fd, key, value)) {
print_entry_error(map_info, key, errno);
@@ -750,7 +751,7 @@ static int dump_map_elem(int fd, void *key, void *value,
}
if (json_output) {
- print_entry_json(map_info, key, value, btf);
+ print_entry_json(map_info, key, value, btf, cpu_ids);
} else if (btf) {
struct btf_dumper d = {
.btf = btf,
@@ -758,9 +759,9 @@ static int dump_map_elem(int fd, void *key, void *value,
.is_plain_text = true,
};
- do_dump_btf(&d, map_info, key, value);
+ do_dump_btf(&d, map_info, key, value, cpu_ids);
} else {
- print_entry_plain(map_info, key, value);
+ print_entry_plain(map_info, key, value, cpu_ids);
}
return 0;
@@ -833,6 +834,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
void *key, *value, *prev_key;
unsigned int num_elems = 0;
struct btf *btf = NULL;
+ int *cpu_ids = NULL;
int err;
key = malloc(info->key_size);
@@ -845,6 +847,16 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
prev_key = NULL;
+ if (map_is_per_cpu(info->type)) {
+ int cpu_cnt;
+
+ cpu_cnt = get_possible_cpu_ids(&cpu_ids);
+ if (cpu_cnt < 0) {
+ err = cpu_cnt;
+ goto exit_free;
+ }
+ }
+
if (wtr) {
err = get_map_kv_btf(info, &btf);
if (err) {
@@ -876,7 +888,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
err = 0;
break;
}
- if (!dump_map_elem(fd, key, value, info, btf, wtr))
+ if (!dump_map_elem(fd, key, value, info, btf, wtr, cpu_ids))
num_elems++;
prev_key = key;
}
@@ -893,6 +905,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
exit_free:
free(key);
free(value);
+ free(cpu_ids);
free_map_kv_btf(btf);
return err;
@@ -1035,17 +1048,29 @@ static int do_update(int argc, char **argv)
return err;
}
-static void print_key_value(struct bpf_map_info *info, void *key,
- void *value)
+static int print_key_value(struct bpf_map_info *info, void *key,
+ void *value)
{
json_writer_t *btf_wtr;
struct btf *btf;
+ int *cpu_ids = NULL;
+ int err = 0;
- if (get_map_kv_btf(info, &btf))
- return;
+ if (map_is_per_cpu(info->type)) {
+ int cpu_cnt = get_possible_cpu_ids(&cpu_ids);
+
+ if (cpu_cnt < 0) {
+ err = cpu_cnt;
+ goto out;
+ }
+ }
+
+ err = get_map_kv_btf(info, &btf);
+ if (err)
+ goto out;
if (json_output) {
- print_entry_json(info, key, value, btf);
+ print_entry_json(info, key, value, btf, cpu_ids);
} else if (btf) {
/* if here json_wtr wouldn't have been initialised,
* so let's create separate writer for btf
@@ -1055,7 +1080,7 @@ static void print_key_value(struct bpf_map_info *info, void *key,
p_info("failed to create json writer for btf. falling back to plain output");
free_map_kv_btf(btf);
btf = NULL;
- print_entry_plain(info, key, value);
+ print_entry_plain(info, key, value, cpu_ids);
} else {
struct btf_dumper d = {
.btf = btf,
@@ -1063,13 +1088,16 @@ static void print_key_value(struct bpf_map_info *info, void *key,
.is_plain_text = true,
};
- do_dump_btf(&d, info, key, value);
+ do_dump_btf(&d, info, key, value, cpu_ids);
jsonw_destroy(&btf_wtr);
}
} else {
- print_entry_plain(info, key, value);
+ print_entry_plain(info, key, value, cpu_ids);
}
free_map_kv_btf(btf);
+out:
+ free(cpu_ids);
+ return err;
}
static int do_lookup(int argc, char **argv)
@@ -1114,7 +1142,7 @@ static int do_lookup(int argc, char **argv)
}
/* here means bpf_map_lookup_elem() succeeded */
- print_key_value(&info, key, value);
+ err = print_key_value(&info, key, value);
exit_free:
free(key);
@@ -1404,7 +1432,7 @@ static int do_pop_dequeue(int argc, char **argv)
goto exit_free;
}
- print_key_value(&info, key, value);
+ err = print_key_value(&info, key, value);
exit_free:
free(key);
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile
2026-09-18 10:20 [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Hui Su
2026-09-18 10:20 ` [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output Hui Su
@ 2026-09-18 10:20 ` Hui Su
2026-09-18 11:38 ` bot+bpf-ci
2026-09-18 11:26 ` [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Quentin Monnet
2 siblings, 1 reply; 6+ messages in thread
From: Hui Su @ 2026-09-18 10:20 UTC (permalink / raw)
To: qmo
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms, Hui Su
bpftool prog profile currently treats the number of possible CPUs as
both the logical CPU ID range and the stride of the perf event array.
That misses valid logical CPUs when the possible CPU mask is sparse, such
as 0,2-3, and can use incorrect PERF_EVENT_ARRAY keys.
Keep the compact possible CPU count for per-CPU result buffers, while
using the actual logical CPU IDs for perf events and the maximum logical
CPU ID plus one as the metric stride in the PERF_EVENT_ARRAY. Keep the
userspace perf file descriptor index separate from the BPF map key. Do
not advance profile_perf_event_cnt for CPUs that return ENODEV, since it
tracks opened userspace perf file descriptors rather than event-array
slots.
Tested:
- Built tools/bpf/bpftool successfully on the host.
- Booted an arm64 QEMU guest with a patched virt device tree reporting
possible=0,2-3, present=0,2-3, and online=0,2-3.
- Ran both pre-fix and fixed bpftool with cycles and instructions against
a BTF-enabled fentry target. The pre-fix binary faulted in
perf_event_alloc(), while the fixed binary reached perf-event setup and
reported failure to create the instructions event on CPU 0. QEMU did
not provide a usable hardware PMU runtime result, so no profile counts
are claimed.
Fixes: 47c09d6a9f67 ("bpftool: Introduce "prog profile" command")
Link: https://lore.kernel.org/bpf/20260813160858.1042834-3-sh_def@163.com/
Signed-off-by: Hui Su <sh_def@163.com>
---
tools/bpf/bpftool/prog.c | 51 +++++++++++++++--------
tools/bpf/bpftool/skeleton/profiler.bpf.c | 8 ++--
2 files changed, 38 insertions(+), 21 deletions(-)
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index a9f730d407a9..bb969ffce453 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -2148,6 +2148,7 @@ struct profile_metric {
};
static __u64 profile_total_count;
+static int profile_cpu_cnt;
#define MAX_NUM_PROFILE_METRICS 4
@@ -2184,7 +2185,7 @@ static int profile_parse_metrics(int argc, char **argv)
static void profile_read_values(struct profiler_bpf *obj)
{
- __u32 m, cpu, num_cpu = obj->rodata->num_cpu;
+ __u32 m, cpu, num_cpu = profile_cpu_cnt;
int reading_map_fd, count_map_fd;
__u64 counts[num_cpu];
__u32 key = 0;
@@ -2349,6 +2350,8 @@ static int profile_tgt_fd = -1;
static char *profile_tgt_name;
static int *profile_perf_events;
static int profile_perf_event_cnt;
+static int *profile_cpu_ids;
+static int profile_cpu_id_span;
static void profile_close_perf_events(struct profiler_bpf *obj)
{
@@ -2358,10 +2361,11 @@ static void profile_close_perf_events(struct profiler_bpf *obj)
close(profile_perf_events[i]);
free(profile_perf_events);
+ profile_perf_events = NULL;
profile_perf_event_cnt = 0;
}
-static int profile_open_perf_event(int mid, int cpu, int map_fd)
+static int profile_open_perf_event(int mid, int cpu, int map_key, int map_fd)
{
int pmu_fd;
@@ -2371,15 +2375,12 @@ static int profile_open_perf_event(int mid, int cpu, int map_fd)
if (errno == ENODEV) {
p_info("cpu %d may be offline, skip %s profiling.",
cpu, metrics[mid].name);
- profile_perf_event_cnt++;
return 0;
}
return -1;
}
- if (bpf_map_update_elem(map_fd,
- &profile_perf_event_cnt,
- &pmu_fd, BPF_ANY) ||
+ if (bpf_map_update_elem(map_fd, &map_key, &pmu_fd, BPF_ANY) ||
ioctl(pmu_fd, PERF_EVENT_IOC_ENABLE, 0)) {
close(pmu_fd);
return -1;
@@ -2391,11 +2392,11 @@ static int profile_open_perf_event(int mid, int cpu, int map_fd)
static int profile_open_perf_events(struct profiler_bpf *obj)
{
- unsigned int cpu, m;
+ unsigned int cpu, m, metric_idx = 0;
int map_fd;
profile_perf_events = calloc(
- obj->rodata->num_cpu * obj->rodata->num_metric, sizeof(int));
+ profile_cpu_cnt * obj->rodata->num_metric, sizeof(int));
if (!profile_perf_events) {
p_err("failed to allocate memory for perf_event array: %s",
strerror(errno));
@@ -2410,13 +2411,17 @@ static int profile_open_perf_events(struct profiler_bpf *obj)
for (m = 0; m < ARRAY_SIZE(metrics); m++) {
if (!metrics[m].selected)
continue;
- for (cpu = 0; cpu < obj->rodata->num_cpu; cpu++) {
- if (profile_open_perf_event(m, cpu, map_fd)) {
- p_err("failed to create event %s on cpu %u",
- metrics[m].name, cpu);
+ for (cpu = 0; cpu < (unsigned int)profile_cpu_cnt; cpu++) {
+ int cpu_id = profile_cpu_ids[cpu];
+ int map_key = cpu_id + metric_idx * profile_cpu_id_span;
+
+ if (profile_open_perf_event(m, cpu_id, map_key, map_fd)) {
+ p_err("failed to create event %s on cpu %d",
+ metrics[m].name, cpu_id);
return -1;
}
}
+ metric_idx++;
}
return 0;
}
@@ -2430,6 +2435,10 @@ static void profile_print_and_cleanup(void)
close(profile_tgt_fd);
free(profile_tgt_name);
+ free(profile_cpu_ids);
+ profile_cpu_ids = NULL;
+ profile_cpu_cnt = 0;
+ profile_cpu_id_span = 0;
}
static void int_exit(int signo)
@@ -2440,7 +2449,8 @@ static void int_exit(int signo)
static int do_profile(int argc, char **argv)
{
- int num_metric, num_cpu, err = -1;
+ int num_metric, err = -1;
+ int *cpu_ids = NULL;
struct bpf_program *prog;
unsigned long duration;
char *endptr;
@@ -2471,11 +2481,13 @@ static int do_profile(int argc, char **argv)
if (num_metric <= 0)
goto out;
- num_cpu = libbpf_num_possible_cpus();
- if (num_cpu <= 0) {
+ profile_cpu_cnt = get_possible_cpu_ids(&cpu_ids);
+ if (profile_cpu_cnt <= 0) {
p_err("failed to identify number of CPUs");
goto out;
}
+ profile_cpu_ids = cpu_ids;
+ profile_cpu_id_span = cpu_ids[profile_cpu_cnt - 1] + 1;
profile_obj = profiler_bpf__open();
if (!profile_obj) {
@@ -2483,11 +2495,12 @@ static int do_profile(int argc, char **argv)
goto out;
}
- profile_obj->rodata->num_cpu = num_cpu;
profile_obj->rodata->num_metric = num_metric;
+ profile_obj->rodata->cpu_id_span = profile_cpu_id_span;
/* adjust map sizes */
- bpf_map__set_max_entries(profile_obj->maps.events, num_metric * num_cpu);
+ bpf_map__set_max_entries(profile_obj->maps.events,
+ num_metric * profile_cpu_id_span);
bpf_map__set_max_entries(profile_obj->maps.fentry_readings, num_metric);
bpf_map__set_max_entries(profile_obj->maps.accum_readings, num_metric);
bpf_map__set_max_entries(profile_obj->maps.counts, 1);
@@ -2534,6 +2547,10 @@ static int do_profile(int argc, char **argv)
profiler_bpf__destroy(profile_obj);
close(profile_tgt_fd);
free(profile_tgt_name);
+ free(profile_cpu_ids);
+ profile_cpu_ids = NULL;
+ profile_cpu_cnt = 0;
+ profile_cpu_id_span = 0;
return err;
}
diff --git a/tools/bpf/bpftool/skeleton/profiler.bpf.c b/tools/bpf/bpftool/skeleton/profiler.bpf.c
index f48c783cb9f7..2504c3a65bb9 100644
--- a/tools/bpf/bpftool/skeleton/profiler.bpf.c
+++ b/tools/bpf/bpftool/skeleton/profiler.bpf.c
@@ -10,7 +10,7 @@ struct bpf_perf_event_value___local {
__u64 running;
} __attribute__((preserve_access_index));
-/* map of perf event fds, num_cpu * num_metric entries */
+/* map of perf event fds, cpu_id_span * num_metric entries */
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
@@ -38,8 +38,8 @@ struct {
__uint(value_size, sizeof(u64));
} counts SEC(".maps");
-const volatile __u32 num_cpu = 1;
const volatile __u32 num_metric = 1;
+const volatile __u32 cpu_id_span = 1;
#define MAX_NUM_METRICS 4
SEC("fentry/XXX")
@@ -67,7 +67,7 @@ int BPF_PROG(fentry_XXX)
if (err)
return 0;
*(ptrs[i]) = reading;
- key += num_cpu;
+ key += cpu_id_span;
}
return 0;
@@ -107,7 +107,7 @@ int BPF_PROG(fexit_XXX)
/* read all events before updating the maps, to reduce error */
for (i = 0; i < num_metric && i < MAX_NUM_METRICS; i++) {
- err = bpf_perf_event_read_value(&events, cpu + i * num_cpu,
+ err = bpf_perf_event_read_value(&events, cpu + i * cpu_id_span,
(void *)(readings + i),
sizeof(*readings));
if (err)
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs
2026-09-18 10:20 [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Hui Su
2026-09-18 10:20 ` [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output Hui Su
2026-09-18 10:20 ` [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile Hui Su
@ 2026-09-18 11:26 ` Quentin Monnet
2 siblings, 0 replies; 6+ messages in thread
From: Quentin Monnet @ 2026-09-18 11:26 UTC (permalink / raw)
To: Hui Su
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms
2026-09-18 19:20 UTC+0900 ~ Hui Su <sh_def@163.com>
> bpftool currently assumes that possible CPU IDs are dense. This is not true
> when the possible CPU mask itself is sparse, such as 0,2-3. In that case,
> per-CPU map output labels and prog profile event-array keys can refer to the
> wrong logical CPUs.
>
> The first patch keeps dense per-CPU buffer slots separate from logical CPU
> IDs when printing map values, and propagates errors from the shared output
> path to its callers.
>
> The second patch applies the same distinction to prog profile: userspace
> keeps a compact CPU count for result buffers, while BPF event-array keys use
> the logical CPU ID and the logical ID span.
Looks good to me, thank you!
Acked-by: Quentin Monnet <qmo@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile
2026-09-18 10:20 ` [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile Hui Su
@ 2026-09-18 11:38 ` bot+bpf-ci
0 siblings, 0 replies; 6+ messages in thread
From: bot+bpf-ci @ 2026-09-18 11:38 UTC (permalink / raw)
To: sh_def, qmo
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms, sh_def, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2660 bytes --]
> Tested:
> - Built tools/bpf/bpftool successfully on the host.
> - Booted an arm64 QEMU guest with a patched virt device tree reporting
> possible=0,2-3, present=0,2-3, and online=0,2-3.
> - Ran both pre-fix and fixed bpftool with cycles and instructions against
> a BTF-enabled fentry target. The pre-fix binary faulted in
> perf_event_alloc(), while the fixed binary reached perf-event setup and
> reported failure to create the instructions event on CPU 0. QEMU did
> not provide a usable hardware PMU runtime result, so no profile counts
> are claimed.
This isn't a bug, but would it read better to move the Tested: block
below the --- line, or fold it into a shorter sentence, so the changelog
itself stays focused on the sparse-mask problem and the new stride? The
block is the longest section of the message while reporting that the run
did not produce a usable result ("reported failure to create the
instructions event on CPU 0", "QEMU did not provide a usable hardware
PMU runtime result, so no profile counts are claimed").
> diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
> index a9f730d407a92..bb969ffce453d 100644
> --- a/tools/bpf/bpftool/prog.c
> +++ b/tools/bpf/bpftool/prog.c
[ ... ]
> @@ -2440,7 +2449,8 @@ static void int_exit(int signo)
>
> static int do_profile(int argc, char **argv)
> {
> - int num_metric, num_cpu, err = -1;
> + int num_metric, err = -1;
> + int *cpu_ids = NULL;
> struct bpf_program *prog;
> unsigned long duration;
> char *endptr;
[ ... ]
> @@ -2471,11 +2481,13 @@ static int do_profile(int argc, char **argv)
> if (num_metric <= 0)
> goto out;
>
> - num_cpu = libbpf_num_possible_cpus();
> - if (num_cpu <= 0) {
> + profile_cpu_cnt = get_possible_cpu_ids(&cpu_ids);
> + if (profile_cpu_cnt <= 0) {
> p_err("failed to identify number of CPUs");
> goto out;
> }
> + profile_cpu_ids = cpu_ids;
> + profile_cpu_id_span = cpu_ids[profile_cpu_cnt - 1] + 1;
This isn't a bug, but could this pass &profile_cpu_ids straight into
get_possible_cpu_ids() and drop the local plus its initializer, since
the helper already clears the output pointer on entry? The local cpu_ids
exists only to be copied into the profile_cpu_ids static on the next
line, and its = NULL initializer is never observed because
get_possible_cpu_ids() unconditionally does *cpu_ids = NULL as its first
statement.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35336124623
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output
2026-09-18 10:20 ` [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output Hui Su
@ 2026-09-18 11:38 ` bot+bpf-ci
0 siblings, 0 replies; 6+ messages in thread
From: bot+bpf-ci @ 2026-09-18 11:38 UTC (permalink / raw)
To: sh_def, qmo
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
davem, kuba, horms, sh_def, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4318 bytes --]
> bpftool: Fix CPU IDs in per-CPU map output
>
> bpftool uses dense per-CPU value-buffer slots when printing per-CPU map
> values. It also uses the slot index as the CPU ID, which produces
> incorrect labels when the possible CPU mask is sparse, such as 0,2-3.
>
> Parse the possible CPU mask and use the corresponding logical CPU ID in
> plain, JSON, and BTF-formatted output. Keep the dense slot index for
> accessing the per-CPU value buffer, and propagate CPU-ID lookup and map
> output errors through the shared output path to its callers.
>
> Tested:
> - Built tools/bpf/bpftool successfully on the host.
> - Booted an arm64 QEMU guest with a patched virt device tree reporting
> possible=0,2-3, present=0,2-3, and online=0,2-3.
> - Compared pre-fix and fixed plain and JSON map output in that guest:
> labels changed from CPU 0,1,2 to CPU 0,2,3.
> - Loaded a BTF-described per-CPU array and dumped it with the fixed
> bpftool in plain BTF and JSON formats; both formats reported CPU 0,2,3
> while preserving the three dense per-CPU values.
This isn't a bug, but could the Tested: block be trimmed to the reproducer
(possible=0,2-3) or moved below the --- line, keeping the changelog focused
on the problem and the why? The first paragraph already mentions the sparse
mask, and the build-succeeded and format-by-format walkthrough lines restate
what the diff does.
> diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c
> index ef366ccc96501..ad10bfe8a0fc3 100644
> --- a/tools/bpf/bpftool/common.c
> +++ b/tools/bpf/bpftool/common.c
[ ... ]
> @@ -655,6 +656,45 @@ unsigned int get_possible_cpus(void)
> return cpus;
> }
>
> +int get_possible_cpu_ids(int **cpu_ids)
> +{
> + const char *possible_cpus_file = "/sys/devices/system/cpu/possible";
> + bool *mask = NULL;
> + int mask_sz, nr_cpus = 0;
> + int *ids = NULL;
> + int i, res;
> +
> + *cpu_ids = NULL;
> +
> + res = parse_cpu_mask_file(possible_cpus_file, &mask, &mask_sz);
> + if (res) {
> + p_err("failed to parse possible CPU mask: %s", strerror(-res));
> + return res;
> + }
> +
> + for (i = 0; i < mask_sz; i++)
> + nr_cpus += mask[i];
> +
> + ids = calloc(nr_cpus, sizeof(*ids));
> + if (!ids) {
> + p_err("failed to allocate possible CPU IDs: %s", strerror(ENOMEM));
^^^^^^^^^^^^^^^^
This isn't a bug, but would p_err("mem alloc failed") (or strerror(errno))
fit the surrounding style better here, since strerror(ENOMEM) always prints
the same fixed string? This is the only strerror(ENOMEM) in common.c; every
other strerror() call passes errno, and the file's idiom for a failed
allocation is the terse p_err("mem alloc failed").
> + res = -ENOMEM;
> + goto out;
> + }
> +
> + for (i = 0, nr_cpus = 0; i < mask_sz; i++) {
> + if (mask[i])
> + ids[nr_cpus++] = i;
> + }
> + *cpu_ids = ids;
> + ids = NULL;
> + res = nr_cpus;
> +out:
> + free(ids);
> + free(mask);
> + return res;
> +}
> +
[ ... ]
> diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
> index 684a8fb724146..586c9e076a483 100644
> --- a/tools/bpf/bpftool/map.c
> +++ b/tools/bpf/bpftool/map.c
[ ... ]
> @@ -833,6 +834,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
> void *key, *value, *prev_key;
> unsigned int num_elems = 0;
> struct btf *btf = NULL;
> + int *cpu_ids = NULL;
> int err;
>
> key = malloc(info->key_size);
> @@ -845,6 +847,16 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
>
> prev_key = NULL;
>
> + if (map_is_per_cpu(info->type)) {
> + int cpu_cnt;
> +
> + cpu_cnt = get_possible_cpu_ids(&cpu_ids);
> + if (cpu_cnt < 0) {
> + err = cpu_cnt;
> + goto exit_free;
> + }
> + }
> +
This isn't a bug, but since both callers only look at the sign of
get_possible_cpu_ids(), would it read better to either pass cpu_cnt down
next to cpu_ids as the loop bound, or have the helper return plain 0/-errno
so there is one source of truth for the array length? Right now the
consumers derive the loop bound independently from get_possible_cpus().
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35336124623
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-18 11:38 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 10:20 [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Hui Su
2026-09-18 10:20 ` [PATCH bpf v2 1/2] bpftool: Fix CPU IDs in per-CPU map output Hui Su
2026-09-18 11:38 ` bot+bpf-ci
2026-09-18 10:20 ` [PATCH bpf v2 2/2] bpftool: Fix sparse CPU IDs in prog profile Hui Su
2026-09-18 11:38 ` bot+bpf-ci
2026-09-18 11:26 ` [PATCH bpf v2 0/2] bpftool: Fix sparse CPU IDs Quentin Monnet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®