image_pdfimage_print

Have you ever seen output like this from your test suite?

Running -858993444 bloblist tests

That’s not a buffer overflow or memory corruption. It’s a wierd interaction between linker alignment, compiler optimisations, and pointer arithmetic. Let me tell you how we tracked it down.

The Mystery

U-Boot uses ‘linker lists’ extensively – a pattern where the linker collects scattered data structures into contiguous arrays. Drivers, commands, and unit tests all use this mechanism. Each list has start (_1) and end (_3) markers, with entries (_2_*) in between.

To count entries, we use pointer subtraction:

#define ll_entry_count(_type, _list) \
    (ll_entry_end(_type, _list) - ll_entry_start(_type, _list))

Simple, right? The compiler divides the byte span by sizeof(struct) to get the count. Except sometimes it returns garbage.

The Clue: 0xCCCCCCCC

That -858993444 value caught my eye. In hex, it’s 0xCCCCCCCC – a suspiciously regular pattern. This isn’t random memory; it’s the result of a calculation.

GCC optimizes division by constants using multiplicative inverses. Instead of expensive division, it multiplies by a “magic number” and shifts. For dividing by 40 (a typical struct size), GCC generates something like:

movl    %eax, %edx
imulq   $-858993459, %rdx    ; magic number for /40
shrq    $34, %rdx

This optimization is mathematically correct – but only when the dividend is an exact multiple of the divisor. When it’s not, you get rubbish.

The Root Cause

U-Boot’s CONFIG_LINKER_LIST_ALIGN (32 bytes on sandbox) aligns each list’s start. But here’s the subtle bug: the end marker was also being aligned:

#define ll_entry_end(_type, _list)                    \
({                                                    \
    static char end[0] __aligned(32)                   \  // <-- Problem!
        __attribute__((used))                         \
        __section(".u_boot_list_"#_list"_3");         \
    (_type *)&end;                                    \
})

When the next list in memory has a higher alignment requirement, the linker inserts padding before our end marker. Our list might have 15 entries of 40 bytes each (600 bytes), but the span from start to end becomes 608 bytes due to padding.

608 / 40 = 15.2

Feed 608 into GCC’s magic-number division for 40, and out comes 0xCCCCCCCC.

The Fix

Change the end marker alignment to 1:

static char end[0] __aligned(1)  // No padding before end marker

Now the end marker sits immediately after the last entry, the span is an exact multiple, and pointer arithmetic works correctly.

Detection

We enhanced our check_linker_lists.py script to catch this:

  1. Gap analysis: Compare gaps between consecutive symbols. Inconsistent gaps mean padding was inserted within the list.
  2. Size comparison: Using nm -S to get symbol sizes, we check if gap > size. If so, padding exists.
  3. Span verification: Check if (end - start) is a multiple of struct size. If not, pointer subtraction will fail.
./scripts/check_linker_lists.py u-boot -v

Lessons Learned

  1. Magic number division is fragile: It only works for exact multiples. Any padding breaks it silently.
  2. Zero-size markers inherit alignment from neighbours: The linker places them at aligned boundaries based on what follows.
  3. Pointer arithmetic assumes contiguous arrays: This assumption is violated when padding sneaks in.
  4. Garbage values often have patterns0xCCCCCCCC isn’t random – it’s a clue pointing to failed arithmetic.

The fix was one line. Finding it took considerably longer, but that’s firmware development for you.

This bug was found while working on U-Boot’s unit test infrastructure. The fix is in the patch “linker_lists: Fix end-marker alignment to prevent padding”.

Author

  • Simon Glass is a primary author of U-Boot, with around 10K commits. He is maintainer of driver model and various other subsystems in U-Boot.

Leave a Reply

Your email address will not be published. Required fields are marked *