Assembly

At this stage we know the two foundations to eventually create an executable from some assembly code, but what about writing some actual assembly? This is the interesting part, as most of us know, assembly differs architecture to architecture, processor to processor and manufacturer to manufacturer. Whilst tools like AS try to make assembly consistent cross architectures, more low level details such as register locations are all independent.

In this post I’m focusing on ARM11, specifically the Raspberry Pi Zero. However, with enough Google-foo and manual reading, you should be able to port this over to any machine type.

All processors should have a freely available Technical Reference Manual, the Raspberry Pi Zero TRM is here: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.set.arm11/index.html (Core ARM1176JZ).

For this post, we’re only really interested in the core register set and the ability to load a register with an address, I won’t be going into any indepth assembly language.

Let’s write some assembly that does nothing but simply returns a 204 Unix exit code.

If you’re not familiar with Unix, programs may close with a code which represents the status of that program, 0 is typically successful where anything else is an error, we will write a program than returns a 204 exit code.

To put this into context, continuous integration systems such as Jenkins may use this exit code to detect a successful or failed build.

$ vim errcode.s

Application code:

.text .global _start

_start:

mov r0, $204 /* Set exit code to 204 in register r0 */

mov r7, $1 /* Load syscall number into register r7 (1 for exit) */

swi $0 /* Invoke the system call */

Assembly programs can be split up into three sections; .text being the main application code, .data for declaring and initialising any constants and .bss to declare variables, for this example we simply have a .text section.

We must then define our entry point into our assembly code so AS knows where to look to begin compiling.

.text

.global _start

Next we must actually define the entry block to the application.

_start:

Now for the interesting part. Next we must begin our work with the CPUs registers. Simply put, a register is a holding place within the CPU for data and instructions. We move things into registers to perform calculations on them, execute calls to the Linux kernel and many other operations during a program's execution lifecycle.

In order to throw an exit code from our application, we must first understand that we need to interact with the Kernel, we do so through a software interrupt, through this interrupt we must ask the Kernel to exit our program and throw a particular status code.

There is a Linux man page that documents this function and the arguments it accepts: http://man7.org/linux/man-pages/man3/exit.3.html.

You can see it accepts a single argument, the status, or in other words, exit code.

In a high level programming language like C, this could probably all be achieved in one single line of code, however, assembly is far more low level than that, so we must deal with placing things into registers and invoking a call to the Kernel manually.

mov r0, $204 /* Set exit code to 204 in register r0 */

mov r7, $1 /* Load syscall number into register r7 (1 for exit) */

swi $0 /* Invoke the system call */

Assemble and link it to see the results:

$ as -o errcode.o errcode.s

$ ln -o errcode errcode.o

$ ./errcode

$ echo $? => 204

echo $? simply returns the status of the last command executed on the command line, here we can see 204 returned, the number we expected.

Here is a quick guide to syscall numbers in Linux: http://asm.sourceforge.net/syscall.html.

Breakdown: