raspberrypi / picotool

BSD 3-Clause "New" or "Revised" License
566 stars 100 forks source link

Accept ELFs with an OS/ABI of ELFOSABI_GNU #111

Closed 255 closed 4 weeks ago

255 commented 1 month ago

RP2 ELF files typically have their EI_OSABI field set to ELFOSABI_NONE. However, when Clang uses GNU extensions, it sets EI_OSABI to ELFOSABI_GNU. The binary is still fully compatible with RP2; ELFOSABI_GNU just indicates that GNU extensions are used in the ELF.

For example, Clang uses the SHF_GNU_RETAIN section flag when compiling a C++17 inline variable with a non-default constructor. This results in an ELF with an EI_OSABI of ELFOSABI_GNU. Picotool currently rejects these ELFs, even though the binary is compatible with the device.

will-v-pi commented 1 month ago

Could you provide a sample ELF file which has ELFOSABI_GNU, so that we can check that the rest of the ELF file handling still works with it?

Also, could you target this PR at the develop branch rather than master

255 commented 1 month ago

I created a minimal example to demonstrate the issue. I built it in Pigweed using Bazel and Clang (see http://pwrev.dev/229671).

#include "pico/stdlib.h"

struct HasNonDefaultConstructor {
  HasNonDefaultConstructor() {}
};

// An inline variable with a non-default destruction causes Clang to use the
// SHF_GNU_RETAIN section flag, which changes the OS/ABI field to ELFOSABI_GNU.
// If you remove `inline` on the next line, the ELF will use ELFOSABI_NONE.
inline HasNonDefaultConstructor variable;

int main() {
  gpio_init(PICO_DEFAULT_LED_PIN);
  gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);

  while (true) {
    gpio_put(PICO_DEFAULT_LED_PIN, true);
    sleep_ms(100);
    gpio_put(PICO_DEFAULT_LED_PIN, false);
    sleep_ms(100);
  }
}

I uploaded two versions of the ELF, one as shown above and one with inline removed: minimal_example.zip.

Inspecting the ELFs, you can see that when the variable is inline, Clang sets the GNU_RETAIN flag for .bss:

$ readelf -t minimal_example.elf minimal_example_without_inline.elf | grep -A2 "File:\|\.bss"
File: minimal_example.elf
There are 23 section headers, starting at offset 0x25500:

--
  [12] .bss
       NOBITS          200003b8 0203b4 0002d8 00   0   0  8
       [00200003]: WRITE, ALLOC, GNU_RETAIN
--
File: minimal_example_without_inline.elf
There are 23 section headers, starting at offset 0x25480:

--
  [12] .bss
       NOBITS          200003b8 0203b4 0002d4 00   0   0  8
       [00000003]: WRITE, ALLOC

Because of that, it sets the OS/ABI to GNU (3) instead of NONE (0) (which readelf reports as System V).

$ readelf -h minimal_example.elf minimal_example_without_inline.elf | grep "File:\|ABI:"
File: minimal_example.elf
  OS/ABI:                            UNIX - GNU
File: minimal_example_without_inline.elf
  OS/ABI:                            UNIX - System V

I flashed these binaries to a Pico using openocd and a debug probe, and they both work as expected. Picotool 2.0.0 refuses to flash minimal_example.elf even though it is compatible.

will-v-pi commented 4 weeks ago

Thanks, those elfs look like they'll be fine with the rest of picotool - I'll merge this