In which I compile snippets of useful Nix code
Cross-Compiling Arm Assembly in a Nix Flake
We want to compile this trivial assembly targeting ARM assembly on a x86_64
Linux machine (running Debian 12 with Nix in this case):
.global main
.type main, %function
main:
mov w0, #123
ret
The assembly file is in a file called src/main.s
.
The program return 123 as a status code, so we can check whether the program
ran correctly later by checking echo $?
in bash or similar. We can run the
program using qemu-aarch64
(not qemu-system-aarch64
) to emulate an ARM CPU
and corresponding Linux user land, instead of having to spin up a virtual
machine – quite handy when reverse engineering binaries on an x86_64
host.
We want to work with a Nix flake here. That
means better support for different targets, and the ability to have better
reproducibility between builds, as all Nix dependencies are fixed in a
flake.lock
file.
I also want to use flake-utils for an easier time specifying the flake file.
{
description = "Arm64 (aarch64) cross-compile demo";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
crossSystem = {
config = "aarch64-unknown-linux-gnu";
};
};
in
{
packages = rec {
cross-arm-64-asm = pkgs.callPackage
({ stdenv, gcc }:
stdenv.mkDerivation {
name = "cross-arm-64-asm";
nativeBuildInputs = [
gcc
];
phases = [ "buildPhase" "installPhase" ];
buildPhase = ''
$CC -march=armv8-a ${./src/main.s} -o main
'';
installPhase = ''
mkdir -p $out/bin
cp main $out/bin
'';
}
)
{ };
default = cross-arm-64-asm;
};
}
);
}
Build the file by running nix build -L .#
. On my system I got the following
output:
warning: Git tree '/home/justusperlwitz/projects/nix-tricks' is dirty
warning: Ignoring setting 'auto-allocate-uids' because experimental feature 'auto-allocate-uids' is not enabled
warning: Ignoring setting 'impure-env' because experimental feature 'configurable-impure-env' is not enabled
cross-arm> Running phase: buildPhase
cross-arm> Running phase: installPhase
We see that the output is placed in a symlinked folder called result
.
# tree result
result
└── bin
└── main
2 directories, 1 file
We check the contents of the resulting result/bin/main file
:
# file result/bin/main
result/bin/main: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/j8h1n0kgl15gpk270i7mr441yamnzvdg-glibc-aarch64-unknown-linux-gnu-2.38-27/lib/ld-linux-aarch64.so.1, for GNU/Linux 3.10.0, not stripped
If you have QEMU on your system, you can then run it like so:
$qemu-aarch64 ./result/bin/main
$echo $?
# Outputs `123`