001. Installing Debian's x32 port in 2020

Thu, 9 Apr 2020 01:44:47 +0200, last updated Tue, 28 Apr 2020 02:54:07 +0200

Some time ago I bought an HP rp5700 Piece of Shit Point of Sale system with a 2.13GHz Core 2 Duo E6400, fascinating OEM assembly, and FreeDOS, fourth-hand – follow this Twitter thread to experience this machine as its story'd unravelled.

Some time later, I decided to make a router out of it, and, having heard of x32 – 32-bit ABI/userland, with a long-mode kernel/CPU – some time prior, that had become the architecture of choice. x32's proponents tout increased performance compared to amd64, but I couldn't care less; if you're looking for performance comparisons, this ain't it, I did this entirely because it was weird and rife with funny corner cases.

The Debian wiki X32Port page dates back to around when x32 was introduced in 2012 with such beautiful excerpts as:

The second [system] is the QEMU/Chroot guest, and it must run Debian 8/Jessie Unstable.

Which, while amusing, isn't very conducive to getting an x32 system up, nor are the outdated-at-best instructions. So this is the combination of the things I've read and tried in order to install Debian "bullseye/sid" x32 on Wed Apr  1 22:02:50 UTC 2020 (it was already 0:02:50 the next day in my timezone but I forgot to set it; oh well):

Login screen showing the aforementioned date and dpkg --print-architecture returning x32

debian-installer existing for x32 would have solved most (all) of the problems with this install, but #778195 is open since Feb 2015, and dead as of that very November. The patch from message #15 applied (relatively) cleanly on top of 1ab40c5 from Salsa, barring one trivial conflict in debian/control, but I couldn't figure out how to build it for x32 on my amd64 machine (or in general, frankly).

Just for the fucks, I also tried to add and build a kernel config for x32, which ended exactly like all my kernel-building endeavours of the past (plus, tracking and building Debian updates ad infinitum sounds like a horrible time).

