[PATCH 01/27] linker_lists: Fix alignment issue
Heinrich Schuchardt
xypron.glpk at gmx.de
Mon Nov 30 07:20:49 CET 2020
Am 30. November 2020 02:53:36 MEZ schrieb Simon Glass <sjg at chromium.org>:
>The linker script uses alphabetic sorting to group the different linker
>lists together. Each group has its own struct and potentially its own
>alignment. But when the linker packs the structs together it cannot
>ensure that a linker list starts on the expected alignment boundary.
>
>For example, if the first list has a struct size of 8 and we place 3 of
>them in the image, that means that the next struct will start at offset
>0x18 from the start of the linker_list section. If the next struct has
>a size of 16 then it will start at an 8-byte aligned offset, but not a
>16-byte aligned offset.
>
>With sandbox on x86_64, a reference to a linker list item using
>ll_entry_get() can force alignment of that particular linker_list item,
>if it is in the same file as the linker_list item is declared.
>
>Consider this example, where struct driver is 0x80 bytes:
>
> ll_entry_declare(struct driver, fred, driver)
>
>...
>
> void *p = ll_entry_get(struct driver, fred, driver)
>
>If these two lines of code are in the same file, then the entry is
>forced
>to be aligned at the 'struct driver' alignment, which is 16 bytes. If
>the
>second line of code is in a different file, then no action is taken,
>since
>the compiler cannot update the alignment of the linker_list item.
>
>In the first case, an 8-byte 'fill' region is added:
>
> .u_boot_list_2_driver_2_testbus_drv
> 0x0000000000270018 0x80 test/built-in.o
> 0x0000000000270018
> _u_boot_list_2_driver_2_testbus_drv
> .u_boot_list_2_driver_2_testfdt1_drv
> 0x0000000000270098 0x80 test/built-in.o
> 0x0000000000270098
> _u_boot_list_2_driver_2_testfdt1_drv
> *fill* 0x0000000000270118 0x8
> .u_boot_list_2_driver_2_testfdt_drv
> 0x0000000000270120 0x80 test/built-in.o
> 0x0000000000270120
> _u_boot_list_2_driver_2_testfdt_drv
> .u_boot_list_2_driver_2_testprobe_drv
> 0x00000000002701a0 0x80 test/built-in.o
> 0x00000000002701a0
> _u_boot_list_2_driver_2_testprobe_drv
>
>With this, the linker_list no-longer works since items after
>testfdt1_drv
>are not at the expected address.
>
>Ideally we would have a way to tell gcc not to align structs in this
>way.
>It is not clear how we could do this, and in any case it would require
>us
>to adjust every struct used by the linker_list feature.
>
>One possible fix is to force each separate linker_list to start on the
>largest possible boundary that can be required by the compiler. However
>that does not seem to work on x86_64, which uses 16-byte alignment in
>this
>case but needs 32-byte alignment.
>
>So add a Kconfig option to handle this. Set the default value to 4 so
>as to avoid changing platforms that don't need it.
>
>Update the ll_entry_start() accordingly.
>
>Signed-off-by: Simon Glass <sjg at chromium.org>
>---
>
> arch/Kconfig | 11 +++++++
> doc/api/linker_lists.rst | 62 ++++++++++++++++++++++++++++++++++++++++
> include/linker_lists.h | 3 +-
> 3 files changed, 75 insertions(+), 1 deletion(-)
>
>diff --git a/arch/Kconfig b/arch/Kconfig
>index 3aa99e08fce..aa8664212f1 100644
>--- a/arch/Kconfig
>+++ b/arch/Kconfig
>@@ -7,6 +7,17 @@ config HAVE_ARCH_IOREMAP
> config NEEDS_MANUAL_RELOC
> bool
>
>+config LINKER_LIST_ALIGN
>+ int
>+ default 32 if SANDBOX
What is so special about the sandbox?
Just evaluate if the host is 64 bit and use 8 or 4 accordingly?
>+ default 8 if ARM64 || X86
Shouldn't the default be 8 on all 64 bit platforms? And 4 on all 32 bit platforms?
Best regards
Heinrich
>+ default 4
>+ help
>+ Force the each linker list to be aligned to this boundary. This
>+ is required if ll_entry_get() is used, since otherwise the linker
>+ may add padding into the table, thus breaking it.
>+ See linker_lists.rst for full details.
>+
> choice
> prompt "Architecture select"
> default SANDBOX
>diff --git a/doc/api/linker_lists.rst b/doc/api/linker_lists.rst
>index 72f514e0ac0..7a37db52ba8 100644
>--- a/doc/api/linker_lists.rst
>+++ b/doc/api/linker_lists.rst
>@@ -96,5 +96,67 @@ defined for the whole list and each sub-list:
> %u_boot_list_2_drivers_2_pci_3
> %u_boot_list_2_drivers_3
>
>+Alignment issues
>+----------------
>+
>+The linker script uses alphabetic sorting to group the different
>linker
>+lists together. Each group has its own struct and potentially its own
>+alignment. But when the linker packs the structs together it cannot
>ensure
>+that a linker list starts on the expected alignment boundary.
>+
>+For example, if the first list has a struct size of 8 and we place 3
>of
>+them in the image, that means that the next struct will start at
>offset
>+0x18 from the start of the linker_list section. If the next struct has
>+a size of 16 then it will start at an 8-byte aligned offset, but not a
>+16-byte aligned offset.
>+
>+With sandbox on x86_64, a reference to a linker list item using
>+ll_entry_get() can force alignment of that particular linker_list
>item,
>+if it is in the same file as the linker_list item is declared.
>+
>+Consider this example, where struct driver is 0x80 bytes:
>+
>+::
>+
>+ ll_entry_declare(struct driver, fred, driver)
>+
>+ ...
>+
>+ void *p = ll_entry_get(struct driver, fred, driver)
>+
>+If these two lines of code are in the same file, then the entry is
>forced
>+to be aligned at the 'struct driver' alignment, which is 16 bytes. If
>the
>+second line of code is in a different file, then no action is taken,
>since
>+the compiler cannot update the alignment of the linker_list item.
>+
>+In the first case, an 8-byte 'fill' region is added:
>+
>+::
>+
>+ .u_boot_list_2_driver_2_testbus_drv
>+ 0x0000000000270018 0x80 test/built-in.o
>+ 0x0000000000270018
>_u_boot_list_2_driver_2_testbus_drv
>+ .u_boot_list_2_driver_2_testfdt1_drv
>+ 0x0000000000270098 0x80 test/built-in.o
>+ 0x0000000000270098
>_u_boot_list_2_driver_2_testfdt1_drv
>+ *fill* 0x0000000000270118 0x8
>+ .u_boot_list_2_driver_2_testfdt_drv
>+ 0x0000000000270120 0x80 test/built-in.o
>+ 0x0000000000270120
>_u_boot_list_2_driver_2_testfdt_drv
>+ .u_boot_list_2_driver_2_testprobe_drv
>+ 0x00000000002701a0 0x80 test/built-in.o
>+ 0x00000000002701a0
>_u_boot_list_2_driver_2_testprobe_drv
>+
>+With this, the linker_list no-longer works since items after
>testfdt1_drv
>+are not at the expected address.
>+
>+Ideally we would have a way to tell gcc not to align structs in this
>way.
>+It is not clear how we could do this, and in any case it would require
>us
>+to adjust every struct used by the linker_list feature.
>+
>+The simplest fix seems to be to force each separate linker_list to
>start
>+on the largest possible boundary that can be required by the compiler.
>This
>+is the purpose of CONFIG_LINKER_LIST_ALIGN
>+
> .. kernel-doc:: include/linker_lists.h
> :internal:
>diff --git a/include/linker_lists.h b/include/linker_lists.h
>index d775d041e04..fd98ecd297c 100644
>--- a/include/linker_lists.h
>+++ b/include/linker_lists.h
>@@ -124,7 +124,8 @@
> */
> #define ll_entry_start(_type, _list) \
> ({ \
>- static char start[0] __aligned(4) __attribute__((unused, \
>+ static char start[0] __aligned(CONFIG_LINKER_LIST_ALIGN) \
>+ __attribute__((unused, \
> section(".u_boot_list_2_"#_list"_1"))); \
> (_type *)&start; \
> })
More information about the U-Boot
mailing list