From: Arnaldo Carvalho de Melo <acme@kernel.org>
To: Alan Maguire <alan.maguire@oracle.com>
Cc: Jiri Olsa <jolsa@kernel.org>,
Clark Williams <williams@redhat.com>,
dwarves@vger.kernel.org, bpf@vger.kernel.org,
Andrii Nakryiko <andrii@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Mark Wieelard <mjw@redhat.com>,
Arnaldo Carvalho de Melo <acme@redhat.com>
Subject: [PATCH 05/12] dwarf_loader: Handle DW_FORM_block in attr_numeric for Rust discriminant values
Date: Fri, 31 Jul 2026 16:30:53 -0300 [thread overview]
Message-ID: <20260731193102.110693-6-acme@kernel.org> (raw)
In-Reply-To: <20260731193102.110693-1-acme@kernel.org>
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Rust enums with 128-bit discriminant types (i128/u128) encode their
DW_AT_discr_value as a DW_FORM_block1 containing a 16-byte little-endian
integer, rather than using a standard data form like DW_FORM_data*.
Since attr_numeric did not handle DW_FORM_block*, these discriminant
values were not read and produced 311 warnings on a real Rust binary like
sashiko-cli:
DW_AT_<0x16>=0xa
DW_AT_<0x16>=0xa
...
(0x16 = DW_AT_discr_value, 0xa = DW_FORM_block1)
Add DW_FORM_block1/block2/block4/block to attr_numeric, reading up to 8
bytes from the block into a uint64_t via memcpy and converting from
little-endian with le64toh() so the result is correct on big-endian hosts
as well. This covers all practical discriminant values.
Before (sashiko-cli):
$ pahole --btf_encode sashiko-cli 2>&1 | grep -c 'DW_AT_'
311
After:
$ pahole --btf_encode sashiko-cli 2>&1 | wc -l
0
All warnings from Rust DWARF encoding of sashiko-cli are now resolved.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 51 +++++++++++++--
tests/block_endian.sh | 146 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 190 insertions(+), 7 deletions(-)
create mode 100755 tests/block_endian.sh
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 4979968828c63d8f..934d410b2f983828 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -7,6 +7,7 @@
#include <assert.h>
#include <dirent.h>
#include <dwarf.h>
+#include <endian.h>
#include <elfutils/libdwfl.h>
#include <elfutils/version.h>
#include <errno.h>
@@ -335,7 +336,7 @@ static uint64_t __libdw_get_uleb128(uint64_t acc, uint32_t i,
var = __libdw_get_uleb128 (var, 1, &(addr)); \
} while (0)
-static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name)
+static uint64_t __attr_numeric(Dwarf_Die *die, uint32_t name, bool little_endian)
{
Dwarf_Attribute attr;
uint32_t form;
@@ -371,6 +372,31 @@ static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name)
return value;
}
break;
+ case DW_FORM_block1:
+ case DW_FORM_block2:
+ case DW_FORM_block4:
+ case DW_FORM_block: {
+ /* Block data is in target byte order, convert to host */
+ Dwarf_Block block;
+ if (dwarf_formblock(&attr, &block) == 0 && block.length > 0) {
+ uint64_t value = 0;
+ size_t n = block.length > sizeof(value) ? sizeof(value) : block.length;
+ if (little_endian) {
+ memcpy(&value, block.data, n);
+ return le64toh(value);
+ }
+ /*
+ * BE: the least-significant bytes are at the END of
+ * the block. Skip any high bytes that don't fit in
+ * uint64_t, then accumulate the rest MSB-first.
+ */
+ size_t off = block.length - n;
+ for (size_t i = 0; i < n; i++)
+ value = (value << 8) | (uint8_t)block.data[off + i];
+ return value;
+ }
+ }
+ break;
default:
fprintf(stderr, "DW_AT_<0x%x>=0x%x\n", name, form);
break;
@@ -379,6 +405,16 @@ static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name)
return 0;
}
+/*
+ * Most callers don't use DW_FORM_block, so this wrapper keeps them unchanged.
+ * When the attribute can be a block (e.g. DW_AT_discr_value), use
+ * __attr_numeric() with the CU's endianness instead.
+ */
+static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name)
+{
+ return __attr_numeric(die, name, true);
+}
+
static uint64_t attr_alignment(Dwarf_Die *die, struct conf_load *conf)
{
return conf->ignore_alignment_attr ? 0 : attr_numeric(die, DW_AT_alignment);
@@ -712,7 +748,7 @@ static struct enumerator *enumerator__new(Dwarf_Die *die, struct cu *cu, struct
if (enumerator != NULL) {
tag__init(&enumerator->tag, cu, die);
enumerator->name = attr_string(die, DW_AT_name, conf);
- enumerator->value = attr_numeric(die, DW_AT_const_value);
+ enumerator->value = __attr_numeric(die, DW_AT_const_value, cu->little_endian);
}
return enumerator;
@@ -801,7 +837,7 @@ static struct constant *constant__new(Dwarf_Die *die, struct cu *cu, struct conf
if (constant != NULL) {
tag__init(&constant->tag, cu, die);
constant->name = attr_string(die, DW_AT_name, conf);
- constant->value = attr_numeric(die, DW_AT_const_value);
+ constant->value = __attr_numeric(die, DW_AT_const_value, cu->little_endian);
}
return constant;
@@ -1080,7 +1116,7 @@ static struct class_member *class_member__new(Dwarf_Die *die, struct cu *cu,
if (!cu__is_c(cu)) {
member->accessibility = attr_numeric(die, DW_AT_accessibility);
- member->const_value = attr_numeric(die, DW_AT_const_value);
+ member->const_value = __attr_numeric(die, DW_AT_const_value, cu->little_endian);
member->virtuality = attr_numeric(die, DW_AT_virtuality);
}
member->hole = 0;
@@ -1189,8 +1225,8 @@ static struct template_value_param *template_value_param__new(Dwarf_Die *die, st
if (tvparm != NULL) {
tag__init(&tvparm->tag, cu, die);
tvparm->name = attr_string(die, DW_AT_name, conf);
- tvparm->const_value = attr_numeric(die, DW_AT_const_value);
- tvparm->default_value = attr_numeric(die, DW_AT_default_value);
+ tvparm->const_value = __attr_numeric(die, DW_AT_const_value, cu->little_endian);
+ tvparm->default_value = __attr_numeric(die, DW_AT_default_value, cu->little_endian);
}
return tvparm;
@@ -1246,7 +1282,8 @@ static struct variant *variant__new(Dwarf_Die *die, struct cu *cu, struct conf_l
if (var != NULL) {
tag__init(&var->tag, cu, die);
- var->discr_value = attr_numeric(die, DW_AT_discr_value);
+ /* DW_AT_discr_value uses DW_FORM_block, needs target endianness */
+ var->discr_value = __attr_numeric(die, DW_AT_discr_value, cu->little_endian);
var->name = NULL;
Dwarf_Die child;
diff --git a/tests/block_endian.sh b/tests/block_endian.sh
new file mode 100755
index 0000000000000000..9ede49a9a133e5a1
--- /dev/null
+++ b/tests/block_endian.sh
@@ -0,0 +1,146 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright © 2026 Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+#
+# Test the DW_FORM_block byte order conversion logic used by
+# __attr_numeric() in dwarf_loader.c. Exercises both LE and BE
+# conversion paths with block lengths 1, 2, 4, and 8 to catch
+# endianness mistakes like using be64toh on sub-8-byte blocks.
+
+. "$(dirname "$0")/test_lib.sh"
+
+outdir=$(make_tmpdir)
+trap cleanup EXIT
+
+title_log "DW_FORM_block byte order conversion."
+
+CC=${CC:-gcc}
+if ! command -v ${CC%% *} > /dev/null 2>&1; then
+ info_log "skip: $CC not available"
+ test_skip
+fi
+
+cat > "$outdir/block_endian_test.c" << 'EOF'
+#define _DEFAULT_SOURCE
+#include <endian.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/*
+ * These two functions replicate the exact conversion logic from
+ * __attr_numeric() in dwarf_loader.c for the DW_FORM_block case.
+ * Any change to that code must be mirrored here.
+ */
+static uint64_t block_to_u64_le(const uint8_t *data, size_t len)
+{
+ uint64_t value = 0;
+ size_t n = len > sizeof(value) ? sizeof(value) : len;
+
+ memcpy(&value, data, n);
+ return le64toh(value);
+}
+
+static uint64_t block_to_u64_be(const uint8_t *data, size_t len)
+{
+ uint64_t value = 0;
+ size_t n = len > sizeof(value) ? sizeof(value) : len;
+
+ for (size_t i = 0; i < n; i++)
+ value = (value << 8) | data[i];
+ return value;
+}
+
+struct test_case {
+ const char *name;
+ int is_be; /* 0 = LE target, 1 = BE target */
+ uint64_t expected;
+ size_t len;
+ uint8_t data[8];
+};
+
+static const struct test_case tests[] = {
+ /* LE target, 1 byte */
+ { "LE n=1 val=5", 0, 5, 1, {0x05} },
+ { "LE n=1 val=0", 0, 0, 1, {0x00} },
+ { "LE n=1 val=255", 0, 255, 1, {0xff} },
+
+ /* LE target, 2 bytes */
+ { "LE n=2 val=256", 0, 256, 2, {0x00, 0x01} },
+ { "LE n=2 val=0x0102",0, 0x0102,2, {0x02, 0x01} },
+
+ /* LE target, 4 bytes */
+ { "LE n=4 val=66051", 0, 66051, 4, {0x03, 0x02, 0x01, 0x00} },
+ { "LE n=4 val=1", 0, 1, 4, {0x01, 0x00, 0x00, 0x00} },
+
+ /* LE target, 8 bytes */
+ { "LE n=8", 0, 0x0807060504030201ULL, 8,
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} },
+
+ /* BE target, 1 byte (single byte has no endianness) */
+ { "BE n=1 val=5", 1, 5, 1, {0x05} },
+ { "BE n=1 val=0", 1, 0, 1, {0x00} },
+ { "BE n=1 val=255", 1, 255, 1, {0xff} },
+
+ /* BE target, 2 bytes */
+ { "BE n=2 val=256", 1, 256, 2, {0x01, 0x00} },
+ { "BE n=2 val=0x0102",1, 0x0102,2, {0x01, 0x02} },
+ { "BE n=2 val=1", 1, 1, 2, {0x00, 0x01} },
+
+ /* BE target, 4 bytes */
+ { "BE n=4 val=66051", 1, 66051, 4, {0x00, 0x01, 0x02, 0x03} },
+ { "BE n=4 val=1", 1, 1, 4, {0x00, 0x00, 0x00, 0x01} },
+
+ /* BE target, 8 bytes */
+ { "BE n=8", 1, 0x0102030405060708ULL, 8,
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} },
+};
+
+int main(void)
+{
+ int failures = 0;
+ size_t n = sizeof(tests) / sizeof(tests[0]);
+
+ for (size_t i = 0; i < n; i++) {
+ const struct test_case *t = &tests[i];
+ uint64_t got;
+
+ if (t->is_be)
+ got = block_to_u64_be(t->data, t->len);
+ else
+ got = block_to_u64_le(t->data, t->len);
+
+ if (got != t->expected) {
+ fprintf(stderr, "FAIL: %s: expected 0x%llx, got 0x%llx\n",
+ t->name,
+ (unsigned long long)t->expected,
+ (unsigned long long)got);
+ failures++;
+ }
+ }
+
+ if (failures) {
+ fprintf(stderr, "%d/%zu tests failed\n", failures, n);
+ return 1;
+ }
+ return 0;
+}
+EOF
+
+$CC -std=c11 -Wall -Werror -o "$outdir/block_endian_test" \
+ "$outdir/block_endian_test.c" 2>/dev/null
+if [ $? -ne 0 ]; then
+ error_log "FAIL: failed to compile block_endian_test"
+ test_fail
+fi
+
+output=$("$outdir/block_endian_test" 2>&1)
+if [ $? -ne 0 ]; then
+ error_log "FAIL: block endian conversion test failed:"
+ error_log "$output"
+ test_fail
+fi
+
+info_log " block form LE+BE conversion: ok"
+
+test_pass
--
2.55.0
next prev parent reply other threads:[~2026-07-31 19:31 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 19:30 [PATCHES 00/12] pahole: Support more rust tags and references to dwz alternate debug files Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 01/12] dwarf_loader: Initial support for DW_TAG_variant_part Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 02/12] dwarf_loader: Initial support for DW_TAG_subprogram in DW_TAG_enumeration Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 03/12] dwarf_loader: Populate DW_TAG_variant children in DW_TAG_variant_part Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 04/12] btf_encoder: Encode variant parts as union members in BTF Arnaldo Carvalho de Melo
2026-07-31 19:30 ` Arnaldo Carvalho de Melo [this message]
2026-07-31 19:30 ` [PATCH 06/12] dwarf_loader: Allow forcing the merge of CUs for solving inter CU tag references Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 07/12] dwarf_loader: Support DW_TAG_imported_unit for same-file partial units Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 08/12] dwarf_loader: Fix cus__merging_cu failing to detect DW_FORM_ref_addr Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 09/12] dwarf_loader: Add cu parameter to tag__set_spec() and dwarf_tag__set_attr_type() Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 10/12] dwarf_loader: Support DW_FORM_GNU_ref_alt references to dwz alternate debug files Arnaldo Carvalho de Melo
2026-07-31 19:30 ` [PATCH 11/12] tests: Add inter-CU type reference comparison test Arnaldo Carvalho de Melo
2026-07-31 19:31 ` [PATCH 12/12] scripts: Add vmlinux_comparison.py for DWARF/BTF analysis Arnaldo Carvalho de Melo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260731193102.110693-6-acme@kernel.org \
--to=acme@kernel.org \
--cc=acme@redhat.com \
--cc=alan.maguire@oracle.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=dwarves@vger.kernel.org \
--cc=jolsa@kernel.org \
--cc=mjw@redhat.com \
--cc=williams@redhat.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox