All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.