Chapter 3: Basic ARM64 Instructions

3.1 What are Instructions?

Instructions are the basic commands that tell the computer what to do. Each instruction is a simple operation the processor can execute.

An instruction typically has two parts:

  1. The operation (what to do)

  2. The operands (what to do it with)

Example:

MOV x0, #42
  • MOV: operation (move data)

  • x0: destination operand

  • #42: source operand

Main Categories of ARM64 Instructions

ARM64 has several categories of instructions. We'll cover the following:

  1. Data Processing Instructions

    • Perform operations on data in registers

    • Examples: ADD, SUB, AND, ORR

  2. Memory Instructions

    • Move data between registers and memory

    • Examples: LDR (load), STR (store)

  3. Branch Instructions

    • Control program flow

    • Examples: B (branch), BL (branch with link)

  4. System Instructions

    • Interact with system features

    • Examples: SVC (supervisor call)

In the following sections, we'll explore each of these categories in detail, starting with Data Processing Instructions. We'll explain what each category does, provide examples, and show how they're used in real ARM64 assembly programming.

3.2 Data Processing Instructions

Data Processing Instructions are fundamental operations in ARM64 assembly. They manipulate data within registers, forming the core of most computational tasks.

3.2.1 Integer Arithmetic

Arithmetic instructions perform basic mathematical operations on register values.

ADD (Addition)

ADD Xd, Xn, Xm

This instruction adds the values in registers Xn and Xm, storing the result in Xd.

Example:

ADD X0, X1, X2  ; X0 = X1 + X2

Explanation: If X1 contains 5 and X2 contains 3, after this instruction, X0 will contain 8.

Answer:

  • Before: X1 = 5, X2 = 3

  • After: X0 = 8 (5 + 3)

The ADD instruction is used for simple addition. It's commonly used in loops, array indexing, and general arithmetic calculations.

SUB (Subtraction)

SUB Xd, Xn, Xm

This instruction subtracts the value in Xm from Xn, storing the result in Xd.

Example:

SUB X0, X1, X2  ; X0 = X1 - X2

Explanation: If X1 contains 10 and X2 contains 7, after this instruction, X0 will contain 3.

Answer:

  • Before: X1 = 10, X2 = 7

  • After: X0 = 3 (10 - 7)

SUB is used for subtraction operations. It's often used in comparison operations, decrementing counters, and calculating differences.

MUL (Multiplication)

MUL Xd, Xn, Xm

This instruction multiplies the values in Xn and Xm, storing the result in Xd.

Example:

MUL X0, X1, X2  ; X0 = X1 * X2

Explanation: If X1 contains 6 and X2 contains 4, after this instruction, X0 will contain 24.

Answer:

  • Before: X1 = 6, X2 = 4

  • After: X0 = 24 (6 * 4)

MUL is used for multiplication. It's common in mathematical computations, scaling values, and implementing algorithms that require multiplication.

SDIV (Signed Division)

SDIV Xd, Xn, Xm

This instruction divides Xn by Xm (treating them as signed integers), storing the quotient in Xd.

Example:

SDIV X0, X1, X2 ; X0 = X1 / X2

Explanation: If X1 contains 15 and X2 contains 3, after this instruction, X0 will contain 5.

Answer:

  • Before: X1 = 15, X2 = 3

  • After: X0 = 5 (15 / 3)

SDIV is used for integer division. It's important to note that this instruction handles signed division, meaning it works correctly with both positive and negative numbers.

Sure, let's add more detailed explanations of the basic concepts for each logical instruction along with the examples.

Let's break down the instructions with simpler and more detailed explanations using small numbers and binary representation to make it clearer.

3.2.2 Logical Instructions

AND Instruction

The AND instruction performs a bitwise AND between two registers. Each bit of the result is 1 if the corresponding bits of both operands are 1; otherwise, it is 0.

Syntax

AND <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

AND X1, X2, X3

This instruction will perform a bitwise AND operation between the values in registers X2 and X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x2 (in binary: 0010, in decimal: 2)

  • X3 = 0x3 (in binary: 0011, in decimal: 3)

Bitwise AND operation:

X2: 0010 (2 in decimal)
X3: 0011 (3 in decimal)
-------------
X1: 0010 (2 in decimal)

Result stored in X1: 0x2 (2 in decimal)

ORR Instruction

The ORR instruction performs a bitwise OR between two registers. Each bit of the result is 1 if the corresponding bit of either operand is 1.

Syntax

ORR <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

ORR X1, X2, X3

This instruction will perform a bitwise OR operation between the values in registers X2 and X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x2 (in binary: 0010, in decimal: 2)

  • X3 = 0x3 (in binary: 0011, in decimal: 3)

Bitwise OR operation:

X2: 0010 (2 in decimal)
X3: 0011 (3 in decimal)
-------------
X1: 0011 (3 in decimal)

Result stored in X1: 0x3 (3 in decimal)

EOR Instruction

The EOR instruction performs a bitwise Exclusive OR (XOR) between two registers. Each bit of the result is 1 if the corresponding bits of the operands are different; otherwise, it is 0.

Syntax

EOR <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

EOR X1, X2, X3

This instruction will perform a bitwise XOR operation between the values in registers X2 and X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x2 (in binary: 0010, in decimal: 2)

  • X3 = 0x3 (in binary: 0011, in decimal: 3)

Bitwise XOR operation:

X2: 0010 (2 in decimal)
X3: 0011 (3 in decimal)
-------------
X1: 0001 (1 in decimal)

