\

Big-Endian Testing with QEMU

71 points - today at 1:28 PM

Source
  • AKSF_Ackermann

    today at 2:46 PM

    > 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.

      • jcalvinowens

        today at 4:31 PM

        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

            today at 7:53 PM

            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 at the boundary and that you have no accidental mixing of formats in your internals; everything has been parsed.

        • socalgal2

          today at 7:25 PM

          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

          • GandalfHN

            today at 4:13 PM

            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

                today at 4:34 PM

                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).

                • adrian_b

                  today at 6:20 PM

                  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.

                  • today at 4:30 PM

                    • 7jjjjjjj

                      today at 5:02 PM

                      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.

                        • namibj

                          today at 6:56 PM

                          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.

                          • zephen

                            today at 6:30 PM

                            > 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.

                            • cmrdporcupine

                              today at 5:25 PM

                              > No big endian system will ever be popular again

                              Cries in 68k nostalgia

                      • j16sdiz

                        today at 3:30 PM

                        If you comes to low level network protocol (e.g. writing a TCP stack), the "network byte order" is always big-endian.

                          • edflsafoiewq

                            today at 4:59 PM

                            That's a serialization format.

                            • 7jjjjjjj

                              today at 5:08 PM

                              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

                                  today at 6:32 PM

                                  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

                                      today at 7:10 PM

                                      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

                                          today at 7:36 PM

                                          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

                                today at 5:22 PM

                                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.

                                • skrtskrt

                                  today at 3:43 PM

                                  Prometheus index format is also a big-endian binary file - havenโ€™t found any reference to why it was chosen.

                              • cbmuser

                                today at 6:24 PM

                                > 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. ;-)

                                • addaon

                                  today at 3:53 PM

                                  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.

                                  • sllabres

                                    today at 5:38 PM

                                    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

                                        today at 5:59 PM

                                        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.

                                    • nyrikki

                                      today at 3:42 PM

                                      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

                                          today at 5:31 PM

                                          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

                                            today at 6:32 PM

                                            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.

                                        • EPWN3D

                                          today at 3:27 PM

                                          I mostly agree, but network byte ordering is still a thing.

                                          • bear8642

                                            today at 3:23 PM

                                            > 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

                                                today at 4:36 PM

                                                The adjacent POWER architecture seems to be used in ppc64le mode these days.

                                                  • classichasclass

                                                    today at 6:11 PM

                                                    For Linux, yes. AIX and IBM i still run big.

                                                      • namibj

                                                        today at 6:59 PM

                                                        The latter can definitely afford a support contract.

                                        • bluGill

                                          today at 3:59 PM

                                          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)

                                        • electroly

                                          today at 2:48 PM

                                          > 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

                                              today at 2:52 PM

                                              > 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

                                                  today at 2:58 PM

                                                  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

                                                      today at 3:03 PM

                                                      I value correct code for purely selfish reasons. The most likely person to try to run my code on a BE system is me.

                                                        • Retr0id

                                                          today at 4:26 PM

                                                          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

                                                              today at 7:02 PM

                                                              u32::from_be_bytes

                                                              u32::from_le_bytes

                                                              u32::from_ne_bytes the n stands for native

                                                          • eesmith

                                                            today at 3:27 PM

                                                            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

                                                                today at 4:52 PM

                                                                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.

                                                                  • classichasclass

                                                                    today at 6:13 PM

                                                                    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.

                                                                    • peyton

                                                                      today at 6:09 PM

                                                                      This is much-appreciated. Iโ€™m hardly a Richard Stallman, but finding little incompatibilities after-the-fact is pretty irritating.

                                                                        • eesmith

                                                                          today at 6:35 PM

                                                                          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.'

                                                                      • eesmith

                                                                        today at 6:15 PM

                                                                        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?

                                                        • edflsafoiewq

                                                          today at 5:01 PM

                                                          Static-assert the machine is little endian.

                                                            • Retr0id

                                                              today at 5:03 PM

                                                              Someone's LLM will comment out that line the moment it causes a build failure

                                                                • thrtythreeforty

                                                                  today at 6:53 PM

                                                                  Then they get to keep both pieces!

                                                                  • edflsafoiewq

                                                                    today at 5:06 PM

                                                                    Oh brave new world, that has such arguments in it!

                                                        • CJefferson

                                                          today at 3:09 PM

                                                          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.

                                                      • siraben

                                                        today at 7:42 PM

                                                        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...

                                                        • 1over137

                                                          today at 7:41 PM

                                                          >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...

                                                          • susam

                                                            today at 4:59 PM

                                                            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

                                                                today at 5:02 PM

                                                                "memory lane" !!

                                                            • zajio1am

                                                              today at 5:07 PM

                                                              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.

                                                              • ncruces

                                                                today at 4:46 PM

                                                                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

                                                                • bluGill

                                                                  today at 3:55 PM

                                                                  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).

                                                                  • eisbaw

                                                                    today at 2:49 PM

                                                                    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

                                                                    • beached_whale

                                                                      today at 5:42 PM

                                                                      I've used docker buildx to do this in the past. Easier to work with than qemu directly(it does so under the hood).

                                                                      • pragmaticviber

                                                                        today at 2:21 PM

                                                                        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

                                                                            today at 2:25 PM

                                                                            Haven't found any bug in QEMU's s390x, but lots in endian code.

                                                                        • throwaway2027

                                                                          today at 3:08 PM

                                                                          Is there any benefit in edge cases to using big-endian these days?

                                                                            • zephen

                                                                              today at 6:37 PM

                                                                              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.

                                                                          • IshKebab

                                                                            today at 7:07 PM

                                                                            > 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++.