Months ago, someone on the FreeBSD forums wanted help getting an assebly language program running on a 64 bit intel machine. I read through the FreeBSD Developers’ Handbook x86 Assembly Language Programming section, and sure enough the 32 bit examples did not work. x86 and x86-64 assembler are just plain different. Also, the ABI is completely different.
I managed to find an x86-64 hello world example for FreeBSD. The environment works. Great! Now what? The problem with hello world examples is that there is no input. Without knowing where to go next, a hello world example is not very useful. Between the Developer’s Handbook, the System V AMD64 ABI Reference and an x86-64 tutorial (that has since disappeared) I managed to write a command line utility in x86-64 ASM that processes command line arguments.
Then I thought back to the days when I wrote ARM assembler for the Gameboy Advance and Nintendo DS and wanted to write a command line UNIX utility in ARM assembler. My Raspberry Pi was halfway around the world at the time, but my Android phone was handy. No FreeBSD on my phone, but a few people had written hello world examples for android (1)(2)(3). FreeBSD and Linux appear to use the same ARM EABI documented on the ARM site. Also, Android’s bionic C libaray has a lot of BSD in it.
The Developer’s Handbook notes that “Assembly language programming under UNIX® is highly undocumented”. I am writing this post to document writing a command line UNIX application in assembler that conforms to the ARM EABI. Specifically, this application will run on Android. Remember, there has never been an easier time to learn assembler!
My GitHub repository for this project can be found here. Please note that manually linking object files is probably not standard Android NDK usage. The build instructions may break in the future. If that happens, good luck figuring the necessary flags to build the examples. =)
Software Versions
Instructions
First, install the Android SDK and Android NDK. Make sure ADB has been installed and you connect to your test device.
A rooted device is required to run ASM programs on Android with these instructions. Running ADB as root is handy. You will need chmod, so you will need to install busybox or some other box that provides the same functionality.
Next, install the Android NDK standalone toolchain. The sysroot path probably needs to be defined. Also adding paths for the SDK, NDK and the to be generated standalone NDK is a good idea. I added these lines to my .profile. Adjust pathnames as necessary.
64 bit ARM devices use aarch64 instead of arm. The following commands may be useful when trying to figure out the architecture of your device.
The first program to build and run is a hello world example written in C. In general, it is generally a good idea to have a working C implementation before writing anything in ASM.
Build it with the following commands.
This is a funny way of building a C program. What is going on? The end goal is to build and run assembler programs. Assembly files need to run through the assebler and the linker. In order to link ASM object files with C object files all object files need to be manually linked. The entry point to a C program is main, but the program really takes control in the _start function. The CRT files contain this _start function. It has all of the setup code for the “C runtime”. This code zeros memory and does other boilerplate tasks. Your compiler usually includes the C runtime automatically so you do not need to think about it. The -lc flag links the standard C library. This is another step your compiler usually handles automatically.
Running the program on the phone should print “Hello, World! [C]”. This will remount the system partition of your phone in read write mode. If you do not know what that means stop reading and do not proceed.
With the C version working, it is time to rewrite the program in ARM ASM. Let us start with a header that defines Android system calls.
Next, the main assembler file.
The next step is building the ASM version.
The start.s file contains a start function, so the C runtime is not necessary. Functions from the C library can be called from assebler, but this function is using system calls, so -lc is not necessary.
Running it on the phone should print “Hello, World! [ASM]”.
A Makefile for the hello world project looks like this.
The next program will echo all of the command line arguments. This is the C source code:
This is probably not what you expected to see. To be fair, the first version looped over argv and used printf. The C version is, however, supposed to be a C representation of the ASM. This version of C main uses the same algorithm as the following ASM. The following start.s uses the same system.inc.
The Makefile is more or less the same, but it has these changes.
Build and test both versions with the following commands.
The output should be as follows:
The programs can be uninstalled as follows.
The GitHub repository has six projects. The hello world and arg_echo projects are listed above. There are a couple more versions of hello world. The puts_hello_world project links to libc and replaces the system call with puts(). The main_hello_world project goes a step furthur and uses an ASM main function and the CRT instead of a _start function. The interoperate project calls C, ASM and inline ASM from both C and ASM. The arg_sort project uses structs and malloc to sort command line arguments with a binary tree. The GitHub Makefiles have targets for working with GDB. NOTES.txt contains project notes and references.
Todo
EABI command line arguments (kind of pushed on the stack; _start and main are different) (EABI post?)
EABI function calls (first few parameters in registers, everything else on the stack) (EABI post?)
Position independent code / Global offset table / Procedure linkage table (probably another post)