Skip to content

Creating a 32bit container on an AArch64 server

Yichao Yu edited this page Jan 17, 2021 · 6 revisions

This is a list of the problems (and solutions to most of them) I had when trying to set up a 32bits container on a fast AArch64 server for 32bits ARM development.

LXC vs systemd-nspawn

I used systemd-nspawn when setting up a 32bits (i686) container on a x64 host. It was convenient on x64 since it supports a --personality option which can be used to set the uname -m to i686 (same as setarch i686 or linux32).

However, when I tried to use it on AArch64, the container is killed by SYS when I tried to run it (see here for someone else who has the same problem). The --personality optional doesn't seem to work on AArch64 either. The problem could be related to some of the issues below but I haven't tested it again after fixing those problems yet.

The alternative I've tried is lxc, which seems to work much better on AArch64. It also comes with templates for many distros making it easier to setup a cantainer for a different distro.

LXC creation

Local template is deprecated so the way to create a container is to use the download template. ArchLinux container for amd64 (x86_64), arm64 (aarch64) and armhf (armv7h) are supported.

The command to use for an armv7 container is

lxc-create --name=<container_name> -t download -- --dist archlinux --release current --arch armhf

LXC init

Old version of lxc requires manually setting the arch by changing the init in the container. As of version 4, this does not seem to be a problem anymore and the ELF arch is set correctly by default.

LXC network

Since I'm not really interested in isolating the network in the container I set lxc.net.0.type to none, which makes the container using the same network as the host. (This can probably cause security issues for other use case.)

Some distros (CentOS and Debian both seem to do this) turns off the network on shutdown causing rebooting the container to reset the host network and in general, I only want to share the network to the container in a read-only way. This is controlled by the NET_ADMIN capability so adding net_admin to lxc.cap.drop in the LXC solves this issue.

LXC autostart

There seem to be multiple way to achieve this. The method I used is to use lxc-autostart. This is a service (lxc-auto.service) that automatically (re?)start containers marked as lxc.start.auto = 1. Simply adding the option to the LXC config file and enable/start the lxc-auto service will make sure that the container is started automatically at host boot time.

An example config file for a 32bits archlinux container

# Template used to create this container: /usr/share/lxc/templates/lxc-download
# Parameters passed to the template: --dist archlinux --release current --arch armhf
# Template script checksum (SHA-1): 9893b2e0dba7be0d74cf38537bebe0af939c269c
# For additional config options, please look at lxc.container.conf(5)

# Uncomment the following line to support nesting containers:
#lxc.include = /usr/share/lxc/config/nesting.conf
# (Be aware this has security implications)

# Distribution configuration
lxc.include = /usr/share/lxc/config/common.conf
lxc.arch = armv7l

# Container specific configuration
lxc.rootfs.path = dir:/var/lib/lxc/arch32/rootfs
lxc.uts.name = arch32

# Network configuration
lxc.net.0.type = none

lxc.start.auto = 1
lxc.cap.drop = setfcap sys_nice sys_pacct sys_rawio net_admin

32bits ELF

Certain kernels (noticeably the one ships with GigaByte MP30-AR0 and the stock ArchLinux ARM linux-aarch64 kernel) do not support 32bits ELF file. This is controlled by the kernel option CONFIG_COMPAT which requires either CONFIG_EXPERT or 4k page size (more on this later).

Page size, min mmap address

The default (only?) page size on ARM (32bits) is 4k and some programs/binaries assumes this. In particular, the dynamic loader / kernel refuses to load any executable who's segment alignments are not a multiple of the page size. Latest binutils always uses at least 64k alignments but the binaries compiled with earlier versions of binutils do not (some in archlinux and most in debian 7) so it is better to set the page size to 4k for maximum compatibility with 32bits applications.

When setting the the page size, also make sure to decrease the minimum allowed mmap address to be the same as the page size. Otherwise, non-privilege process will not be able to map executables compiled with a low load address and can cause SegFault at exec time. (errno is permission denied).

Deprecated instructions

Some armv6 instructions are not supported by the hardware anymore and the kernel emulation for those instructions needs to be turned on in order to run those applications.

The options to enable are CONFIG_SWP_EMULATION, CONFIG_CP15_BARRIER_EMULATION and CONFIG_SETEND_EMULATION under CONFIG_ARMV8_DEPRECATION.

Ref this question on ARM community. Thanks to wookey from the linarno IRC for providing the options to enable instruction emulation and 32bits ELF support.

Some other notes

These are not necessarily related to creating a 32bit container on an aarch64 machine but are some issues I had when using LXC in general.

lxc.service

On Ubuntu lxc-auto doesn't seem to exist but lxc.server does which seems to cover the function of lxc-auto.

schedtune cgroup

On Ubuntu 18.04 with the Quancomm kernel for the RB5 board and lxc 3.0.2, using schedtune cgroup causes an error when trying to start an lxc instance since the kernel does not like nested cgroup for schedtune which lxc wants to do (LXC creates an lxc sub group and a group for the container under that). The error code was printed as a memory allocation error which is quite confusing though the dmesg output was more reasonable: "Nested SchedTune boosting groups not allowed". Disabling the use of this cgroup globally in /etc/lxc/lxc.conf using lxc.cgroup.use key worksaround the problem.

AppArmor

It causes some issues with logind etc in the container so I disabled it on the RB5 board. To be fair, some services in the host also failed so it might not be a apparmor problem rather bad config from thundercomm... I worked around this by disabling apparmor in the kernel command line.

Resolved

When trying to create nested environment (e.g. when building a chroot package), systemd-nspawn by default (called by makechrootpkg) copied the static DNS config to the chroot which talks to resolved on 127.0.0.53. On the RB5 board when using the none network, the resolved from the parent disabled the version in the lxc which somehow causes the chroot DNS resolving to fail (the listened port is visible from the container but somehow the chroot cannot use it...) Disabling the DNSStubListener=no in /etc/systemd/resolved.conf fixed this for me... (probably not the best solution).