Result stored in X1: 0x1 (1 in decimal)

BIC Instruction

The BIC instruction performs a bitwise AND NOT operation between two registers. It clears the bits of the first operand that are set in the second operand.

Syntax

BIC <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

BIC X1, X2, X3

This instruction will clear the bits in X2 that are set in X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x3 (in binary: 0011, in decimal: 3)

  • X3 = 0x2 (in binary: 0010, in decimal: 2)

Bitwise AND NOT operation:

X2: 0011 (3 in decimal)
X3: 0010 (2 in decimal)
-------------
X1: 0001 (1 in decimal)

Result stored in X1: 0x1 (1 in decimal)

ORN Instruction

The ORN instruction performs a bitwise OR NOT operation between two registers.

Syntax

ORN <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

ORN X1, X2, X3

This instruction will perform a bitwise OR operation between X2 and the bitwise NOT of X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x1 (in binary: 0001, in decimal: 1)

  • X3 = 0x2 (in binary: 0010, in decimal: 2)

Bitwise OR NOT operation:

X2: 0001 (1 in decimal)
~X3: 1101 (bitwise NOT of X3, or in decimal: 13)
-------------
X1: 1101 (13 in decimal)

Result stored in X1: 0xD (13 in decimal)

EON Instruction

The EON instruction performs a bitwise Exclusive OR NOT operation between two registers.

Syntax

EON <Rd>, <Rn>, <Rm>
  • Rd: Destination register.

  • Rn: First operand register.

  • Rm: Second operand register.

Example

EON X1, X2, X3

This instruction will perform a bitwise XOR operation between X2 and the bitwise NOT of X3 and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x1 (in binary: 0001, in decimal: 1)

  • X3 = 0x2 (in binary: 0010, in decimal: 2)

Bitwise XOR NOT operation:

X2: 0001 (1 in decimal)
~X3: 1101 (bitwise NOT of X3, or in decimal: 13)
-------------
X1: 1100 (12 in decimal)

Result stored in X1: 0xC (12 in decimal)

3.2.3 Comparison Instructions

Comparison instructions are used to compare the values of two registers. The result of the comparison affects the condition flags (N, Z, C, V), which can be used in subsequent conditional instructions. The main comparison instructions in ARM64 are:

  1. CMP - Compare

  2. CMN - Compare Negative

CMP Instruction

The CMP (Compare) instruction compares the values of two registers by subtracting the second operand from the first operand. It sets the condition flags based on the result, but does not store the result.

Syntax

CMP <Rn>, <Rm>
  • Rn: First operand register.

  • Rm: Second operand register.

Example

CMP X2, X3

This instruction will compare the values in registers X2 and X3 by performing the operation X2 - X3.

Explanation

Let's assume:

  • X2 = 0x5 (5 in decimal)

  • X3 = 0x3 (3 in decimal)

The comparison will perform:

X2 - X3

This is:

5 - 3 = 2

The condition flags are set as follows:

  • N (Negative): 0 (result is not negative)

  • Z (Zero): 0 (result is not zero)

  • C (Carry): 1 (no borrow, means X2 >= X3)

  • V (Overflow): 0 (no overflow)

CMN Instruction

The CMN (Compare Negative) instruction compares the values of two registers by adding the second operand to the first operand. It sets the condition flags based on the result, but does not store the result.

Syntax

CMN <Rn>, <Rm>
  • Rn: First operand register.

  • Rm: Second operand register.

Example

CMN X2, X3

This instruction will compare the values in registers X2 and X3 by performing the operation X2 + X3.

Explanation

Let's assume:

  • X2 = 0x2 (2 in decimal)

  • X3 = 0x3 (3 in decimal)

The comparison will perform:

X2 + X3

This is:

2 + 3 = 5

The condition flags are set as follows:

  • N (Negative): 0 (result is not negative)

  • Z (Zero): 0 (result is not zero)

  • C (Carry): 0 (irrelevant in addition)

  • V (Overflow): 0 (no overflow)

3.2.4 Shift Instructions

Shift instructions are used to shift the bits in a register to the left or right. The main shift instructions in ARM64 are:

  1. LSL - Logical Shift Left

  2. LSR - Logical Shift Right

  3. ASR - Arithmetic Shift Right

LSL Instruction

The LSL (Logical Shift Left) instruction shifts the bits of a register to the left by a specified number of positions. Zeros are shifted into the least significant bits.

Syntax

LSL <Rd>, <Rn>, #<shift>
  • Rd: Destination register.

  • Rn: Source register.

  • shift: Number of positions to shift.

Example

LSL X1, X2, #2

This instruction will shift the bits in register X2 to the left by 2 positions and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x2 (0010 in binary, 2 in decimal)

Logical Shift Left by 2 positions:

X2: 0010 (2 in decimal)
Shift:     00
-------------
X1: 1000 (8 in decimal)

Result stored in X1: 0x8 (8 in decimal)

LSR Instruction

The LSR (Logical Shift Right) instruction shifts the bits of a register to the right by a specified number of positions. Zeros are shifted into the most significant bits.

Syntax

LSR <Rd>, <Rn>, #<shift>
  • Rd: Destination register.

  • Rn: Source register.

  • shift: Number of positions to shift.

Example

LSR X1, X2, #2

This instruction will shift the bits in register X2 to the right by 2 positions and store the result in X1.

Explanation

Let's assume:

  • X2 = 0x8 (1000 in binary, 8 in decimal)

Logical Shift Right by 2 positions:

