Skip to main content


User Emulation with Docker

FROM multiarch/debian-debootstrap:mips-buster-slim as qemu
FROM scratch
ADD ./firmware.tar.gz /
COPY --from=qemu /usr/bin/qemu-mips-static /usr/bin
CMD [ "/usr/bin/qemu-mips-static", "/bin/busybox" ]

Working Chroot Environment

Build Kernel

export WD_PREFIX=$(pwd)/
tar xf linux-4.4.60.tar.xz
mkdir linux_build
cd linux-4.4.60
make ARCH=mips O=../linux_build defconfig
cd ../linux_build
make ARCH=mips menuconfig

Configure mips kernel with: make ARCH=mips menuconfig

  • Machine selection -> MIPS Malta board
  • Endianness Selection () -> Little Endian / Big Endian (choose what you need)
  • General Setup -> Cross-compiler tool prefix -> mipsel-static-linux-gnu-
  • General Setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support
  • Uncheck Enable loadable module support.
  • Device Drivers -> Character devices -> Serial drivers:
    • 8250/16550 and compatible serial support
    • Console on 8250/16550 and compatible serial port

If you attempt to build the kernel at this point, you may find yourself with an error that is similar to:

/usr/bin/ld: scripts/dtc/ multiple definition of `yylloc'; scripts/dtc/dtc-lexer.lex.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status

To fix this, run (from the linux-4.4.60 folder): sed -i 's/^YYLTYPE yylloc;$//' scripts/dtc/dtc-lexer.lex.c_shipped

Build kernel (from linux_build folder): make ARCH=mips -j8 vmlinux

Build Userspace

export TC_TUPLE=mipsel-static-linux-gnu
mkdir -p ${WD_PREFIX}sysroot ${WD_PREFIX}rootfs
tar -xpf musl-1.2.1.tar.gz
cd musl-1.2.1
./configure --target=${TC_TUPLE} --prefix=${WD_PREFIX}sysroot
make install
cd ../linux_build
make headers_install ARCH=mips INSTALL_HDR_PATH=${WD_PREFIX}sysroot
cd ..
tar -xpf busybox-1.32.0.tar.bz2
cd busybox-1.32.0
make menuconfig

Setup some build configurations:

  • Enable Settings -> Build Options -> Build static binary (no shared libs)
  • Set Settings -> Build Options -> Cross compiler prefix to aarch64-linux-gnu-
  • Set Settings -> Build Options -> Path to sysroot to /projects/playground/minsys/sysroot
  • Set Settings -> Build Options -> Additional CFLAGS to -Wno-undef -Wno-parentheses -Wno-strict-prototypes -specs=/projects/playground/minisys/sysroot/lib/musl-gcc.specs
  • Set Settings -> Installation Options -> Destination path for 'make install' to /projects/playground/minsys/rootfs
  • Disable Linux System Utilities -> eject -> Scsi Support

Build busybox: make install

Build and install dropbear:

tar xf dropbear-2022.82.tar.bz2
cd dropbear-2022.82
./configure --host=mips-static-linux-gnu --prefix=${WD_PREFIX}rootfs --disable-zlib --enable-static CC="mips-static-linux-gnu-gcc -specs=${WD_PREFIX}sysroot/lib/musl-gcc.specs" LD="mips-static-linux-gnu-ld"
make install
cd ..

TODO: Use /etc/init-rc.d with:

  • for script in `ls /etc/init-rc.d` ; do $script ; done
  • find /etc/init-rc.d -maxdepth 1 -type f -executable -exec {} \;

Create generic ${WD_PREFIX}rootfs/init:


mount -t proc proc /proc
mount -t sysfs proc /sys

mount -n -t tmpfs none /dev
mknod -m 622 /dev/console c 5 1
mknod -m 666 /dev/null c 1 3
mknod -m 666 /dev/zero c 1 5
mknod -m 666 /dev/ptmx c 5 2
mknod -m 666 /dev/tty c 5 0
mknod -m 444 /dev/random c 1 8
mknod -m 444 /dev/urandom c 1 9

find /etc/init-rc.d -maxdepth 1 -type f -executable -exec {} \;

exec /bin/sh

Make script executable:

cd ${WD_PREFIX}rootfs
chmod +x init
cd ..

Create ${WD_PREFIX}rootfs/etc/init-rc.d/ init:


if [ ! -e /etc/passwd ]; then
touch /etc/passwd

if [ ! -e /etc/group ]; then
touch /etc/group

# If no root user exists..
grep root /etc/passwd >/dev/null || `adduser root -D -u 0 ; echo "root:root" | chpasswd`

if [ ! -e /home/root ]; then
mkdir /home /home/root

Create ${WD_PREFIX}rootfs/etc/init-rc.d/ init:


ifconfig eth0 netmask up
ping -c 512 -A >/dev/null 2>/dev/null

Create ${WD_PREFIX}rootfs/etc/init-rc.d/ init:

if [ ! -e /etc/dropbear ]; then
mkdir /etc/dropbear

if [ ! -e /dev/pts ]; then
mkdir /dev/pts

# If devpts not mounted...
grep devpts /proc/mounts || mount -t devpts -o rw,mode=620,ptmxmode=666 devpts /dev/pts

dropbear -p 5522 -R -B -a


pushd rootfs
# Note: busybox may have created some of these during its install.
mkdir -p dev bin sbin etc proc sys usr/bin usr/sbin
# TODO: Get the non-sudo equivalents.
mknod -m 622 ./dev/console c 5 1
mknod -m 666 ./dev/null c 1 3
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

TODO: binwalk / rsync squashfs

chmod +x
fakeroot ./

Start emulation with:

qemu-system-mipsel -m 256 -M malta -display curses \
-kernel linux_build/vmlinux \
-append "console=ttyS0 init=/init" \
-initrd initramfs.cpio \
-netdev tap,id=if0,ifname=mipsif0 -device e1000,netdev=if0 \
-netdev socket,id=if1,listen=:2000 -device e1000,netdev=if1 \
-netdev user,id=if2,hostfwd=tcp::2222-:22 -device e1000,netdev=if2

Networking Notes:

  • Setup the tap interface before hand with: sudo tunctl -t mipsif0 ; sudo ifconfig mipsif0 up
  • Network Socket packets are prefixed with 32bit length field followed by ethernet frame.
  • Host Forward Synopsis: hostfwd=tcp:[<hostaddr>]:<hostport>-[guestaddr]:<guestport>
  • You can list multiple hostfwd entries comma separated: -netdev user,id=id0,hostfwd=tcp::2222-:22,hostfwd=tcp::2280-:80

QEmu Usage Notes:

  • Esc + 2 -> Monitor (e.g. quit to exit)
  • Esc + 3 -> Serial Console
  • If you use -display none, Ctrl-A x to quit.