I also tried to cross-grade an installed amd64 system, but doing it through buster-to-sid just™ refused to work, and sid-to-sid broke on +b2 "Rebuild with ruby2.5 support dropped" binNMUs existing for i386 and amd64 but not for x32 (being a few versions behind on some packages didn't help, either), although, in the few days it's taken me to write this, it'd started to look like the rest of the architectures are catching up.

And so, it came time to install Debian without d-i, like some sort of Arch user.

# What you'll need

debootstrap and root privileges.

Also a kernel that can run x32 binaries; Debian amd64 kernels can, but have it disabled by default, and need a syscall.x32=y parameter to do so (I looked, out of curiosity: Arch doesn't support x32 at all, Gentoo does, Fedora and RHEL don't; FreeBSD's "Linux® Binary Compatibility" does not (as of 12.1)).

I used d-i's rescue mode (rescue/enable=true parameter) and added syscall.x32=y in the bootloader manually, this allowed me to use d-i for some of the annoying config parts (hostname, network), and PXE-booting it was the most convenient for me.

# What you'll want

In (reverse-)chronological order: a disklabel, a bootloader, an initramfs (generator), a kernel (at time of writing this means Linux, since FreeBSD is i386/amd64-only on x86, Hurd is i386-only), an init, a user, configs – /etc/fstab, /etc/hosts, timezone.

My target disklabel is a standard GPT-on-BIOS setup with a separate /boot (this helps by allowing the use of partlabels throughout, since SZAROTKA-BOOT is longer than the vfat maximum of 11):

nabijaczleweli@szarotka:~$ sudo fdisk -l
Disk /dev/sda: 149.5 GiB, 160041885696 bytes, 312581808 sectors
Disk model: WDC WD1600AAJS-6
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 22C172F7-4E1F-D244-A6DC-260DEE716A5F

Device         Start       End   Sectors  Size Type
/dev/sda1       2048      4095      2048    1M BIOS boot
/dev/sda2       4096 312377343 312373248  149G Linux root (x86-64)
/dev/sda3  312377344 312581774    204431 99.8M Linux extended boot

nabijaczleweli@szarotka:~$ sudo blkid
/dev/sda1: PARTLABEL="szarotka-bios-boot" PARTUUID="a331980b-475a-1f43-b985-0849341df04b"
/dev/sda2: LABEL="szarotka-root-ng" UUID="d9e8441a-1a97-47af-b002-d764ac46121b" TYPE="ext4"
           PARTLABEL="szarotka-root" PARTUUID="52695edc-4993-654c-9e04-e30d9cc1e367"
           TYPE="vfat" PARTLABEL="szarotka-boot" PARTUUID="c817f3e5-e3e5-2449-b0f2-23dbb523a239"

# Assembling the system

Pressing Tab in ISOLINUX et al. pops up the command-line, which looks vaguely like this:

ISOLINUX boot menu, showing Expert Install, Rescue Mode, and Automated Install; below it a editing prompt to edit the kernel command-line, the last element being syscall.x32=y

Going through the rescue up to this menu ensures all the small annoying things are configured:

Two QEMU windows, both showing a debian-installer 'Enter rescue mode' screen; the one at the top says that 'The installer could not find any partitions' with a <Go Back> and <Continue> buttons; the one at the bottom says to 'Enter a device you wish to use as your root file system' with a list of drives between /dev/sda1 and /dev/sda3, an 'Assemble RAID array' and 'Do not use a root file system' options as well as a <Go Back> button

You can press Alt+F2/F3 to switch to a virtual terminal with a usable background and $TERM.

Prepare the target VFS with all the partitions mounted first. ~ # mkdir target
~ # mount /dev/disk/by-partlabel/szarotka-root target
~ # mkdir target/boot
~ # mount /dev/disk/by-partlabel/szarotka-boot target/boot
Start off with a simple chroot.
Note the ill-advised --no-check-gpg, but finding/downloading/extracting that .deb in a bare *X environment is too annoying to warrant itself.
Note also the sid suite — no stable for as long as x32 is a port.
~ # debootstrap --arch=x32 --variant=minbase --include=debian-ports-archive-keyring --no-check-gpg sid target https://deb.debian.org/debian-ports
Now you can (try to) chroot into the new system; if you see chroot: can't execute '/bin/sh': exec format error, your kernel doesn't support x32 executables. On Debian kernels, verify that you have syscall.x32=y in /proc/cmdline.
The bind-mounts of /dev, /sys, and /proc are there for the GRUB install and dracut's host-only mode; of /tmp and /run – to avoid leaving some mounted-over garbage in the target system.
~ # mount --bind /dev target/dev
~ # mount --bind /sys target/sys
~ # mount --bind /proc target/proc
~ # mount --bind /run target/run
~ # mount -t tmpfs tmpfs target/tmp
~ # chroot target /bin/bash
root@szarotka:/# # Huzzah!
Sprinkle in your favourite init and initramfs generator. root@szarotka:/# apt install systemd-sysv
root@szarotka:/# apt install dracut --no-install-recommends
The time has come to add a kernel, which is the only required amd64 part of the system (if you need something from the non-free or contrib components (e.g. microcode tools), you'll still need the amd64 packages, or to apt source and rebuild them for x32).
Note the [arch] options – those are to avoid errors from apt as it tries to find x32 in the non-ports repositories, and vice versa.
Note also lack of deb-src spec for the ports repository, as it doesn't have sources at all (compare).
root@szarotka:/# sed -i 's/deb/deb [arch=x32]/' /etc/apt/sources.list
root@szarotka:/# cat >> /etc/apt/sources.list
deb     [arch=amd64] https://deb.debian.org/debian sid main contrib non-free
deb-src              https://deb.debian.org/debian sid main contrib non-free
root@szarotka:/# dpkg --add-architecture amd64
root@szarotka:/# apt update
root@szarotka:/# apt install linux-image-amd64 --no-install-recommends
And a bootloader (at time of writing grub2 recommends os-prober – if you don't plan on running other OSs this can be --no-install-recommendsed).
Remember: at the prompt, if unsure about the choice offered by GRUB, you can switch VTs again (Alt+F2/F3) and use blkid or lsblk.
The sed adds the syscall toggle inside GRUB_CMDLINE_LINUX, rather than _DEFAULT, since it's required to boot the system at all (not using a text editor because, despite ed being the standard text editor, using sed/awk/cat is less annoying and easier to spec like this (also, the normal vi derivatives were broken due to the aforementioned ruby2.5-related problem (I ended up using nvi))).
root@szarotka:/# apt install grub2
Configuring grub-pc

  1. /dev/sda (??? MB; ???)  2. - /dev/sda2 (??? MB; /)  3. - /dev/sda3 (??? MB; /boot)

GRUB install devices: /dev/sda

Installing for i386-pc platform.
root@szarotka:/# sed -ri 's/(CMDLINE_LINUX=)""/\1"syscall.x32=y"/' /etc/default/grub
root@szarotka:/# update-grub
Various bits and bobs; /etc/resolv.conf was already set up during debootstrap.
If using vfat for your /boot, check if your kernel sets FAT_DEFAULT_UTF8=y, or specify utf8=y instead of defaults.
This /etc/hosts is really bare-bones and the d-i one is too long to reproduce here in full; I'd recommend copying and modifying one from another host on your network.
Previously this paragraph contained some ill-advised init-specific bollocks about setting timezones, reproduced below, instead of the dpkg-reconfigure; mistakes of my youth slash two weeks ago I guess.
If you're not using systemd, here is where you'd link /etc/localtime to somewhere in /usr/share/zoneinfo (see localtime(5)), otherwise run timedatectl(1) set-timezone CET after rebooting.
root@szarotka:/# cat > /etc/fstab
PARTLABEL=szarotka-root  /      ext4  noatime   0  1
PARTLABEL=szarotka-boot  /boot  vfat  defaults  0  2
root@szarotka:/# cat > /etc/hosts localhost szarotka szarotka.local.nabijaczleweli.xyz
root@szarotka:/# dpkg-reconfigure tzdata
Configuring tzdata

  1. Africa  […]  7. Atlantic  8. Europe  9. Indian  10. Pacific  11. SystemV  […]
Geographic area: 8

  4. Athens  […]  32. Minsk  39. Prague  46. Simferopol  53. Ulyanovsk  60. Warsaw
Time zone: 60

Current default time zone: 'Europe/Warsaw'
Local time is now:      Tue Apr 28 02:25:44 CEST 2020.
Universal Time is now:  Tue Apr 28 00:25:44 UTC 2020.
root@szarotka:/# apt install sudo
root@szarotka:/# adduser --ingroup users nabijaczleweli
root@szarotka:/# adduser nabijaczleweli sudo

This should ensure the system boots and is usable enough after doing so — press Ctrl+Alt+Del and find out!

Linux console in QEMU window, showing a login, uname output of 'Linux szarotka 5.5.0-1-amd64', dpkg --print-architecture yielding 'x32', dpkg listing of amd64 packages yielding just two kernel packages, and the output of file on a system executable starting with 'ELF 32-bit LSB shared object, x86-64'

If that didn't get your system to boot, please do shoot me an e-mail, but if it did, then that's all from me for today, except, maybe, one last thing?

Ever wondered what'd happen if you had inits but the kernel couldn't execute them? So did I:

Linux console during init in QEMU windows, first complaining about 'modprobe bindfmt-464c' not being processable, then 'Starting init: /sbin/init exists but couldn't execute it (error: -8)', then 'Run /etc/init as init process', then /bin/init, then /bin/sh, yielding the same error as /sbin/init, finishing with a kernel panic 'not syncing: No working init found.  Try passing init= option to kernel.'

Alright, bye!

Nit-pick? Correction? Improvement? Annoying? Cute? Anything? Don't hesitate to post or open an issue!

Creative text licensed under CC-BY-SA 4.0, code licensed under The MIT License.
This page is open-source, you can find it at GitHub, and contribute and/or yell at me there.
Like what you see? Consider giving me a follow over at social medias listed here, or maybe even a sending a buck or two patreon my way if my software helped you in some significant way?
Automatically generated with GCC 5‍.‍4‍.‍0's C preprocessor on 15.03.2021 23:02:07 UTC from src/blogn_t/001-x32-in-2020.html.pp.
See job on TravisCI.
RSS feed