X2: 1000 (8 in decimal)
Shift: 00
-------------
X1: 0010 (2 in decimal)

Result stored in X1: 0x2 (2 in decimal)

ASR Instruction

The ASR (Arithmetic Shift Right) instruction shifts the bits of a register to the right by a specified number of positions. The most significant bit (sign bit) is replicated into the shifted positions.

Syntax

ASR <Rd>, <Rn>, #<shift>
  • Rd: Destination register.

  • Rn: Source register.

  • shift: Number of positions to shift.

Example

ASR X1, X2, #2

This instruction will shift the bits in register X2 to the right by 2 positions and store the result in X1.

Explanation

Let's assume:

  • X2 = 0xF8 (1111 1000 in binary, -8 in decimal using two's complement for negative numbers)

Arithmetic Shift Right by 2 positions:

X2: 1111 1000 (-8 in decimal)
Shift: 11
-------------
X1: 1111 1110 (-2 in decimal)

Result stored in X1: 0xFE (-2 in decimal)

3.2.5 Rotate Instructions

Rotate instructions are used to rotate the bits in a register around the end of the register. The main rotate instructions in ARM64 are:

  1. ROR - Rotate Right

  2. ROL - Rotate Left (not directly available in ARM64, can be achieved using ROR and appropriate shifts)

ROR Instruction

The ROR (Rotate Right) instruction rotates the bits of a register to the right by a specified number of positions. Bits shifted out of the least significant bit position are rotated back into the most significant bit position.

Syntax

ROR <Rd>, <Rn>, #<shift>
  • Rd: Destination register.

  • Rn: Source register.

  • shift: Number of positions to rotate.

Example

ROR X1, X2, #2

This instruction will rotate the bits in register X2 to the right by 2 positions and store the result in X1.

Explanation

Let's assume:

  • X2 = 0xB (1011 in binary, 11 in decimal)

Rotate Right by 2 positions:

X2: 1011 (11 in decimal)
Rotate: 11
-------------
X1: 1110 (14 in decimal)

Result stored in X1: 0xE (14 in decimal)

ROL Instruction

The ROL (Rotate Left) instruction is not directly available in ARM64, but it can be achieved using the ROR instruction and appropriate shifts. Rotate Left by N positions is equivalent to Rotate Right by (32-N) positions.

Example

LSL X1, X2, #2
LSR X3, X2, #30
ORR X1, X1, X3

This sequence of instructions will rotate the bits in register X2 to the left by 2 positions and store the result in X1.

Explanation

Let's assume:

  • X2 = 0xB (1011 in binary, 11 in decimal)

Rotate Left by 2 positions:

  1. Logical Shift Left by 2 positions:

X2: 1011 (11 in decimal)
Shift: 00
-------------
X1: 101100 (44 in decimal)
  1. Logical Shift Right by 30 positions (equivalent to Rotate Right by 2 positions):

X2: 1011 (11 in decimal)
Shift: 11
-------------
X3: 10 (2 in decimal)
  1. Bitwise OR of X1 and X3:

X1: 101100 (44 in decimal)
X3: 10 (2 in decimal)
-------------
X1: 101110 (46 in decimal)

Result stored in X1: 0x2E (46 in decimal)

1. Practicle Example: data_processing_instructions.s

.global _start
.section .text

_start:
    // Initialize registers
    mov x0, #10         // Set x0 to 10 (binary: 1010)
    mov x1, #3          // Set x1 to 3  (binary: 0011)

    // Arithmetic operations
    add x2, x0, x1      // Addition: x2 = x0 + x1
    sub x3, x0, x1      // Subtraction: x3 = x0 - x1
    mul x4, x0, x1      // Multiplication: x4 = x0 * x1
    udiv x5, x0, x1     // Unsigned Division: x5 = x0 / x1

    // Logical operations
    and x6, x0, x1      // Bitwise AND: x6 = x0 & x1
    orr x7, x0, x1      // Bitwise OR: x7 = x0 | x1
    eor x8, x0, x1      // Bitwise XOR: x8 = x0 ^ x1

    // Shift operations
    lsl x9, x0, #2      // Logical Shift Left: x9 = x0 << 2
    lsr x10, x0, #1     // Logical Shift Right: x10 = x0 >> 1
    asr x11, x0, #1     // Arithmetic Shift Right: x11 = x0 >> 1

    // Rotate operation
    ror x12, x0, #2     // Rotate Right: x12 = ROR(x0, 2)

    // Comparison and Conditional Set
    cmp x0, x1          // Compare x0 and x1
    cset x13, gt        // Set x13 to 1 if x0 > x1, else 0

    // Exit syscall
    mov x8, #93         // Syscall number for exit
    mov x0, #0          // Status code 0
    svc #0              // Invoke syscall

2. Compilation and Debugging Setup

Compile and link:

as data_processing_instructions.s -o data_processing_instructions.o
ld data_processing_instructions.o -o data_processing_instructions

Start gdbserver:

gdbserver :1234 ./data_processing_instructions

Connect with GDB:

gdb
(gdb) file data_processing_instructions
(gdb) target remote localhost:1234
(gdb) break _start
(gdb) continue

3. Detailed Explanation of Each Instruction

  1. mov x0, #10 and mov x1, #3

    • These instructions load immediate values into registers.

    • x0 = 10 (binary: 1010), x1 = 3 (binary: 0011)

  2. add x2, x0, x1

    • Adds the values in x0 and x1, storing the result in x2.

    • Expected result: x2 = 13 (10 + 3)

  3. sub x3, x0, x1

    • Subtracts x1 from x0, storing the result in x3.

    • Expected result: x3 = 7 (10 - 3)

  4. mul x4, x0, x1

    • Multiplies x0 by x1, storing the result in x4.

    • Expected result: x4 = 30 (10 * 3)

  5. udiv x5, x0, x1

    • Performs unsigned division of x0 by x1, storing the result in x5.

    • Expected result: x5 = 3 (10 / 3, integer division)

  6. and x6, x0, x1

    • Performs bitwise AND between x0 and x1.

    • 1010 (x0) AND 0011 (x1) = 0010

    • Expected result: x6 = 2

  7. orr x7, x0, x1

    • Performs bitwise OR between x0 and x1.

    • 1010 (x0) OR 0011 (x1) = 1011

    • Expected result: x7 = 11

  8. eor x8, x0, x1

    • Performs bitwise XOR between x0 and x1.

    • 1010 (x0) XOR 0011 (x1) = 1001

    • Expected result: x8 = 9

  9. lsl x9, x0, #2

    • Logically shifts x0 left by 2 bits.

    • 1010 becomes 101000

    • Expected result: x9 = 40 (10 * 2^2)

  10. lsr x10, x0, #1

    • Logically shifts x0 right by 1 bit.

    • 1010 becomes 0101

    • Expected result: x10 = 5 (10 / 2)

  11. asr x11, x0, #1

    • Arithmetically shifts x0 right by 1 bit.

    • For positive numbers, same as LSR.

    • Expected result: x11 = 5

  12. ror x12, x0, #2

    • Rotates x0 right by 2 bits.

    • 1010 becomes 1010...10 (64-bit rotation)

    • Expected result: x12 = 1073741826 (2^30 + 2)

  13. cmp x0, x1 and cset x13, gt

    • Compares x0 and x1, then sets x13 to 1 if x0 > x1, else 0.

    • Expected result: x13 = 1 (because 10 > 3)

4. Debugging Process

Step through each instruction:

(gdb) stepi
(gdb) info registers x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13

Repeat for each instruction, observing how register values change after each operation.

3.3 System Instructions

System Instructions are special commands that allow a program to interact directly with the processor at a system level. These instructions are essential for:

  • Operating system operations

  • Hardware management

  • Maintaining system integrity

They are like the backstage commands that keep everything running smoothly in a computer system.

3.3.1 Exception Generation

Exception generation instructions intentionally cause the processor to enter an "exception state." This is useful for:

  • System calls (requests to the operating system)

  • Debugging

  • Handling errors

SVC (Supervisor Call)

  • Purpose: To make system calls (requests to the operating system).

  • Syntax: svc #immediate

  • Example:

    svc #0
  • Explanation: When this instruction is executed, it causes the processor to switch to a more privileged execution mode. This allows the operating system to perform tasks that normal user programs cannot do directly, such as accessing hardware or performing critical system functions.

Practical Example:

Imagine you are writing a program that needs to read a file from disk. Reading files directly from the disk is a sensitive operation that requires special privileges. By using the svc #0 instruction, your program requests the operating system to handle the file reading process securely.

BRK (Breakpoint)

  • Purpose: To enter a debugging state.

  • Syntax: brk #immediate

  • Example:

    brk #0
  • Explanation: This instruction pauses program execution and enters a debugger if one is attached. It's like setting a breakpoint in your code, which is a common debugging technique to inspect the state of the program at specific points.

Practical Example:

If you're developing a complex application and want to check the value of variables at a certain point, you can use the brk instruction. When the program execution reaches the brk instruction, it will pause, allowing you to inspect the variables' values.

HVC (Hypervisor Call)

  • Purpose: To call hypervisor functions in virtualized environments.

  • Syntax: hvc #immediate

  • Example:

    hvc #0
  • Explanation: In systems where multiple operating systems run on the same hardware (virtualization), this instruction allows a guest operating system to request services from the hypervisor, which manages the virtual machines.

In a cloud computing environment, multiple virtual machines (VMs) might run on a single physical server. If a VM needs to perform an operation that requires coordination with the hypervisor (like allocating more memory), it uses the hvc instruction to request the hypervisor's assistance.

3.3.2 System Control

System control instructions manage the processor state and system registers, which are special storage locations used to control various aspects of the processor's operation.

MSR (Move to System Register)

  • Purpose: To write a value to a system register.

  • Syntax: msr <system_register>, <source_register>

  • Example:

    msr sctlr_el1, x0
  • Explanation: This instruction writes the value in register x0 to the system control register sctlr_el1, which is used to control the behavior of the processor at Exception Level 1.

Suppose you need to change the configuration of the CPU's memory management. By writing a specific value to the sctlr_el1 register, you can enable or disable certain memory features.

MRS (Move from System Register)

  • Purpose: To read a value from a system register.

  • Syntax: mrs <destination_register>, <system_register>

  • Example:

    mrs x0, sctlr_el1
  • Explanation: This instruction reads the value from the system control register sctlr_el1 and stores it in register x0.

If you want to check the current configuration of the CPU's memory management, you can read the value from the sctlr_el1 register into a general-purpose register like x0 to examine its settings.

3.3.3 Cache Maintenance

Cache maintenance instructions manage the processor's cache, which is a small, fast memory used to speed up access to frequently used data.

DC (Data Cache operation)

  • Purpose: To perform operations on the data cache.

  • Common Variants:

    • dc cvac: Clean data cache by virtual address (writes back data from the cache to main memory).

    • dc ivac: Invalidate data cache by virtual address (marks data in the cache as invalid).

  • Example:

    dc cvac, x0
  • Explanation: This instruction cleans the cache line containing the address in x0, meaning it writes the data back to main memory and ensures consistency.

Practical Example:

When you modify data in memory, the changes are first made in the cache for speed. To ensure that the changes are saved to the main memory, you can use the dc cvac instruction. This is important for data integrity, especially in multi-threaded applications where multiple processes might access the same data.

IC (Instruction Cache operation)

  • Purpose: To perform operations on the instruction cache.

  • Example:

    ic iallu
  • Explanation: This instruction invalidates all entries in the instruction cache, meaning it marks them as outdated so that new instructions must be fetched from memory.

Practical Example:

After updating the code in memory (e.g., loading a new program or self-modifying code), the instruction cache may still hold old instructions. Using ic iallu ensures that the CPU fetches the updated instructions from memory, preventing the execution of outdated code.

3.3.4 TLB (Translation Lookaside Buffer) Maintenance

The TLB is a special cache that speeds up the translation of virtual addresses to physical addresses.

TLBI (TLB Invalidate)

  • Purpose: To invalidate entries in the TLB.

  • Example:

    tlbi vmalle1is
  • Explanation: This instruction invalidates all TLB entries for the current virtual machine at Exception Level 1, forcing the processor to update its address mappings.

When the virtual-to-physical address mappings change (e.g., when a new program is loaded or the memory layout changes), the TLB needs to be updated. Using tlbi vmalle1is ensures that the processor invalidates outdated mappings and fetches the new ones.

3.4 Branching and Condition Codes

3.4.1 Condition Codes

Condition codes are like little flags that our computer uses to remember the result of certain operations. They help us make decisions in our programs. In ARM64, there are four main condition codes:

  1. N (Negative): Indicates if the result of the operation is negative.

  2. Z (Zero): Indicates if the result of the operation is zero.

  3. C (Carry): Indicates if there's a carry out of the most significant bit (useful for unsigned operations).

  4. V (Overflow): Indicates if the operation resulted in an overflow (useful for signed operations).

These flags are stored in the PSTATE (Processor State) register.

How Do We Set These Flags?

We set these flags using special versions of normal instructions. These versions have an 's' at the end. For example:

adds x0, x1, x2   // Add x1 and x2, put the result in x0, and set flags
subs x0, x1, x2   // Subtract x2 from x1, put the result in x0, and set flags

We can also set flags using the compare instruction:

cmp x0, x1        // Compare x0 and x1, setting flags based on the result

A Simple Example

Let's look at a basic example to understand how these flags are set:

File: condition_flags.s

.global _start
.section .text

_start:
    // Initialize registers
    mov x0, #5
    mov x1, #3

    // Perform subtraction and set flags
    subs x2, x0, x1   // x2 = x0 - x1, and set flags

    // Infinite loop to stop execution
    b .

Explanation:

  • Initialize registers: We set x0 to 5 and x1 to 3.

  • Perform subtraction and set flags: The subs instruction subtracts x1 from x0, stores the result in x2, and sets the condition flags based on the result.

  • Infinite loop: The b . instruction creates an infinite loop, causing the program to repeatedly branch to itself. This halts execution so we can inspect the state of the registers and flags in GDB.

Checking Flags in GDB

To see the condition flags in action, we can use GDB:

  1. Assemble and Link the Program:

    as condition_flags.s -o condition_flags.o
    ld condition_flags.o -o condition_flags
  2. Start GDB Server:

    gdbserver :1234 ./condition_flags
  3. Start GDB and Connect to the Server: Open a new terminal and start GDB:

    gdb
  4. Load the Program in GDB:

    (gdb) file condition_flags
  5. Connect to the Remote Target:

    (gdb) target remote localhost:1234
  6. Step Through the Program:

    (gdb) stepi  // Step to 'mov x0, #5'
    (gdb) stepi  // Step to 'mov x1, #3'
    (gdb) stepi  // Step to 'subs x2, x0, x1'
    (gdb) info registers cpsr

    Check the condition flags in the cpsr register.

  1. Check CPSR Register:

(gdb) info registers cpsr

This should show something like:

cpsr           0x20201000          [ EL=0 BTYPE=0 SSBS SS C ]

The CPSR register in GDB will show the condition flags. The condition flags are represented by specific bits in the CPSR register:

  • N: Bit 31

  • Z: Bit 30

  • C: Bit 29

  • V: Bit 28

For example, if the CPSR value is 0x20201000, you can interpret the flags as follows:

  • N (Negative): Bit 31 = 0 (not set)

  • Z (Zero): Bit 30 = 0 (not set)

  • C (Carry): Bit 29 = 1 (set)

  • V (Overflow): Bit 28 = 0 (not set)

Understanding CPSR

The CPSR (Current Program Status Register) contains the condition flags as part of its bits. You can view the CPSR value in GDB and decode the flags from it.

Example:

  1. Check CPSR Register Value:

    (gdb) info registers cpsr
    cpsr           0x20201000          [ EL=0 BTYPE=0 SSBS SS C ]
  2. Binary Representation: Convert the CPSR value to its binary form. For example, 0x20201000 in binary is 0010 0000 0010 0000 0001 0000 0000 0000.

  3. Extract Condition Flags:

    • N (Negative): Bit 31 = 0 (not set)

    • Z (Zero): Bit 30 = 0 (not set)

    • C (Carry): Bit 29 = 1 (set)

    • V (Overflow): Bit 28 = 0 (not set)

3.4.2 Branching

Branching is a fundamental concept in programming that allows the flow of execution to change based on certain conditions. In ARM64 assembly, branching is crucial for implementing control structures like if-else statements, loops, and switch cases. Branching is like making a decision in your code. It's how we tell the computer to do different things based on certain conditions.

Think of it like a fork in a road:

  • If it's sunny, go to the beach.

  • If it's raining, stay home.

In assembly, we use branches to create these decision points.

In ARM64, branching instructions are used to jump to different parts of the program. The main types of branching instructions are:

  1. Unconditional Branch: Always jumps to the specified label.

  2. Conditional Branch: Jumps to the specified label only if certain conditions are met.

1. Unconditional Branch

An unconditional branch always jumps to the specified label, regardless of any condition flags. It is used to transfer control unconditionally to another part of the program.

Example:

.global _start
.section .text

_start:
    mov x0, #1        // Move 1 into register x0
    b target_label    // Unconditionally branch to target_label
    mov x0, #2        // This instruction will not execute

target_label:
    mov x1, #3        // Move 3 into register x1

Explanation:

  • b target_label is an unconditional branch. The program always jumps to target_label.

  • The instruction mov x0, #2 will not execute because the branch instruction b target_label is executed first.

2. Common Conditional Branches:

1. B.eq (branch if equal)

Explanation:

  • B.eq: Jumps to the specified label if the Zero (Z) flag is set.

  • The Zero (Z) flag is set when the result of a comparison is zero, meaning the operands are equal.

Example:

.global _start
.section .text

_start:
    mov x0, #5        // Move 5 into register x0
    mov x1, #5        // Move 5 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.eq equal        // Branch to 'equal' if x0 == x1

    // This part executes if x0 != x1
    mov x2, #0        // Set x2 to 0 (not equal)
    b end             // Unconditionally branch to 'end'

equal:
    mov x2, #1        // Set x2 to 1 (equal)

end:
    b .               // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #5 and mov x1, #5:

    • Set x0 to 5 and x1 to 5.

    • Registers: x0 = 5, x1 = 5

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 equals x1, the Zero (Z) flag is set.

    • Registers: x0 = 5, x1 = 5

    • Flags: Z = 1

  3. b.eq equal:

    • Branches to the equal label if the Zero (Z) flag is set.

    • Since the Z flag is set, the program jumps to the equal label.

  4. equal:

    • The branch is taken, so x2 is set to 1, indicating x0 equals x1.

    • Registers: x2 = 1

  5. end:

    • The program reaches the end label and enters an infinite loop, stopping execution.

    • The program does not execute any further instructions.

2. B.ne (branch if not equal)

Explanation:

  • B.ne: Jumps to the specified label if the Zero (Z) flag is not set.

  • The Zero (Z) flag is not set when the result of a comparison is not zero, meaning the operands are not equal.

Example:

.global _start
.section .text

_start:
    mov x0, #5        // Move 5 into register x0
    mov x1, #3        // Move 3 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.ne not_equal    // Branch to 'not_equal' if x0 != x1

    // This part executes if x0 == x1
    mov x2, #0        // Set x2 to 0 (equal)
    b end             // Unconditionally branch to 'end'

not_equal:
    mov x2, #1        // Set x2 to 1 (not equal)

end:
    b .               // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #5 and mov x1, #3:

    • Set x0 to 5 and x1 to 3.

    • Registers: x0 = 5, x1 = 3

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 does not equal x1, the Zero (Z) flag is not set.

    • Registers: x0 = 5, x1 = 3

    • Flags: Z = 0

  3. b.ne not_equal:

    • Branches to the not_equal label if the Zero (Z) flag is not set.

    • Since the Z flag is not set, the program jumps to the not_equal label.

  4. not_equal:

    • The branch is taken, so x2 is set to 1, indicating x0 does not equal x1.

    • Registers: x2 = 1

  5. end:

    • The program reaches the end label and enters an infinite loop, stopping execution.

    • The program does not execute any further instructions.

3. B.lt (branch if less than)

Explanation:

  • B.lt: Jumps to the specified label if the Negative (N) flag is not equal to the Overflow (V) flag.

  • This indicates that the first operand is less than the second operand in signed comparison.

Example:

.global _start
.section .text

_start:
    mov x0, #3        // Move 3 into register x0
    mov x1, #5        // Move 5 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.lt less_than    // Branch to 'less_than' if x0 < x1

    // This part executes if x0 >= x1
    mov x2, #0        // Set x2 to 0 (not less than)
    b end             // Unconditionally branch to 'end'

less_than:
    mov x2, #1        // Set x2 to 1 (less than)

end:
    b .               // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #3 and mov x1, #5:

    • Set x0 to 3 and x1 to 5.

    • Registers: x0 = 3, x1 = 5

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 is less than x1, the Negative (N) flag is set.

    • Registers: x0 = 3, x1 = 5

    • Flags: N = 1, V = 0

  3. b.lt less_than:

    • Branches to the less_than label if the Negative (N) flag is not equal to the Overflow (V) flag.

    • Since N is not equal to V, the program jumps to the less_than label.

  4. less_than:

    • The branch is taken, so x2 is set to 1, indicating x0 is less than x1.

    • Registers: x2 = 1

  5. end:

    • The program reaches the end label and enters an infinite loop, stopping execution.

    • The program does not execute any further instructions.

4. B.le (branch if less than or equal)

Explanation:

  • B.le: Jumps to the specified label if the Zero (Z) flag is set or the Negative (N) flag is not equal to the Overflow (V) flag.

  • This indicates that the first operand is less than or equal to the second operand in signed comparison.

Example:

.global _start
.section .text

_start:
    mov x0, #3        // Move 3 into register x0
    mov x1, #5        // Move 5 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.le less_or_equal  // Branch to 'less_or_equal' if x0 <= x1

    // This part executes if x0 > x1
    mov x2, #0          // Set x2 to 0 (not less than or equal)
    b end               // Unconditionally branch to 'end'

less_or_equal:
    mov x2, #1          // Set x2 to 1 (less than or equal)

end:
    b .                 // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #3 and mov x1, #5:

    • Set x0 to 3 and x1 to 5.

    • Registers: x0 = 3, x1 = 5

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 is less than x1, the Negative (N) flag is set.

    • Registers: x0 = 3, x1 = 5

    • Flags: N = 1, V = 0

  3. b.le less_or_equal:

    • Branches to the less_or_equal label if the Zero (Z) flag is set or the Negative (N) flag is not equal to the Overflow (V) flag.

    • Since N is not equal to V, the program jumps to

the less_or_equal label. 4. less_or_equal: - The branch is taken, so x2 is set to 1, indicating x0 is less than or equal to x1. - Registers: x2 = 1 5. end: - The program reaches the end label and enters an infinite loop, stopping execution. - The program does not execute any further instructions.

5. B.gt (branch if greater than)

Explanation:

  • B.gt: Jumps to the specified label if the Zero (Z) flag is not set and the Negative (N) flag is equal to the Overflow (V) flag.

  • This indicates that the first operand is greater than the second operand in signed comparison.

Example:

.global _start
.section .text

_start:
    mov x0, #5        // Move 5 into register x0
    mov x1, #3        // Move 3 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.gt greater_than // Branch to 'greater_than' if x0 > x1

    // This part executes if x0 <= x1
    mov x2, #0        // Set x2 to 0 (not greater than)
    b end             // Unconditionally branch to 'end'

greater_than:
    mov x2, #1        // Set x2 to 1 (greater than)

end:
    b .               // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #5 and mov x1, #3:

    • Set x0 to 5 and x1 to 3.

    • Registers: x0 = 5, x1 = 3

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 is greater than x1, the Zero (Z) flag is not set and the Negative (N) flag is equal to the Overflow (V) flag.

    • Registers: x0 = 5, x1 = 3

    • Flags: Z = 0, N = 0, V = 0

  3. b.gt greater_than:

    • Branches to the greater_than label if the Zero (Z) flag is not set and the Negative (N) flag is equal to the Overflow (V) flag.

    • Since Z is 0 and N is equal to V, the program jumps to the greater_than label.

  4. greater_than:

    • The branch is taken, so x2 is set to 1, indicating x0 is greater than x1.

    • Registers: x2 = 1

  5. end:

    • The program reaches the end label and enters an infinite loop, stopping execution.

    • The program does not execute any further instructions.

6. B.ge (branch if greater than or equal)

Explanation:

  • B.ge: Jumps to the specified label if the Negative (N) flag is equal to the Overflow (V) flag.

  • This indicates that the first operand is greater than or equal to the second operand in signed comparison.

Example:

.global _start
.section .text

_start:
    mov x0, #5        // Move 5 into register x0
    mov x1, #3        // Move 3 into register x1

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.ge greater_or_equal // Branch to 'greater_or_equal' if x0 >= x1

    // This part executes if x0 < x1
    mov x2, #0            // Set x2 to 0 (not greater than or equal)
    b end                 // Unconditionally branch to 'end'

greater_or_equal:
    mov x2, #1            // Set x2 to 1 (greater than or equal)

end:
    b .                   // Infinite loop to stop execution

Detailed Explanation:

  1. mov x0, #5 and mov x1, #3:

    • Set x0 to 5 and x1 to 3.

    • Registers: x0 = 5, x1 = 3

  2. cmp x0, x1:

    • Compares x0 and x1. Since x0 is greater than x1, the Negative (N) flag is equal to the Overflow (V) flag.

    • Registers: x0 = 5, x1 = 3

    • Flags: N = 0, V = 0

  3. b.ge greater_or_equal:

    • Branches to the greater_or_equal label if the Negative (N) flag is equal to the Overflow (V) flag.

    • Since N is equal to V, the program jumps to the greater_or_equal label.

  4. greater_or_equal:

    • The branch is taken, so x2 is set to 1, indicating x0 is greater than or equal to x1.

    • Registers: x2 = 1

  5. end:

    • The program reaches the end label and enters an infinite loop, stopping execution.

    • The program does not execute any further instructions.

Example Program: branching_example.s

.global _start
.section .text

_start:
    // Initialize registers
    mov x0, #5        // Set x0 to 5
    mov x1, #5        // Set x1 to 5

    cmp x0, x1        // Compare x0 and x1, setting condition flags

    b.eq equal        // Branch to 'equal' if x0 == x1

    // This part executes if x0 != x1
    mov x2, #0        // Set x2 to 0 (x0 != x1)
    b end             // Branch to 'end'

equal:
    mov x2, #1        // Set x2 to 1 (x0 == x1)

    // Modify x0 and x1 to test other conditions
    mov x0, #3        // Set x0 to 3
    mov x1, #5        // Set x1 to 5

    cmp x0, x1        // Compare x0 and x1, setting condition flags
    b.lt less_than    // Branch to 'less_than' if x0 < x1

    mov x3, #0        // Set x3 to 0 (x0 >= x1)
    b end             // Branch to 'end'

less_than:
    mov x3, #1        // Set x3 to 1 (x0 < x1)

    // Modify x0 and x1 to test another condition
    mov x0, #10       // Set x0 to 10
    mov x1, #5        // Set x1 to 5

    cmp x0, x1        // Compare x0 and x1, setting condition flags
    b.gt greater_than // Branch to 'greater_than' if x0 > x1

    mov x4, #0        // Set x4 to 0 (x0 <= x1)
    b end             // Branch to 'end'

greater_than:
    mov x4, #1        // Set x4 to 1 (x0 > x1)

end:
    b .               // Infinite loop to stop execution

Steps to Compile, Run, and Debug

  1. Save the program: Save the above code to a file named branching_example.s.

  2. Assemble the program:

    as branching_example.s -o branching_example.o
  3. Link the program:

    ld branching_example.o -o branching_example
  4. Start GDB Server:

    gdbserver :1234 ./branching_example

  5. Start GDB and Connect to the Server: Open a new terminal and start GDB:

    gdb
  6. Load the Program in GDB:

    (gdb) file branching_example
  7. Connect to the Remote Target:

    (gdb) target remote localhost:1234

Step-by-Step Debugging with Explanations

  1. Initial Setup:

    (gdb) stepi  # Step into 'mov x0, #5'
    (gdb) info registers x0

    Registers: x0 = 5

  2. Setting x1:

    (gdb) stepi  # Step into 'mov x1, #5'
    (gdb) info registers x1

    Registers: x1 = 5

  3. Compare x0 and x1:

    (gdb) stepi  # Step into 'cmp x0, x1'
    (gdb) info registers cpsr
    • Explanation: Compares x0 and x1. Since x0 equals x1, the Zero (Z) flag is set.

    • Registers: x0 = 5, x1 = 5

    • Flags: Z = 1

  4. Branch if Equal:

    (gdb) stepi  # Step into 'b.eq equal'
    • Explanation: Branches to the equal label if the Zero (Z) flag is set.

    • Since the Z flag is set (Z = 1), the program jumps to the equal label.

  5. Setting x2 at the equal label:

    (gdb) stepi  # Step into 'mov x2, #1'
    (gdb) info registers x2
    • Explanation: The branch is taken, so x2 is set to 1, indicating x0 equals x1.

    • Registers: x2 = 1

  6. Modify x0 and x1 for less than comparison:

    (gdb) stepi  # Step into 'mov x0, #3'
    (gdb) stepi  # Step into 'mov x1, #5'
    (gdb) info registers x0 x1

    Registers: x0 = 3, x1 = 5

  7. Compare x0 and x1 for less than:

    (gdb) stepi  # Step into 'cmp x0, x1'
    (gdb) info registers cpsr
    • Explanation: Compares x0 and x1. Since x0 is less than x1, the Negative (N) flag is set.

    • Registers: x0 = 3, x1 = 5

    • Flags: N = 1, V = 0

  8. Branch if Less Than:

    (gdb) stepi  # Step into 'b.lt less_than'
    • Explanation: Branches to the less_than label if the Negative (N) flag is set.

    • Since N is set, the program jumps to the less_than label.

  9. Setting x3 at the less_than label:

    (gdb) stepi  # Step into 'mov x3, #1'
    (gdb) info registers x3
    • Explanation: The branch is taken, so x3 is set to 1, indicating x0 is less than x1.

    • Registers: x3 = 1

  10. Modify x0 and x1 for greater than comparison:

    (gdb) stepi  # Step into 'mov x0, #10'
    (gdb) stepi  # Step into 'mov x1, #5'
    (gdb) info registers x0 x1

    Registers: x0 = 10, x1 = 5

  11. Compare x0 and x1 for greater than:

    (gdb) stepi  # Step into 'cmp x0, x1'
    (gdb) info registers cpsr
    • Explanation: Compares x0 and x1. Since x0 is greater than x1, the Zero (Z) flag is not set and the Negative (N) flag is clear.

    • Registers: x0 = 10, x1 = 5

    • Flags: Z = 0, N = 0, V = 0

  12. Branch if Greater Than:

    (gdb) stepi  # Step into 'b.gt greater_than'
    • Explanation: Branches to the greater_than label if the Zero (Z) flag is not set and the Negative (N) flag is equal to the Overflow (V) flag.

    • Since Z is 0 and N is equal to V, the program jumps to the greater_than label.

  13. Setting x4 at the greater_than label:

    (gdb) stepi  # Step into 'mov x4, #1'
    (gdb) info registers x4
    • Explanation: The branch is taken, so x4 is set to 1, indicating x0 is greater than x1.

    • Registers: x4 = 1

  14. End and Infinite Loop:

    (gdb) stepi  # Step into 'end'
    (gdb) stepi  # Infinite loop at 'b .'

Summary of the Program Execution:

  1. Initial Comparison:

    • x0 = 5, x1 = 5

    • `cmp x0, x

1setsZ = 1 - Branches tolabel_equal, sets x2 = 1`

  1. Less Than Comparison:

    • x0 = 3, x1 = 5

    • cmp x0, x1 sets N = 1

    • Branches to label_less_than, sets x3 = 1

  2. Greater Than Comparison:

    • x0 = 10, x1 = 5

    • cmp x0, x1 sets Z = 0, N = 0, V = 0

    • Branches to label_greater_than, sets x4 = 1

  3. End:

    • The program reaches the end label and enters an infinite loop.

Last updated