[HN Gopher] Big-Endian Testing with QEMU
___________________________________________________________________
Big-Endian Testing with QEMU
Author : jandeboevrie
Score : 78 points
Date : 2026-04-03 13:28 UTC (9 hours ago)
(HTM) web link (www.hanshq.net)
(TXT) w3m dump (www.hanshq.net)
| pragmaticviber wrote:
| It's all fun and games until you have to figure out if the
| endianness bug is in your code or in QEMU's s390x emulation.
| rurban wrote:
| Haven't found any bug in QEMU's s390x, but lots in endian code.
| AKSF_Ackermann wrote:
| > When programming, it is still important to write code that runs
| correctly on systems with either byte order
|
| What you should do instead is write all your code so it is
| little-endian only, as the only relevant big-endian architecture
| is s390x, and if someone wants to run your code on s390x, they
| can afford a support contract.
| bear8642 wrote:
| > the only relevant big-endian architecture is s390x
|
| The adjacent POWER architecture is also still relevant - but as
| you say, they too can afford a support contract.
| AKSF_Ackermann wrote:
| The adjacent POWER architecture seems to be used in ppc64le
| mode these days.
| classichasclass wrote:
| For Linux, yes. AIX and IBM i still run big.
| namibj wrote:
| The latter can definitely afford a support contract.
| EPWN3D wrote:
| I mostly agree, but network byte ordering is still a thing.
| j16sdiz wrote:
| If you comes to low level network protocol (e.g. writing a TCP
| stack), the "network byte order" is always big-endian.
| skrtskrt wrote:
| Prometheus index format is also a big-endian binary file -
| haven't found any reference to why it was chosen.
| edflsafoiewq wrote:
| That's a serialization format.
| 7jjjjjjj wrote:
| It goes without saying that all binary network protocols
| should document their byte order, and that if you're
| implementing a protocol documented as big endian you should
| use ntohl and friends to ensure correctness.
|
| However if designing a new network protocol, choosing big
| endian is insanity. Use little endian, skip the macros, and
| just add #ifndef LITTLE_ENDIAN #error
|
| Or the like to a header somewhere.
| AnthonyMouse wrote:
| What does it actually cost you to define a macro which is a
| no-op on little endian architectures and then use it at the
| point of serialization/deserialization?
| kccqzy wrote:
| A lot because to the compiler a no-op macro is the same
| as not having the macro in the same place so it won't
| catch cases where you should use the macro but didn't.
| Then you just give yourself a false sense of security
| unless you actually test on big endian.
| AnthonyMouse wrote:
| The article demonstrates how you can run your existing
| test suite on big endian with a few simple commands. Or
| you can just wait until someone actually wants to use it
| there, _they_ run your program or test suite on their
| actual big endian machine and then you get a one-line
| pull request for the place you forgot to use the macro.
|
| Adding other architectures to your build system also
| tends to reveal nasty bugs in general, e.g. you were
| unknowingly triggering UB on all architectures but on the
| one you commonly use it causes silent data corruption
| whereas one with a different memory layout results in a
| much more conspicuous segfault.
| whizzter wrote:
| And honestly at this point it's mostly a historical artifact,
| if we write that kind of stuff then sure we need to care but
| to produce modern stuff is a honestly massive waste of time
| at this point.
|
| FWIW I doing hobby-stuff for Amiga's (68k big-endian) but
| that's just that, hobby stuff.
| nyrikki wrote:
| The linked to blog post in the OP explains this better IMHO
| [0]: If the data stream encodes values with
| byte order B, then the algorithm to decode the value on
| computer with byte order C should be about B, not about the
| relationship between B and C.
|
| One cannot just ignore the big/little data interchange problem
| MacOS[1], Java, TCP/IP, Jpeg etc...
|
| The point (for me) is not that your code runs on a s390, it is
| that you abstract your personal local implementation details
| from the data interchange formats. And unfortunately almost all
| of the processors are little, and many of the popular and
| unavoidable externalization are big...
|
| [0] https://commandcenter.blogspot.com/2012/04/byte-order-
| fallac... [1] https://github.com/apple/darwin-
| xnu/blob/main/EXTERNAL_HEADE...
| whizzter wrote:
| MacOS "was" big-endian due to 68k and later PPC cpu's (the
| PPC Mac's could've been little but Apple picked big for
| convenience and porting).
|
| Their x86 changeover moved the CPU's to little-endian and
| Aarch64 continues solidifies that tradition.
|
| Same with Java, there's probably a strong influence from
| SPARC's and with PPC, 68k and SPARC being relevant back in
| the 90s it wasn't a bold choice.
|
| But all of this is more or less legacy at this point, I have
| little reason to believe that the types of code I write will
| ever end up on a s390 or any other big-endian platform unless
| something truly revolutionizes the computing landscape since
| x86, aarch64, risc-v and so on run little now.
| adrian_b wrote:
| To cope with data interchange formats, you need a set of big
| endian data types, e.g. for each kind of signed or unsigned
| integer with a size of 16 bits or bigger you must have a big
| endian variant, e.g. identified with a "_be" suffix.
|
| Most CPUs (including x86-64) have variants of the load and
| store instructions that reverse the byte order (e.g. MOVBE in
| x86-64). The remaining CPUs have byte reversal instructions
| for registers, so a reversed byte order load or store can be
| simulated by a sequence of 2 instructions.
|
| So the little-endian types and the big-endian data types must
| be handled identically by a compiler, except that the load
| and store instructions use different encodings.
|
| The structures used in a data-exchange format must be
| declared with the correct types and that should take care of
| everything.
|
| Any decent programming language must provide means for the
| user to define such data types, when they are not provided by
| the base language.
|
| The traditional UNIX conversion functions are the wrong way
| to handle endianness differences. An optimizing compiler must
| be able to recognize them as special cases in order to be
| able to optimize them away from the machine code.
|
| A program that is written using only data types with known
| endianness can be compiled for either little-endian targets
| or big-endian targets and it will work identically.
|
| All the problems that have ever existed in handling
| endianness have been caused by programming languages where
| the endianness of the base data types was left undefined, for
| fear that recompiling a program for a target of different
| endianness could result in a slower program.
|
| This fear is obsolete today.
| addaon wrote:
| There's still at least one relevant big-endian-only ARM chip
| out there, the TI Hercules. While in the past five or ten years
| we've gone from having very few options for lockstep
| microcontrollers (with the Hercules being a very compelling
| option) to being spoiled for choice, the Hercules is still a
| good fit for some applications, and is a pretty solid chip.
| GandalfHN wrote:
| Outsourcing endianness pain to your customers is an easy way to
| teach them about segfaults and silent data corruption. s390x is
| niche, endian bugs are not.
|
| Network protocols and file formats still need a defined byte
| order, and the first time your code talks to hardware or reads
| old data, little-endian assumptions leak all over the place.
| Ignoring portability buys you a pile of vendor-specific hacks
| later, because your team will meet those 'irrelevant' platforms
| in appliances, embedded boxes, or somebody else's DB import
| path long before a sales rep waves a support contract at you.
| AKSF_Ackermann wrote:
| Not sure why you consider that to be an issue, if you need to
| interact with a format that specifies values to be BE, just
| always byte-swap. And every appliance/embedded box i had to
| interact with ran either x86 or some flavour of 32-bit arm
| (in LE mode, of course).
| 7jjjjjjj wrote:
| Assuming an 8-bit byte used to be a "vendor specific hack."
| Assuming twos complement integers used to be a "vendor
| specific hack." When all the 36-bit machines died, and all
| the one's complement machines died, we got over it.
|
| That's where big endian is now. All the BE architectures are
| dying or dead. No big endian system will ever be popular
| again. It's time for big endian to be consigned to the
| dustbin of history.
| cmrdporcupine wrote:
| > No big endian system will ever be popular again
|
| _Cries in 68k nostalgia_
| zephen wrote:
| > It's time for big endian to be consigned to the dustbin
| of history.
|
| And, _especially_ what _most people_ call big-endian, which
| is a bastardized mixed-endian mess of _most_ significant
| _byte_ is zero, while _least_ significant _bit_ is likewise
| zero.
| namibj wrote:
| JS numbers behave much more like C's definition of signed
| overflow being UB as it's signed numbers are effectively
| like 51-ish bit with a SEPARATE sign bit and non-
| assiciative behavior when overflow happens.
| adrian_b wrote:
| Endianness problems should have been solved by compilers, not
| by programmers.
|
| Most existing CPUs, have instructions to load and store
| memory data of various sizes into registers, while reversing
| the byte order.
|
| So programs that work with big-endian data typically differ
| from those working with little-endian data just by replacing
| the load and store instructions.
|
| Therefore you should have types like int16, int32, int64,
| int16_be, int32_be, int64_be, for little-endian integers and
| big-endian integers and the compiler should generate the
| appropriate code.
|
| At least in the languages with user-defined data types and
| overloadable operators and functions, like C++, you can
| define these yourself, when the language does not provide
| them, instead of using ugly workarounds like htonl and the
| like, which can be very inefficient if the compiler is not
| clever enough to optimize them away.
| jcalvinowens wrote:
| Don't _ignore_ endianness. But making little endian the default
| is the right thing to do, it is so much more ubiquitous in the
| modern world.
|
| The vast majority of modern network protocols use little endian
| byte ordering. Most Linux filesystems use little endian for
| their on-disk binary representations.
|
| There is absolutely no good reason for networking protocols to
| be defined to use big endian. It's an antiquated arbitrary
| idea: just do what makes sense.
|
| Use these functions to avoid ifdef noise:
| https://man7.org/linux/man-pages/man3/endian.3.html
| Veserv wrote:
| You should actually not use format-swapping operations.
|
| You should actually use format-swapping loads/stores (i.e
| deserialization/serialization).
|
| This is because your computer can not compute on values of
| non-native endianness. As such, the value is logically
| converted back and forth on every operation. Of course, a
| competent optimizer can elide these conversions, but such
| actions fundamentally lack machine sympathy.
|
| The better model is viewing the endianness as a serialization
| format and converting at the boundaries of your compute
| engine. This ensures you only need to care about endianness
| when serializing and deserializing wire formats and that you
| have no accidental mixing of formats in your internals;
| everything has been parsed to native before any computation
| occurs.
|
| Essentially, non-native endianness should only exist in
| memory and preferably only memory filled in by the outside
| world before being parsed.
| drob518 wrote:
| What do you mean by "networking protocols," exactly? Most
| packet level Internet protocols (TCP, UDP, etc.) are big
| endian. Ethernet is big endian at the octet level and little
| endian on the wire at the bit level. Network order is big
| endian because it has to be something and it's easier to draw
| pictures as a matrix of bytes that are transmitted from left
| to right and top to bottom. There is no right answer to
| endianness. It's like which side of the road cars should
| drive on. You just need to pick one and stick with it. Mostly
| people bitch about endianness when their processor is the
| opposite of whatever someone else picked. But processors are
| all over the map. IBM mainframes are big endian. Motorola 68k
| is big. HP PA-RISC is big. IBM Power started big and then
| went bi. MIPS is bi. RISC-V is little. ARM is bi but
| dominantly little (AArch64). And of course x86 is little. So,
| take your pick. That said, little endianness is the right
| answer as is driving on the right side of the road.
| hmry wrote:
| > RISC-V is little
|
| These days it's bi, actually :) Although I don't see any
| CPU designer actually implementing that feature, except
| maybe MIPS (who have stopped working on their own ISA, and
| now want all their locked-in customers to switch to RISC-V
| without worrying about endianness bugs)
| drob518 wrote:
| Well, sort of. Instruction fetch is always little-endian
| but data load/store can be flipped into big. But IIRC the
| standard profiles specify little, so it's pretty much
| always going to be little. But yea, technically speaking
| data load/store could be big. Maybe that's important for
| some embedded environments.
| hmry wrote:
| > Well, sort of. Instruction fetch is always little-
| endian but data load/store can be flipped into big
|
| ARM works the same way. And SPARC is the opposite,
| instructions are always big-endian, but data can be
| switched to little-endian.
| sllabres wrote:
| Not only the System/390. Its also IBM i, AIX, and for many
| protocols the network byte order. AFAIK the binary data in JPG
| (1) and Java Class [2] files a re big endian. And if you write
| down a hexadecimal number as 0x12345678 you are writing big-
| endian.
|
| (1) for JPG for embedded TIFF metadata which can have both.
|
| [2]
| https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.ht...
| hmry wrote:
| The endianness of file formats and handwriting is irrelevant
| when it comes to deciding whether your code should support
| running on big-endian CPUs.
|
| The only question that matters: Do your customers / users
| want to run it on big-endian hardware? And for 99% of
| programmers, the answer is no, because their customers have
| never knowingly been in the same room as a big-endian CPU.
| cbmuser wrote:
| > What you should do instead is write all your code so it is
| little-endian only, as the only relevant big-endian
| architecture is s390x, and if someone wants to run your code on
| s390x, they can afford a support contract.
|
| Or you can just be a nice person and make your code endian-
| agnostic. ;-)
| socalgal2 wrote:
| I'm with you this. I lived through the big endian / little
| endian hell in the 80/90s. Little endian won. Anyone making a
| big endian architechture at this point would be shooting
| themselves in the foot because off all the incompatibilities.
| Don't make things more complicated.
|
| In fact, I'd be surprised if you made a big endian arch and
| then ran a browser on it if some large number of websites would
| fail because they used typedarrays and aren't endian aware.
|
| The solution is not to ask every programmer in the universe to
| write endian aware code. The solution is to standardize on
| little endian
| classichasclass wrote:
| We already know that's the case. I had to add little endian
| typed array emulation to TenFourFox.
| userbinator wrote:
| Linus agrees: https://www.phoronix.com/news/Torvalds-No-RISC-V-
| BE
| electroly wrote:
| > When programming, it is still important to write code that runs
| correctly on systems with either byte order
|
| I contend it's almost never important and almost nobody writing
| user software should bother with this. Certainly, people who
| didn't already know they needed big-endian should not start
| caring now because they read an article online. There are
| countless rare machines that your code doesn't run on--what's so
| special about big endian? The world is little endian now. Big
| endian chips aren't coming back. You are spending your own time
| on an effort that will never pay off. If big endian is really
| needed, IBM will pay you to write the s390x port and they will
| provide the machine.
| Retr0id wrote:
| > There are countless rare machines that your code doesn't run
| on--what's so special about big endian?
|
| One difference is that when your endian-oblivious code runs on
| a BE system, it can be _subtly_ wrong in a way that 's hard to
| diagnose, which is a whole lot worse than not working at all.
| electroly wrote:
| That sounds like a problem to deal with as part of your
| _paid_ IBM s390x porting contract. I guess my point is: why
| deal with this before IBM is paying you? No other big endian
| platform matters, and s390x users are 100% large commercial
| customers. If IBM or one of their customers isn 't paying
| you, there's nobody else who would need it. If IBM _is_
| paying you, you can test on a real z /VM that they provide. I
| see big endian as entirely their burden now; nobody else
| needs it. If they want it, they can pay for the work.
| Retr0id wrote:
| I value correct code for purely selfish reasons. The most
| likely person to try to run my code on a BE system is me.
| eesmith wrote:
| There are a lot of odd (by modern standards) machines out
| there.
|
| You're also the most likely person to try to run your
| code on an 18 bit machine.
| fc417fc802 wrote:
| It might sound outrageous but I guard against this sort
| of thing. When I write utility code in C++ I generally
| include various static asserts about basic platform
| assumptions.
| peyton wrote:
| This is much-appreciated. I'm hardly a Richard Stallman,
| but finding little incompatibilities after-the-fact is
| pretty irritating.
| eesmith wrote:
| Take a look at
| https://www.kermitproject.org/ckupdates.html . These
| quotes come from the last few years:
|
| > [fixes] specific to VMS (a.k.a. OpenVMS),
|
| > For conformity with DECSYSTEM-20 Kermit ...
|
| > running on a real Sun3, compiled with a non-ANSII
| compiler (Sun cc 1.22)
|
| > this is fatal in HP-UX 10 with the bundled compiler
|
| > OpenWatcom 1.9 compiler
|
| > OS/2 builds
|
| > making sure that all functions are declared in both
| ANSI format and K&R format (so C-Kermit can built on both
| new and old computers)
|
| Oooooh! A clang complaint: 'Clang also complains about
| perfectly legal compound IF statements and/or complex IF
| conditions, and wants to have parens and/or brackets
| galore added for clarity. These statements were written
| by programmers who understood the rules of precedence of
| arithmetic and logical operators, and the code has been
| working correctly for decades.'
| classichasclass wrote:
| So do I. I don't find that outrageous at all. Anyone
| trying to do the port to something unusual would
| appreciate the warning.
|
| Granted, I still work on a fair number of big endian
| systems even though my daily drivers (ppc64le, Apple
| silicon) are little.
| eesmith wrote:
| There's platform and there's platform. I assume a POSIX
| platform, so I don't need to check for CHAR_BIT. My code
| won't work on some DSP with 64-bit chars, and I don't
| care enough to write that check.
|
| Many of the tests I did back in the 1990s seem pointless
| now. Do you have checks for non-IEEE 754 math?
| shakna wrote:
| Well, last year clang did not define __STDC_IEC_559__, so
| assuming IEEE-754 math with most C compilers is a bad
| idea.
| Retr0id wrote:
| Also, endian-correct code is usually semantically
| clearer. For example, if you're reading network-ordered
| bytes into an int, an unconditional endian swap (which
| will produce correct results on LE systems but not BE) is
| less clear than invoking a "network bytes to u32" helper.
| namibj wrote:
| u32::from_be_bytes
|
| u32::from_le_bytes
|
| u32::from_ne_bytes the n stands for native
| edflsafoiewq wrote:
| Static-assert the machine is little endian.
| Retr0id wrote:
| Someone's LLM will comment out that line the moment it
| causes a build failure
| edflsafoiewq wrote:
| Oh brave new world, that has such arguments in it!
| thrtythreeforty wrote:
| Then they get to keep both pieces!
| CJefferson wrote:
| You are correct, honestly, I couldn't disagree more with th
| article. At this point I can't imagine why it's important.
|
| It's also increasingly hard to test. Particularly when you have
| large expensive testsuites which run incredibly slowly on this
| simulated machines.
| eisbaw wrote:
| I did that many years back, but with MIPS and MIPSel:
| https://youtu.be/BGzJp1ybpHo?si=eY_Br8BalYzKPJMG&t=1130
|
| presented at Embedded Linux Conf
| throwaway2027 wrote:
| Is there any benefit in edge cases to using big-endian these
| days?
| zephen wrote:
| Well, blogging about how it's important can certainly give
| insight to others about the age of your credentials, just in
| case repeatedly shouting "Get off my lawn!" didn't suffice.
| bluGill wrote:
| For most code it doesn't matter. It matters when you are writing
| files to be read by something else, or when sending data over a
| network. So make sure the places where those happen are thin
| shims that are easy to fix if it doesn't work. (that is done
| write data from everywhere, put a layer in place for this).
| bluGill wrote:
| What I really want is memory order emulation. X86 as strong
| memory order guarantees, ARM has much weaker guarantees. Which
| means the multi-threaded queue I'm working on works all the time
| on development x86 machine even if I forget to put in the correct
| memory-order schematics, but it might or might not work on ARM
| (which is what my of my users have). (I am in the habit of
| running all my stress tests 1000 times before I'm willing to send
| them out, but that doesn't mean the code is correct, it means it
| works on x86 and passed my review which might miss something)
| newpavlov wrote:
| For Rust we have Loom [0], but do not expect for it to work on
| your whole application.
|
| [0]: https://github.com/tokio-rs/loom
| ncruces wrote:
| If you're using Go on GitHub (and doing stuff where this actually
| matters) adding this to your CI can be as simple as this:
| https://github.com/ncruces/wasm2go/blob/v0.3.0/.github/workf...
|
| On Linux it's really as simple as installing QEMU binfmt and
| doing: GOARCH=s390x go test
| susam wrote:
| I wrote a similar post [1] some 16 years ago. My solution back
| then was to install Debian for PowerPC on QEMU using qemu-system-
| ppc.
|
| But Hans's post uses user-mode emulation with qemu-mips, which
| avoids having to set up a whole big-endian system in QEMU. It is
| a very interesting approach I was unaware of. I'm pretty sure
| qemu-mips was available back in 2010, but I'm not sure if the
| gcc-mips-linux-gnu cross-compiler was readily available back
| then. I suspect my PPC-based solution might have been the only
| convenient way to solve this problem at the time.
|
| Thanks for sharing it here. It was nice to go down memory lane
| and also learn a new way to solve the same problem.
|
| [1] https://susam.net/big-endian-on-little-endian.html
| mistrial9 wrote:
| "memory lane" !!
| zajio1am wrote:
| There is one reason not mentioned in the article why it is worth
| testing code on big-endian systems - some bugs are more visible
| there than on little-endian systems. For example, accessing
| integer variable through pointer of wrong type (smaller size)
| often pass silently on little-endian (just ignoring higher
| bytes), while read/writ bad values on big-endian.
| beached_whale wrote:
| I've used docker buildx to do this in the past. Easier to work
| with than qemu directly(it does so under the hood).
| IshKebab wrote:
| > When programming, it is still important to write code that runs
| correctly on systems with either byte order
|
| Eh, is it? There aren't any big endian systems left that matter
| for anyone that isn't doing super niche stuff. Unless you are
| writing a really foundation library that you want to work
| _everywhere_ (like libc, zlib, libpng etc.) you can safely just
| assume everything is little endian. I usually just put a
| static_assert that the system is little endian for C++.
| 1over137 wrote:
| >But without access to a big-endian machine, how does one test
| it? QEMU provides a convenient solution. With its user mode
| emulation we can easily run a binary on an emulated big-endian
| system
|
| Nice article! But pity it does not elaborate on how...
| siraben wrote:
| Without installing anything, this can also be reproduced with a
| shell script that uses a Nix shebang to specify the cross
| compilers.
|
| https://gist.github.com/siraben/cb0eb96b820a50e11218f0152f2e...
| drob518 wrote:
| This whole endianness issue can be traced to western civilization
| adopting Arabic numbers. Western languages are written left to
| right, but Arabic is right to left. Thus, Arabic numbers appear
| as big-endian when viewed in western languages. Consequently,
| big-endian appears to be "normal" for us in the modern age. But
| in Arabic, numbers appear little-endian because everything is
| right to left. Roman numbers are big-endian, though. Maybe that's
| why we kept the Arabic ordering even when adopting the system? We
| could have flipped Arabic numbers around and written them as
| little-endian, but we didn't.
| zajio1am wrote:
| 'Arabic' numbers comes originally from India, from Brahmi
| numerals. And Brahmi script was left to right. So big-endian
| was 'normal' even originally, it was Arabs who kept left-to-
| right numbers within right-to-left script (and therefore use
| little-endian relative to direction of Arabic script).
| RustyRussell wrote:
| As with many comments here: use a build-time assertion that the
| system is little-endian, and ignore it. _Untested code is broken
| code_.
|
| I was at IBM when we gave up on big endian for Power. Too much
| new code assumed LE, and we switched, despite the insane
| engineering effort (though TBH, that effort had the side effect
| of retaining some absolutely first-class engineers a few more
| years).
___________________________________________________________________
(page generated 2026-04-03 23:01 UTC)