diff options
| author | Simon Glass <[email protected]> | 2026-03-21 07:46:23 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2026-04-14 11:03:52 -0600 |
| commit | f9b507748080c2ef52d516345c2431182a83f19f (patch) | |
| tree | 8b39b691cb8b1bdf0af1f7991fd6f06871b9f947 /include | |
| parent | eb00c710508d09b2a3b9aca75dd18280f1304703 (diff) | |
linker_lists: Fix end-marker alignment to prevent padding
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 optimises pointer subtraction (end - start) using
magic-number multiplication for division. This optimisation 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")
Signed-off-by: Simon Glass <[email protected]>
Diffstat (limited to 'include')
| -rw-r--r-- | include/linker_lists.h | 22 |
1 files changed, 20 insertions, 2 deletions
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") /** |
