|home| |posts| |projects| |cv| |bookmarks| |github|

Endianess Test With Zig and Qemu

If you send data over a network/write data to a file on one machine, with let's say x86_64 arch, and then receive that data/read that same file from another machine, with let's say mips arch, you need to write it with a specific byte order(little or big endian).

Therefore you might want to test your code to make sure it works correctly on both architectures.

This can be done by using the techniques described in the previous two posts( this one and this one).

I'm going to write two simple programs, one that writes an integer to a file(the writer) and one that reads that file and prints the integer from it(the reader).

I'll show two versions for both the writer and the reader, one that handles endianess correctly and one that doesn't.

The writer will be executed on x86_64(little endian) and the reader on mips(big endian).

To keep this post short, the code shown bellow will omit the error handling.

Incorrect version

Let's start with the bad version.

Here's the writer:

#include <stdint.h>
#include <stdio.h>

int main() {
  FILE* f = fopen("data.bin", "wb");

  uint32_t data = 42;

  fwrite(&data, 1, sizeof(data), f);

  printf("%d\n", data);

  fclose(f);

  return 0;
}

And here's the reader:

#include <stdint.h>
#include <stdio.h>

int main() {
  FILE* f = fopen("data.bin", "rb");

  uint32_t data = 0;
  fread(&data, 1, sizeof(data), f);

  printf("%d\n", data);

  fclose(f);

  return 0;
}

Now compile the programs:

zig cc -o writer writer.c -target x86_64-linux-musl
zig cc -o reader reader.c -target mips-linux-musl

Run the writer:

./writer
42

And run the reader:

qemu-mips-static ./reader
704643072

As expected, the output is not the same, the writer writes 42 but the reader reads 704643072.

Correct version

Now let's write the correct version by using the most sensible way of handling byte order in data streams (see the byte order fallacy).

Here's the writer:

#include <stdint.h>
#include <stdio.h>

static void le_put_uint32(uint8_t* b, uint32_t d) {
  b[0] = (uint8_t)(d);
  b[1] = (uint8_t)(d >> 8);
  b[2] = (uint8_t)(d >> 16);
  b[3] = (uint8_t)(d >> 24);
}

int main() {
  FILE* f = fopen("data.bin", "wb");

  uint32_t data = 42;

  uint8_t b[sizeof(data)];
  le_put_uint32(b, data);

  fwrite(b, 1, sizeof(b), f);

  printf("%d\n", data);

  fclose(f);

  return 0;
}

And here's the reader:

#include <stdint.h>
#include <stdio.h>

static uint32_t le_get_uint32(const uint8_t* b) {
  return (uint32_t)(b[0]) |
         (uint32_t)(b[1]) << 8 |
         (uint32_t)(b[2]) << 16 |
         (uint32_t)(b[3]) << 24;
}

int main() {
  FILE* f = fopen("data.bin", "rb");

  uint8_t b[sizeof(uint32_t)];
  fread(b, 1, sizeof(b), f);

  uint32_t data = le_get_uint32(b);

  printf("%d\n", data);

  fclose(f);

  return 0;
}

Now compile the programs:

zig cc -o writer writer.c -target x86_64-linux-musl
zig cc -o reader reader.c -target mips-linux-musl

Run the writer:

./writer
42

And run the reader:

qemu-mips-static ./reader
42

Now the code works correctly and the reader reads the same value as the writer wrote.

Final words

I find this kind of technique very useful for testing code that has to create some form of data stream which may be generated/consumed from machines with different byte orders.