Search code examples
qemuendiannessarm64

Very confused about the significance of endianness in emulated code


I honestly can't make any sense of this. I'm generating code for aarch64 and it appears that I can run the exact same code with either qemu-aarch64 and qemu-aarch64_be by only changing the endianness of the ELF headers. Otherwise, the executables are byte-for-byte identical. Objdump also correctly disassembles the code in both cases. How could that be? The code generated is (I think) little-endian on disk and in fact it does not seem to work if it's the opposite endianness.

Big-endian (headers and first four instructions):

00000000  7f 45 4c 46 02 02 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  00 02 00 b7 00 00 00 01  00 00 00 00 00 00 10 80  |................|
00000020  00 00 00 00 00 00 00 40  00 00 00 00 00 00 00 00  |.......@........|
00000030  00 00 00 00 00 40 00 38  00 01 00 00 00 00 00 00  |.....@.8........|
00000040  00 00 00 01 00 00 00 05  00 00 00 00 00 00 00 80  |................|
00000050  00 00 00 00 00 00 10 80  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 03 0c  00 00 00 00 00 00 03 0c  |................|
00000070  00 00 00 00 00 00 00 20  00 00 00 00 00 00 00 00  |....... ........|
00000080  ff 4f 00 d1 e0 03 40 39  00 20 00 11 e0 03 00 39  |.O....@9. .....9|

  80:   d1004fff    sub sp, sp, #0x13
  84:   394003e0    ldrb    w0, [sp]
  88:   11002000    add w0, w0, #0x8
  8c:   390003e0    strb    w0, [sp]

Little endian:

00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 b7 00 01 00 00 00  80 10 00 00 00 00 00 00  |................|
00000020  40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  01 00 00 00 00 00 00 00  |....@.8.........|
00000040  01 00 00 00 05 00 00 00  80 00 00 00 00 00 00 00  |................|
00000050  80 10 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  0c 03 00 00 00 00 00 00  0c 03 00 00 00 00 00 00  |................|
00000070  20 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ...............|
00000080  ff 4f 00 d1 e0 03 40 39  00 20 00 11 e0 03 00 39  |.O....@9. .....9|

  80:   d1004fff    sub sp, sp, #0x13
  84:   394003e0    ldrb    w0, [sp]
  88:   11002000    add w0, w0, #0x8
  8c:   390003e0    strb    w0, [sp]

I feel like I'm missing something. What's going on here? The only thing that I can think of is that qemu is doing some magic behind the scenes since how the data is laid out obviously has no effect whatsoever. However, if that's the case, how does it know that the endianness is either correct or incorrect?


Solution

  • From the ARMv8-A Reference Manual:

    B2.6.2 Instruction endianness

    In Armv8-A, A64 instructions have a fixed length of 32 bits and are always little-endian.

    So only memory loads and stores are affected by endianness. I would still expect your binary to break in some manner on data that is initialised at compile time, but it is technically possible to not have any such data.