* [PATCH 0/2] A few linker-list fixes @ 2026-03-21 13:46 Simon Glass 2026-03-21 13:46 ` [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding Simon Glass 2026-03-21 13:46 ` [PATCH 2/2] dm: Fix linker list alignment for ll_entry_get() Simon Glass 0 siblings, 2 replies; 6+ messages in thread From: Simon Glass @ 2026-03-21 13:46 UTC (permalink / raw) To: u-boot; +Cc: Simon Glass, Marek Vasut, Pavel Herrmann, Tom Rini This series includes two patches to fix aignment problems with linker lists. In certain circumstances these can cause the drivers list to be non-contiguous, causing crashes, hangs, etc. Simon Glass (2): linker_lists: Fix end-marker alignment to prevent padding dm: Fix linker list alignment for ll_entry_get() include/linker_lists.h | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) -- 2.43.0 base-commit: eb00c710508d09b2a3b9aca75dd18280f1304703 branch: llistb ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding 2026-03-21 13:46 [PATCH 0/2] A few linker-list fixes Simon Glass @ 2026-03-21 13:46 ` Simon Glass 2026-03-23 9:56 ` Rasmus Villemoes 2026-03-21 13:46 ` [PATCH 2/2] dm: Fix linker list alignment for ll_entry_get() Simon Glass 1 sibling, 1 reply; 6+ messages in thread From: Simon Glass @ 2026-03-21 13:46 UTC (permalink / raw) To: u-boot; +Cc: Simon Glass, Tom Rini 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 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 <simon.glass@canonical.com> --- include/linker_lists.h | 22 ++++++++++++++++++++-- 1 file 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") /** -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding 2026-03-21 13:46 ` [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding Simon Glass @ 2026-03-23 9:56 ` Rasmus Villemoes 2026-03-23 15:29 ` Tom Rini 2026-03-23 16:17 ` Simon Glass 0 siblings, 2 replies; 6+ messages in thread From: Rasmus Villemoes @ 2026-03-23 9:56 UTC (permalink / raw) To: Simon Glass; +Cc: u-boot, Simon Glass, Tom Rini On Sat, Mar 21 2026, Simon Glass <sjg@chromium.org> wrote: > 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 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. So I'm wondering why that is guaranteed. I mean, the linker is placing these sections one after another in order 2_foo_2_last_foo size sizeof(struct foo), alignment max(4, alignof(struct foo)) 2_foo_3 size 0, alignment 4 (1 with your patch) 2_bar_1 size 0, alignment CONFIG_LINKER_LIST_ALIGN 2_bar_2_first_bar size sizeof(struct bar), alignment max(4, alignof(struct bar)) So clearly the end of last_foo does have 4-byte alignment, yet it is observed that the linker sometimes makes 2_foo_3's address coincide with 2_bar_1's address? What I don't understand is that it seems that the linker could place the zero-size object 2_foo_3 at any 4-byte aligned address between the end of 2_foo_2_last_foo and 2_bar_1. And the same seems to be true when one changes it to have even smaller alignment requirement. So why does an align(1) stop the linker from placing that 0-size section at the same address as 2_bar_1, or even force it (as we need) to put it at the first possible address, i.e. immediately after last_foo? Unless alignment 1 is somehow special-cased to mean "place as early as possible", I can't see how this should provide any better guarantees than what we already have. So I don't oppose the patch at all, but I'd really like to understand how it actually works. Rasmus ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding 2026-03-23 9:56 ` Rasmus Villemoes @ 2026-03-23 15:29 ` Tom Rini 2026-03-23 16:17 ` Simon Glass 1 sibling, 0 replies; 6+ messages in thread From: Tom Rini @ 2026-03-23 15:29 UTC (permalink / raw) To: Rasmus Villemoes, Simon Glass; +Cc: u-boot, Simon Glass [-- Attachment #1: Type: text/plain, Size: 2991 bytes --] On Mon, Mar 23, 2026 at 10:56:14AM +0100, Rasmus Villemoes wrote: > On Sat, Mar 21 2026, Simon Glass <sjg@chromium.org> wrote: > > > 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 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. > > So I'm wondering why that is guaranteed. I mean, the linker is placing > these sections one after another in order > > > 2_foo_2_last_foo size sizeof(struct foo), alignment max(4, alignof(struct foo)) > 2_foo_3 size 0, alignment 4 (1 with your patch) > 2_bar_1 size 0, alignment CONFIG_LINKER_LIST_ALIGN > 2_bar_2_first_bar size sizeof(struct bar), alignment max(4, alignof(struct bar)) > > So clearly the end of last_foo does have 4-byte alignment, yet it is > observed that the linker sometimes makes 2_foo_3's address coincide with > 2_bar_1's address? > > What I don't understand is that it seems that the linker could place the > zero-size object 2_foo_3 at any 4-byte aligned address between the end > of 2_foo_2_last_foo and 2_bar_1. And the same seems to be true when one > changes it to have even smaller alignment requirement. > > So why does an align(1) stop the linker from placing that 0-size section > at the same address as 2_bar_1, or even force it (as we need) to put it > at the first possible address, i.e. immediately after last_foo? Unless > alignment 1 is somehow special-cased to mean "place as early as > possible", I can't see how this should provide any better guarantees > than what we already have. > > So I don't oppose the patch at all, but I'd really like to understand > how it actually works. And as was recently demonstrated in getting device trees to be 8 byte aligned in linker scripts, you need to be making changes in the linker script to ensure output alignment matches what you expect and also it's not always obvious what actually will happen in all cases (when you haven't been dealing with the nuances of linker scripts for days on end). -- Tom [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding 2026-03-23 9:56 ` Rasmus Villemoes 2026-03-23 15:29 ` Tom Rini @ 2026-03-23 16:17 ` Simon Glass 1 sibling, 0 replies; 6+ messages in thread From: Simon Glass @ 2026-03-23 16:17 UTC (permalink / raw) To: Rasmus Villemoes; +Cc: u-boot, Simon Glass, Tom Rini Hi Rasmus, On Mon, 23 Mar 2026 at 03:56, Rasmus Villemoes <ravi@prevas.dk> wrote: > > On Sat, Mar 21 2026, Simon Glass <sjg@chromium.org> wrote: > > > 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 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. > > So I'm wondering why that is guaranteed. I mean, the linker is placing > these sections one after another in order > > > 2_foo_2_last_foo size sizeof(struct foo), alignment max(4, alignof(struct foo)) > 2_foo_3 size 0, alignment 4 (1 with your patch) > 2_bar_1 size 0, alignment CONFIG_LINKER_LIST_ALIGN > 2_bar_2_first_bar size sizeof(struct bar), alignment max(4, alignof(struct bar)) > > So clearly the end of last_foo does have 4-byte alignment, yet it is > observed that the linker sometimes makes 2_foo_3's address coincide with > 2_bar_1's address? > > What I don't understand is that it seems that the linker could place the > zero-size object 2_foo_3 at any 4-byte aligned address between the end > of 2_foo_2_last_foo and 2_bar_1. And the same seems to be true when one > changes it to have even smaller alignment requirement. > > So why does an align(1) stop the linker from placing that 0-size section > at the same address as 2_bar_1, or even force it (as we need) to put it > at the first possible address, i.e. immediately after last_foo? My commit message was a bit confusing - alignment of symbol is not based on what follows an item, just on the item itself (despite appearances to the contrary). My understanding of this is that the linker processes input sections sequentially within the SORT(_u_boot_list*) output section, placing each at the first address that satisfies its alignment. So the location counter advances forward only by the minimum needed. But this isn't specific to alignment 1. I've used __aligned(1) in order to make it clear we don't want any alignment. In all current cases, __aligned(4) would be OK too since the structs we use are always 4-byte-aligned. We just want the end marker to go at the current location-counter, i.e. immediately after the last entry. I suppose another way of saying this is that we want the end marker to be a 'multiple of the struct size' higher than the start marker. With ll_end_decl() using __aligned(CONFIG_LINKER_LIST_ALIGN), the result depends on the struct size and the number of items in the list. On sandbox the value is 32. If the last entry ends at, say, 0x103c (4-byte aligned but not 32-byte aligned), the linker must advance to 0x1040 to place the end marker. So then there is a 4-byte gap, i.e. (end - start) not a multiple of sizeof(struct), and the compiler's magic-number division optimisation fails. > Unless > alignment 1 is somehow special-cased to mean "place as early as > possible", I can't see how this should provide any better guarantees > than what we already have. Yes, we could use 4, see above. I think that would be confusing though. We cannot use CONFIG_LINKER_LIST_ALIGN as above. > > So I don't oppose the patch at all, but I'd really like to understand > how it actually works. BTW I spent quite a lot of time analysing what was actually going on and even wrote a Python script to check the build. There is a blog post about all of this too (search for "Linker Lists" and magic numbers). Regards, Simon ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] dm: Fix linker list alignment for ll_entry_get() 2026-03-21 13:46 [PATCH 0/2] A few linker-list fixes Simon Glass 2026-03-21 13:46 ` [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding Simon Glass @ 2026-03-21 13:46 ` Simon Glass 1 sibling, 0 replies; 6+ messages in thread From: Simon Glass @ 2026-03-21 13:46 UTC (permalink / raw) To: u-boot; +Cc: Simon Glass, Marek Vasut, Pavel Herrmann, Tom Rini From: Simon Glass <simon.glass@canonical.com> The extern declaration in ll_entry_get() lacks the __aligned(4) attribute present in ll_entry_declare(). When the compiler sees an unaligned extern reference to a linker list entry in the same compilation unit as its definition, it may increase the section alignment beyond the expected struct size. This causes gaps in the linker list array, which the alignment checker reports as failures. For example, sandbox_dir is both defined and referenced via DM_DRIVER_GET() in sandboxfs.c. The compiler applies 32-byte alignment to its section instead of the 4-byte alignment from the definition, creating an 8-byte gap before it in the driver list. Add __aligned(4) to the extern declaration in ll_entry_get() to match ll_entry_declare() Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linker_lists.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linker_lists.h b/include/linker_lists.h index 6a018f175ca..470dcfc621a 100644 --- a/include/linker_lists.h +++ b/include/linker_lists.h @@ -284,7 +284,8 @@ */ #define ll_entry_get(_type, _name, _list) \ ({ \ - extern _type _u_boot_list_2_##_list##_2_##_name; \ + extern _type _u_boot_list_2_##_list##_2_##_name \ + __aligned(4); \ _type *_ll_result = \ &_u_boot_list_2_##_list##_2_##_name; \ _ll_result; \ -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-03-23 16:17 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-21 13:46 [PATCH 0/2] A few linker-list fixes Simon Glass 2026-03-21 13:46 ` [PATCH 1/2] linker_lists: Fix end-marker alignment to prevent padding Simon Glass 2026-03-23 9:56 ` Rasmus Villemoes 2026-03-23 15:29 ` Tom Rini 2026-03-23 16:17 ` Simon Glass 2026-03-21 13:46 ` [PATCH 2/2] dm: Fix linker list alignment for ll_entry_get() Simon Glass
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox