📜 ⬆️ ⬇️

ARM assembler

Hello to all!
By occupation I am a Java programmer. The last months of work have forced me to get acquainted with the development for Android NDK and, accordingly, writing native applications in C. Here I was faced with the problem of optimizing Linux libraries. Many turned out to be absolutely not optimized for ARM and heavily loaded the processor. Previously, I practically did not program in assembler, so at first it was difficult to start learning this language, but still I decided to try. This article is written, so to speak, from newbie to newbies. I will try to describe the basics that I have already studied, I hope someone will be interested. In addition, I will be glad to constructive criticism from the side of professionals.

Introduction

So, to begin with, let's see what is ARM. Wikipedia gives the following definition:

ARM (Advanced RISC Machine, Acorn RISC Machine, Advanced RISC Machine) architecture is a family of licensed 32-bit and 64-bit microprocessor cores developed by ARM Limited. The company deals exclusively with the development of kernels and tools for them (compilers, debugging tools, etc.), earning on the licensing of architecture to third-party manufacturers.

If anyone does not know, now most of mobile devices, tablets are developed on this particular processor architecture. The main advantage of this family is low power consumption, due to which it is often used in various embedded systems. The architecture has evolved over time, and since ARMv7 3 profiles have been defined: 'A' (application) - applications, 'R' (real time) - in real time, 'M' (microcontroller) - microcontroller. The history of the development of this technology and other interesting data you can read on Wikipedia or googling on the Internet. ARM supports different modes of operation (Thumb and ARM, moreover Thumb-2 has recently appeared, being a mixture of ARM and Thumb). In this article, we consider the actual ARM mode, in which the 32-bit instruction set is executed.
')
Each ARM processor is made up of the following blocks:

This is not all components of ARM, but the deepening into the wilds of building processors is not included in the topic of this article.
Pipeline execution

The ARM processors use a 3-stage pipeline (starting with ARM8, a 5-stage pipeline was implemented). Consider a simple pipeline on the example of the processor ARM7TDMI. Execution of each instruction consists of three steps:

1. Sampling stage (F)
At this stage, instructions come from RAM to the processor pipeline.
2. Decoding Phase (D)
Instructions are decoded and their type is recognized.
3. Stage performance (E)
The data enters the ALU and is executed and the resulting value is written to the specified register.

But when developing it is necessary to take into account that there are instructions that use several execution cycles, for example, load (LDR) or store. In this case, the execution stage (E) is divided into stages (E1, E2, E3 ...).
Conditional execution

One of the most important functions of an ARM assembler is conditional execution. Each instruction can be executed conditionally and suffixes are used for this. If the suffix is ​​added to the instruction name, then before executing it, the parameters are checked. If the parameters do not meet the condition, the instruction is not executed. Suffixes:
MI is a negative number
PL - positive or zero
AL - always carry out instructions
The suffixes of conditional execution are much larger. The remaining suffixes and examples read in the official documentation: ARM documentation
And now it's time to consider ...
Fundamentals of the ARM assembler syntax

Those who previously worked with the assembler can actually skip this item. For all the others I will describe the basics of working with this language. So, each assembly language program consists of instructions. The instruction is created this way:
{label} {instruction | operands} {@ comment}
Label is an optional parameter. Instruction - directly mnemonic instructions to the processor. Basic instructions and their use will be discussed further. Operands - constants, addresses of registers, addresses in random access memory. Comment is an optional parameter that does not affect the execution of the program.
Register Names

The following register names are allowed:
1.r0-r15

2.a1-a4

3.v1-v8 (variable registers, from r4 to r11)

4.sb and SB (static register, r9)

5.sl and SL (r10)

6.fp and FP (r11)

7.ip and IP (r12)

8.sp and SP (r13)

9.lr and LR (r14)

10.pc and PC (software counter, r15).

Variables and costants

In ARM assembler, like any (practically) other programming language, variables and constants can be used. They are divided into the following types:

Numeric variables are initialized as follows:
a SETA 100; creates a numeric variable “a” with a value of 100.
String variables:
improb SETS "literal"; creates a variable improb with the value "literal". ATTENTION! Variable value can not exceed 5120 characters.
Logical variables use the values ​​TRUE and FALSE respectively.

Examples of ARM assembler instructions

In this table I have collected the basic instructions that will be required for further development (at the most basic stage :):
TitleSyntaxApplication
ADD (add)ADD r0, r1, r2
r0 = r1 + r2
SUB (subtraction)SUB r0, r1, r2
r0 = r1 - r2
RSB (reverse subtraction)RSB r0, r1, # 10
r0 = 10 - r1
MUL (multiplication)MUL r0, r1, r2
r0 = r1 * r2
MovMOV r0, r1
r0 = r1
ORR (logical operation)ORR r0, r1, r2
r0 = r1 | r2
TEQTEQ r0, r1
r0 == r1
LDR (download)LDR r4, [r5]
r4 = * r5
STRSTR r4, [r5]
* r5 = r4
ADRADR r3, a
a is a variable. r3 = & a


To fix the use of basic instructions, let's write a few simple examples, but first we need the arm toolchain. I work in Linux so I chose: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). It is put as easy as any other program on Linux. In my case (Russian Fedora) it only took to install rpm packages from the site.
Now it's time to write the simplest example. The program will be absolutely useless, but the main thing that will work :) Here is the code that I offer you:
start: @  ,    mov r0, #3 @    r0  3 mov r1, #2 @      r1,     2 add r2, r1, r0 @   r0  r1,    r2 mul r3, r1, r0 @    r1    r0,    r3 stop: b stop @    

We compile the program before receiving the .bin file:
 /usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o arm.elf arm.o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binary arm.elf arm.bin 

(the code in the arm.s file, and the toolchain in my case is in the / usr / arm / bin / directory)
If everything went well, you will have 3 files: arm.s (the actual code), arm.o, arm.elf, arm.bin (the executable program itself). In order to test the program does not necessarily have its own arm device. It is enough to install QEMU. For reference:
QEMU is a free, open source program for emulating hardware on various platforms.

Includes emulation of Intel x86 processors and I / O devices. It can emulate 80386, 80486, Pentium, Pentium Pro, AMD64 and other x86-compatible processors; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k - only partially.

Works on Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android, etc.


So, for emulation, arm will need qemu-system-arm. This package is in yum, so that those who have Fedora, you can not bother and just execute the command:
yum install qemu-system-arm

Next, you need to run the ARM emulator, so that it executes our arm.bin program. To do this, create a file flash.bin, which will be the flash memory for QEMU. It's very easy to do this:
 dd if=/dev/zero of=flash.bin bs=4096 count=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc 

Now we load QEMU with the received flash memory:
 qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null 

At the exit, you will get something like this:

[anton @ localhost ~] $ qemu-system-arm -M connex -pflash flash.bin -nographic -serial / dev / null
QEMU 0.15.1 monitor - type 'help' for more information
(qemu)

Our arm.bin program had to change the values ​​of the four registers, so to check the correctness of the work, let's look at these registers. This is done very simple command: info registers
At the output, you will see all 15 ARM registers, with the four of them having modified values. Check :) The values ​​of the registers coincide with those that can be expected after the execution of the program:
 (qemu) info registers R00=00000003 R01=00000002 R02=00000005 R03=00000006 R04=00000000 R05=00000000 R06=00000000 R07=00000000 R08=00000000 R09=00000000 R10=00000000 R11=00000000 R12=00000000 R13=00000000 R14=00000000 R15=00000010 PSR=400001d3 -Z-- A svc32 


PS In this article I tried to describe the basics of programming in ARM assembler. Hope you enjoyed it! This is enough to further delve into the wilds of this language and write programs on it. If everything works out, I will write further about what I find out myself. If there are errors, please do not kick, since I am new to the assembler.

Source: https://habr.com/ru/post/133808/


All Articles