[Concept,08/21] linker_lists: Fix end-marker alignment to prevent padding

Message ID 20251214175449.3799539-9-sjg@u-boot.org
State New
Headers
Series test: Add support for passing arguments to C unit tests |

Commit Message

Simon Glass Dec. 14, 2025, 5:54 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Change the alignment of end markers in ll_entry_end() and ll_end_decl()
from __aligned(4) and __aligned(CONFIG_LINKER_LIST_ALIGN) respectively
to __aligned(1).

The linker places zero-size end markers at aligned boundaries based on
what follows them. When the next list's start marker has a high alignment
requirement (e.g., 32 bytes), padding gets inserted before the end marker.
This causes the byte span (end - start) to not be an exact multiple of
the struct size.

The compiler optimizes pointer subtraction (end - start) using magic-number
multiplication for division. This optimization only produces correct results
when the byte span is an exact multiple of the struct size. With padding,
the result is garbage (e.g., -858993444 instead of 15).

By using __aligned(1), the end marker is placed immediately after the last
entry with no padding, ensuring (end - start) equals exactly n * sizeof
where n is the number of entries. This makes ll_entry_count() and direct
pointer arithmetic work correctly.

Fixes: 0b2fa98aa5e5 ("linker_lists: Fix alignment issue")
Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 include/linker_lists.h | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)
  

Patch

diff --git a/include/linker_lists.h b/include/linker_lists.h
index 0f4a2d686e2..6a018f175ca 100644
--- a/include/linker_lists.h
+++ b/include/linker_lists.h
@@ -145,6 +145,20 @@ 
  * Since this macro defines an array end symbol, its leftmost index
  * must be 2 and its rightmost index must be 3.
  *
+ * The end symbol uses __aligned(1) to ensure it is placed immediately after
+ * the last entry without any padding. This is critical for ll_entry_count()
+ * to work correctly.
+ *
+ * If the end marker had a higher alignment (e.g., 4 or 32 bytes), the linker
+ * might insert padding between the last entry and the end marker to satisfy
+ * alignment requirements of the following section. This would cause pointer
+ * subtraction (end - start) to produce incorrect results because the compiler
+ * optimizes pointer division using magic-number multiplication, which only
+ * works correctly when the byte span is an exact multiple of the struct size.
+ *
+ * With __aligned(1), the end marker is placed at exactly (start + n * sizeof)
+ * where n is the number of entries, ensuring correct pointer arithmetic.
+ *
  * Example:
  *
  * ::
@@ -153,7 +167,7 @@ 
  */
 #define ll_entry_end(_type, _list)					\
 ({									\
-	static char end[0] __aligned(4) __attribute__((unused))		\
+	static char end[0] __aligned(1) __attribute__((unused))		\
 		__section("__u_boot_list_2_"#_list"_3");			\
 	_type * tmp = (_type *)&end;					\
 	asm("":"+r"(tmp));						\
@@ -239,8 +253,12 @@ 
 	static _type _sym[0] __aligned(CONFIG_LINKER_LIST_ALIGN)	\
 		__maybe_unused __section("__u_boot_list_2_" #_list "_1")
 
+/*
+ * ll_end_decl uses __aligned(1) to avoid padding before the end marker.
+ * See the comment for ll_entry_end() for a full explanation.
+ */
 #define ll_end_decl(_sym, _type, _list)					\
-	static _type _sym[0] __aligned(CONFIG_LINKER_LIST_ALIGN)	\
+	static _type _sym[0] __aligned(1)				\
 		__maybe_unused __section("__u_boot_list_2_" #_list "_3")
 
 /**