© Stephen Smith 2019
S. SmithRaspberry Pi Assembly Language Programminghttps://doi.org/10.1007/978-1-4842-5287-1_2

2. Loading and Adding

Stephen Smith1 
(1)
Gibsons, BC, Canada
 

In this chapter, we will go slowly through the MOV and ADD instructions to lay the groundwork on how they work, especially in the way they handle parameters (operands). So, in the following chapters, we can proceed at a faster pace, as we encounter the rest of the ARM instruction set.

Before getting into the MOV and ADD instructions, we will discuss the representation of negative numbers and the concepts of shifting and rotating bits.

Negative Numbers

In the previous chapter, we discussed how computers represent positive integers as binary numbers, called unsigned integers, but what about negative numbers? Our first thought might be to make 1 bit represent whether the number is positive or negative. This is simple, but it turns out it requires extra logic to implement, since now the CPU must look at the sign bits, then decide whether to add or subtract and in which order.

About Two’s Complement

The great mathematician John von Neumann, of the Manhattan Project, came up with the idea of the two’s complement representation for negative numbers, in 1945, when working on the Electronic Discrete Variable Automatic Computer (EDVAC)—one of the earliest electronic computers.

Consider a 1-byte hexadecimal number like 01. If we add
  • 0x01 + 0xFF = 0x100

(all binary ones), we get 0x100.

However, if we are limited to 1-byte numbers, then the 1 is lost and we are left with 00:
  • 0x01 + 0xFF = 0x00

The mathematical definition of a number’s negative is a number that when added to it makes zero; therefore, mathematically, FF is –1. You can get the two’s complement form for any number by taking
  • 2N – number

In our example, the two’s complement of 1 is
  • 28 – 1 = 256 – 1 = 255 = 0xFF

This is why it’s called two’s complement. An easier way to calculate the two’s complement is to change all the 1s to 0s and all the 0s to 1s and then add 1. If we do that to 1, we get
  • 0xFE + 1 = 0xFF

Two’s complement is an interesting mathematical oddity for integers that are limited to having a maximum value of one less than a power of two (which is all computer representations of integers).

Why would we want to represent negative integers this way on computers? As it turns out, addition is simple for the computer to execute. There are no special cases; if you discard the overflow, everything works out. This means less circuitry is required to perform the addition, and as a result it can be performed faster. Besides handling the signs correctly, this also results in the CPU using the same addition logic for signed and unsigned arithmetic, another circuitry saving measure. Consider
  • 5 + –3

3 in 1 byte is 0x03 or 0000 0011.

Inverting the bits is
  • 1111 1100

Add 1 to get
  • 1111 1101 = 0xFD

Now add
  • 5 + 0xFD = 0x102 = 2

Since we are limited to 1 byte or 8 bits.

About Gnome Programmer’s Calculator

Fortunately, we have computers to do the conversions and arithmetic for us, but when we see signed numbers in memory, we need to recognize what they are. The Gnome programmer’s calculator can calculate two’s complement for you. Figure 2-1 shows the Gnome calculator representing –3.

Note

The Gnome programmer’s calculator uses 64-bit representations.

../images/486919_1_En_2_Chapter/486919_1_En_2_Fig1_HTML.jpg
Figure 2-1

The Gnome calculator calculating the two’s complement of 3

About One’s Complement

If we don’t add 1 and just change all the 1s to 0s and vice versa, then this is called one’s complement . There are uses for the one’s complement form, and we will encounter it in how some instructions process their operands.

Big vs. Little-endian

At the end of Chapter 1, “Getting Started,” we saw that the words of our compiled program had their bytes stored in the reverse order to what we might expect they should be stored as. In fact, if we look at a 32-bit representation of 1 stored in memory, it is
  • 01 00 00 00

rather than
  • 00 00 00 01

Most processors pick one format or the other to store numbers. Motorola and IBM mainframes use what is called big-endian, where numbers are stored in the order of most significant digit to least significant digit, in this case
  • 00 00 00 01

Intel processors use little-endian format and store the numbers in reverse order with the least significant digit first, namely:
  • 01 00 00 00

Figure 2-2 shows how the bytes in integers are copied into memory in both little- and big-endian formats. Notice how the bytes end up in the reverse order to each other.
../images/486919_1_En_2_Chapter/486919_1_En_2_Fig2_HTML.png
Figure 2-2

How integers are stored in memory in Little vs. big-endian format

About Bi-endian

The ARM CPU is called bi-endian , because it can do either. There is a program status flag in the CPSR that says which endianness to use. We’ll look at all the bits in the CPSR a bit later. By default, Raspbian and your programs use little-endian like Intel processors. You can change this if you want to. We’ll look at an application of changing this flag in a later chapter.

Pros of Little-endian

The advantage of little-endian format is that it makes it easy to change the size of integers, without requiring any address arithmetic. If you want to convert a 4-byte integer to a 1-byte integer, you take the first byte. Assuming the integer is in the range of 0–255, and the other 3 bytes are zero.

For example, if memory contains the 4 byte or word representation for 1, in little-endian, the memory contains
  • 01 00 00 00

If we want the 1-byte representation of this number, we take the first byte; for the 16-bit representation, we take the first 2 bytes. The key point is that the memory address we use is the same in call cases, saving us an instruction cycle adjusting it.

When we are in the debugger, we will see more representations, and these will be pointed out again as we run into them.

Note

Even though Raspbian uses little-endian, many protocols like TCP/IP used on the Internet use big-endian and so require a transformation when moving data from the Raspberry Pi to the outside world.

Shifting and Rotating

We have 16 32-bit registers, and much of programming consists of manipulating the bits in these registers. Two extremely useful bit manipulations are shifting and rotating. Mathematically shifting all the bits left one spot is the same as multiplying by 2, and generally shifting n bits is equivalent to multiplying by 2n. Conversely, shifting bits to the right by n bits is equivalent to dividing by 2n.

For example, consider shifting the number 3 left by 4 bits:
  • 0000 0011    (the binary representation of the number 3)

Shift the bits left by 4 bits and we get
  • 0011 0000

which is
  • 0x30 = 3 ∗ 16 = 3 ∗ 24

Now if we shift 0x30 right by 4 bits, we undo what we just did and see how it is equivalent to dividing by 24.

About Carry Flag

In the CPSR, there is a bit for carry . This is normally used to perform addition on larger numbers. If you add two 32-bit numbers and the result is larger than 32 bits, the carry flag is set. We’ll see how to use this when we look at addition in detail later in this chapter. When we shift and rotate, it turns out to be useful to include the carry flag. This means we can do a conditional logic based on the last bit shifted out of the register.

About the Barrel Shifter

The ARM processor has circuitry for shifting, called a barrel shifter , but there aren’t any native instructions for shifting or rotating bits; rather, it is done as a side effect from other instructions like the MOV instruction that we are about to cover. The reason for this is that the barrel shifter is outside the Arithmetic Logic Unit (ALU) and instead is part of the circuitry that loads the second operand to an instruction. We’ll see this in action when we cover Operand2 for the MOV instruction. Figure 2-3 shows the location of the barrel shifter in relation to the ALU.
../images/486919_1_En_2_Chapter/486919_1_En_2_Fig3_HTML.jpg
Figure 2-3

The location of the barrel shifter to perform shifts as part of loading Operand2

Basics of Shifting and Rotating

We have five cases to cover, as follows:
  1. 1.

    Logical shift left

     
  2. 2.

    Logical shift right

     
  3. 3.

    Arithmetic shift right

     
  4. 4.

    Rotate right

     
  5. 5.

    Rotate right extend

     

Logical Shift Left

This is quite straightforward, as we shift the bits left by the indicated number of places, and zeros come in from the right. The last bit shifted out ends up in the carry flag.

Logical Shift Right

Equally easy, here we shift the bits right, zeros come in from the left, and the last bit shifted out ends up in the carry flag.

Arithmetic Shift Right

The problem with logical shift right is, if it is a negative number, having a zero come in from the left suddenly turns the number positive. If we want to preserve the sign bit, use arithmetic shift right. Here a 1 comes in from the left, if the number is negative, and a 0 if it is positive. This is then the correct form if you are shifting signed integers.

Rotate Right

Rotating is like shifting, except the bits don’t go off the end; instead, they wrap around and reappear from the other side. So, rotate right shifts right, but the bits that leave on the right, reappear on the left.

Rotate Right Extend

Rotate right extend behaves like rotate right, except it treats the register as a 33-bit register, where the carry flag is the 33rd bit and is to the right of bit 0. This type of rotate is limited to moving 1 bit at a time; therefore, the number of bits is not specified on the instruction.

MOV/MVN

In this section, we are going to look at several forms of the MOV instruction:
  1. 1.

    MOV RD, #imm16

     
  2. 2.

    MOVT RD, #imm16

     
  3. 3.

    MOV RD, RS

     
  4. 4.

    MOV RD, operand2

     
  5. 5.

    MVN RD, operand2

     

We’ve seen examples of the first case, putting a small number into a register. Here the immediate value can be any 16-bit quantity, and it will be placed in the lower 16 bits of the specified register. This form of the MOV instruction is as simple as you can get; therefore, we will use it frequently.

About MOVT

The second form answers our question of how to load the full 32 bits of a register. MOVT , the move top instruction, loads the 16-bit immediate operand into the upper 16 bits of the register without disturbing the bottom 16 bits. Suppose we want to load register R2 with the hex value 0x4F5D6E3A. We could use
      MOV   R2, #0x6E3A
      MOVT  R2, #0x4F5D

Only two instructions, so not too painful, but a bit annoying.

Register to Register MOV

In the next case 3, we have a version that moves one register into another that sounds useful.

The Dreaded Flexible Operand2

All the ARM’s data processing instructions have the option of taking a flexible Operand2 as one of their parameters. At this point, it won’t be clear why you want some of this functionality, but as we encounter more instructions and start to build small programs, we’ll see how they help us. At the bit level, there is a lot of complexity here, but the people who designed the Assembler did a good job of providing syntax to hide a lot of this from us. Still, when doing Assembly programming, it’s good to always know what is going on under the covers.

There are two formats for Operand2:
  1. 1.

    A register and a shift

     
  2. 2.

    A small number and a rotation

     

Operand2 is processed via the barrel shifter, it’s just a matter of what is shifted and by how much.

Register and Shift

First, you can specify a register and a shift. For this you specify a register that takes 4 bits and then a shift that is 5 bits (for a total of a full 32-bit shift). For example:
      MOV   R1, R2, LSL #1      @ Logical shift left
is how we specify to take R2, logically shift it left by 1 bit, and put the result in R1. We can then handle the other shift and rotate scenarios we mentioned earlier with
      MOV   R1, R2, LSR #1      @ Logical shift right
      MOV   R1, R2, ASR #1      @Arithmetic shift right
      MOV   R1, R2, ROR #1      @ Rotate right
      MOV   R1, R2, RRX         @ Rotate extended right
Since shifting and rotating are quite common, the Assembler provides mnemonics for these, so you can specify
      LSL   R1, R2, #1      @ Logical shift left
      LSR   R1, R2, #1      @ Logical shift right
      ASR   R1, R2, #1      @Arithmetic shift right
      ROR   R1, R2, #1      @ Rotate right
      RRX   R1, R2          @ Rotate extended right

These assemble to the same byte code. The intent is that it makes the code a little more readable, since it is clear you are doing a shift or rotate operation and not just loading a register.

Small Number and Rotation

Secondly, the other form of operand2 consists of a small number, namely, an 8-bit (1-byte) quantity that can be rotated through an even number of positions, such as RORs of 0, 2, 4, 8, ..., 30. This uses up the 12 bits we have for operand2, 8 for the number and 4 for the rotation. The values we get are like this:
  • 0 - 255 [0 - 0xff]

  • 256,260,264,..,1020 [0x100-0x3fc, step 4, 0x40-0xff ror 30]

  • 1024,1040,1056,..,4080 [0x400-0xff0, step 16, 0x40-0xff ror 28]

  • 4096,4160, 4224,..,16320 [0x1000-0x3fc0, step 64, 0x40-0xff ror 26]

This is quite a clever scheme, as it lets you represent any power of 2 from 0 to 31, so you can set any individual bit in a register. It also lets you set any individual byte in a register. These turn out to be quite frequent scenarios, and you can specify it as part of most data processing instructions.

Fortunately, we don’t need to figure this all out. We just specify a number and the Assembler figures out how to represent it. Since there are only 12 bits, not all 32-bit numbers can be represented, so if you specify something that can’t be dealt with, then the Assembler gives you an error message. You then need to use a MOV/MOVT pair as outlined previously.

MOV has the advantage that it can take an #imm16 operand, which can usually get us out of trouble. However, other instructions that must specify a third register, like the ADD instruction, don’t have this luxury.

Frequently, programmers deal with small integers like loop indexes, say to loop from 1 to 10. These simple cases are handled easily, and we don’t need to be concerned.
      @ Too big for #imm16
      MOV   R1, #0xAB000000
      @ Too big for #imm16 and can't      be represented.
      MOV   R1, #0xABCDEF11
The second instruction gives the error
Error: invalid constant (abcdef11) after fixup

when you run your program through the Assembler. This means the Assembler tried all its tricks and failed to represent the number. To load this, you need to use an MOV/MOVT pair.

MVN

This is the Move Not instruction. It works just like MOV, except it reverses all the 1s and 0s as it loads the register. This means it loads the register with the one’s complement form of what you specified. Another way to say it is that it applies a logical NOT operation to each bit in the word you are loading into the register.

MVT is a distinct opcode and not an alias for another instruction with cryptic parameters. The ARM32 instruction set only has 16 opcodes, so this is an important instruction with three main uses:
  1. 1.

    To calculate the one’s complement of something for you. This has its uses, but does it warrant its own opcode?

     
  2. 2.

    Multiply by –1. We saw that with the shift operations we can multiply or divide by powers of 2. This instruction gets us halfway to multiplying by –1. Remember that the negative of a number is the two’s complement of the number or the one’s complement plus one. This means we can multiply by –1 by doing this instruction, then add one. Why would we do this rather than use the Multiply (MUL) instruction? The same for shifting, why do that rather than using MUL? The answer is that the MUL instruction is quite slow and can take quite a few clock cycles to do its work. Shifting only takes one cycle, and using MVN and ADD, we can multiply by –1 in only two clock cycles. Multiplying by –1 is very common, and now we can do it quickly.

     
  3. 3.

    You get twice the number of values due to the extra bit—13 vs. 12. It turns out that all the numbers obtained by using a byte value and even shift are different for MVN and MOV. This means that if the Assembler sees that the number you specified can’t be represented in a MOV instruction, then it tries to change it to an MVN instruction and vice versa. So, you really have 13 bits of immediate data, rather than 12. NOTE: It still might not be able to represent your number, and you may still need to use a MOV/MOVT pair .

     

MOV Examples

In this section, we will write a short program to exercise all the MOV instructions. Create a file called
movexamps.s
containing Listing 2-1.
@
@ Examples of the MOV instruction.
@
.global _start         @ Provide program starting address
@ Load R2 with 0x4F5D6E3A first using MOV and MOVT
_start:    MOV  R2, #0x6E3A
      MOVT R2, #0x4F5D
@ Just move R2 into R1
      MOV  R1, R2
@ Now let’s see all the shift versions of MOV
      MOV   R1, R2, LSL #1   @ Logical shift left
      MOV   R1, R2, LSR #1   @ Logical shift right
      MOV   R1, R2, ASR #1   @Arithmetic shift right
      MOV   R1, R2, ROR #1   @ Rotate right
      MOV   R1, R2, RRX      @ Rotate extended right
@ Repeat the above shifts using
@      the Assembler mnemonics.
      LSL   R1, R2, #1       @ Logical shift left
      LSR   R1, R2, #1       @ Logical shift right
      ASR   R1, R2, #1       @Arithmetic shift right
      ROR   R1, R2, #1       @ Rotate right
      RRX   R1, R2           @ Rotate extended right
@ Example that works with 8 bit immediate and shift
      MOV   R1, #0xAB000000  @ Too big for #imm16
@ Example that can't be represented and
@      results in an error
@ Uncomment the instruction if you want to
@      see the error
@     MOV   R1, #0xABCDEF11  @ Too big for #imm16
@ Example of MVN
      MVN   R1, #45
@ Example of a MOV that the Assembler will
@      change to MVN
      MOV   R1, #0xFFFFFFFE  @ (-2)
@ Set up the parameters to exit the program
@ and then call Linux to do it.
      Mov   R0, #0      @ Use 0 return code
       mov  R7, #1      @ Service command code 1
       svc     0         @ Call      Linux to terminate
Listing 2-1

MOV examples

You can compile this program with the build file
as -o movexamps.o movexamps.s
ld -o movexamps movexamps.o

You can run the program after building it.

Note

This program doesn’t do anything besides move various numbers into registers.

We will look at how to see what is going on in Chapter 3, “Tooling Up,” when we cover the GNU Debugger (GDB).

If we disassemble the program using
objdump -s -d movexamps.o
we get Listing 2-2.
Disassembly of section .text:
00000000 <_start>:
   0: e3062e3a   movw  r2, #28218  ; 0x6e3a
   4: e3442f5d   movt  r2, #20317  ; 0x4f5d
   8: e1a01002   mov   r1, r2
   c: e1a01082   lsl   r1, r2, #1
  10: e1a010a2   lsr   r1, r2, #1
  14: e1a010c2   asr   r1, r2, #1
  18: e1a010e2   ror   r1, r2, #1
  1c: e1a01062   rrx   r1, r2
  20: e1a01082   lsl   r1, r2, #1
  24: e1a010a2   lsr   r1, r2, #1
  28: e1a010c2   asr   r1, r2, #1
  2c: e1a010e2   ror   r1, r2, #1
  30: e1a01062   rrx   r1, r2
  34: e3a014ab   mov   r1, #-1426063360 ; 0xab000000
  38: e3e0102d   mvn   r1, #45    ; 0x2d
  3c: e3e01001   mvn   r1, #1
  40: e3a00000   mov   r0, #0
  44: e3a07001   mov   r7, #1
  48: ef000000   svc   0x00000000
Listing 2-2

Disassembly of the MOV examples

All the instructions start with
  • 0xe

that means to always execute the instruction.

Most of the remaining instructions have
  • 0x1a

as their next digits. The first 3 bits are for instruction format and are 0 meaning
  • Register

  • Register

  • Immediate

We then have the 4 bits for the opcode. All the MOV instruction variants have this as
  • 1101

the opcode for MOV. We see all the shift operations are really MOV instructions, and the computer is trying to be helpful by letting us know what the instruction does. The MVN instruction has an opcode of
  • 1111

This includes the MVN we put in our source file and the MOV instruction that the Assembler changed to MVN so it could load –2.

The first two instructions that load 16-bit operands are different. Notice that the Assembler changed our first MOV into a Move Wide (MOVW) instruction. These aren’t part of the data processing instructions we are looking at now, and are special cases, but they are handy.

ADD/ADC

We can now put any value we like in a register, so let’s start doing some computing. Let’s start with addition. The instructions we will cover are
  1. 1.

    ADD{S} Rd, Rs, Operand2

     
  2. 2.

    ADD{S} Rd, Rs, #imm12

     
  3. 3.

    ADD{S} Rd, Rs1, Rs2

     
  4. 4.

    ADC{S} Rd, Rs, Operand2

     
  5. 5.

    ADC{S} Rd, Rs1, Rs2

     
These instructions all add their second and third parameters and put the result in their first parameter Register Destination (Rd) . We already know about the following:
  • Registers

  • Operand2

  • #imm12

Pushing through that stuff with the MOV instructions was tough, but it’s done. The case with three registers is a special case of Operand2, just with a shift of 0 applied. The registers Rd and Source Register (Rs) can be the same. If you just want to add 1 to R1, you can specify
ADD R1, #1
The Assembler compiles this as
ADD R1, R1, #1

This saves some typing and is a bit clearer. This is a common scenario to increment loop counters.

We haven’t developed the code to print out a number yet, as we must first convert the number to an ASCII string. We will get to this after we cover loops and conditional statements . In the meantime, we can get one number from our program via the program’s return code. This is a 1-byte unsigned integer. Let’s look at an example of multiplying a number by –1 and see the output. Listing 2-3 is the code to do this.
@
@ Example of the ADD/ADC instructions.
@
.global _start       @ Provide program starting address
@ Multiply 2 by –1 by using MVN and then adding 1
_start: MVN     R0, #2
     ADD   R0, #1
@ Set up the parameters to exit the program
@ and then call Linux to do it.
@ R0 is the return code and will be what we
@ calculated above.
      mov     R7, #1      @ Service command code 1
      svc     0           @ Call      Linux to terminate
Listing 2-3

An example of MVN and ADD

Here we use the MVN instruction to calculate the one’s complement of our number, in this case 2, then we add 1 to get the two’s complement form. We use R0 since this will be the return code returned via the Linux terminate command. To see the return code, type
      echo $?

after running the program and it prints out 254. If you examine the bits, you will see this is the two’s complement form for –2 in 1 byte.

Add with Carry

The new concepts in this section are what the {S} after the instruction means along with why we have both ADD and ADC. This will be our first use of the CPSR.

Think back to how we learned to add numbers:
 17
+78
 95
  1. 1.

    We first add 7 + 8 and get 15.

     
  2. 2.

    We put 5 in our sum and carry the 1 to the tens column.

     
  3. 3.

    Now we add 1 + 7 + the carry from the ones column, so we add 1+7+1 and get 9 for the tens column.

     

This is the idea behind the carry flag. When an addition overflows, it sets the carry flag, so we can include that in the sum of the next part. NOTE: A carry is always 0 or 1, so we only need a 1-bit flag for this.

The ARM processor adds 32 bits at a time, so we only need the carry flag if we are dealing with numbers larger than will fit into 32 bits. This means that even though we are in 32-bit mode, we can easily add 64-bit or even larger integers.

In Chapter 1, “Getting Started,” we quickly mentioned that bit 20 in the instruction format specifies whether an instruction alters the CPSR. So far, we haven’t set that bit, so none of the instructions we’ve written so far will alter the CPSR. If we want an instruction to alter the CPSR, then we place an “S” on the end of the opcode, and the Assembler will set bit 20 when it builds binary version of the instruction. This applies to all instructions, including the MOV instructions we just looked at.
ADDS R0, #1
is just like
ADD R0, #1

except that it sets various bits in the CPSR. We’ll cover all the bits when we cover conditional statements . For now, we are interested in the carry flag that is designated C. If the result of an addition is too large, then the C flag is set to 1; otherwise, it is set to 0.

To add two 64-bit integers, use two registers to hold each number. In our example, we’ll use registers R2 and R3 for the first number, R4 and R5 for the second, and then R0 and R1 for the result. The code would then be
      ADDS  R1, R3, R5   @ Lower order word
      ADC   R0, R2, R4   @ Higher order word

The first ADDS adds the lower-order 32 bits and sets the carry flag if needed. It might set other flags in the CPSR, but we’ll worry about those later. The second instruction, ADDC, adds the higher-order words, plus the carry flag.

The nice thing here is that although we are in 32-bit mode, we can still do a 64-bit addition in only two clock cycles. Let’s look at a simple complete example in Listing 2-4.
@
@ Example of 64-bit addition with
@      the ADD/ADC instructions.
@
.global _start       @ Provide program starting address
@ Load the registers with some data
@ First 64-bit number is 0x00000003FFFFFFFF
_start:    MOV   R2, #0x00000003
      MOV  R3, #0xFFFFFFFF      @ as      will change to MVN
@ Second 64-bit number is 0x0000000500000001
      MOV  R4, #0x00000005
      MOV  R5, #0x00000001
      ADDS R1, R3, R5      @ Lower order word
      ADC  R0, R2, R4      @ Higher order word
@ Set up the parameters to exit the program
@ and then call Linux to do it.
@ R0 is the return code and will be what we
@ calculated above.
        mov     R7, #1      @ Service command code 1
        svc     0           @ Call      Linux to terminate
Listing 2-4

Example of 64-bit addition with ADD and ADC

Here we are adding
00000003 FFFFFFFF
00000005 00000001
00000009 00000000
We’ve rigged this example to demonstrate the carry flag and to produce an answer we can see in the return code. The largest unsigned integer is
  • 0xFFFFFFFF

and adding 1 results in
  • 0x100000000

that doesn’t fit in 32 bits, so we get
  • 0x00000000

with a carry. The high-order words add 3 + 5 + carry to yield 9. The high-order word is in R0, so it is the return code when the program exits. If we type
echo $?

we get 9 as expected.

Learning about MOV was difficult, because this was the first time; we encountered both shifting and Operand2. With these behind us, learning about ADD was much easier. We still have some complicated topics to cover, but as we become more experienced with how to manipulate bits and bytes, the learning should become easier.

Summary

In this chapter, we learned how negative integers are represented in a computer. We went on to discuss big vs. little-endian byte ordering. We then looked at the concept of shifting and rotating the bits in a register.

Next, we looked in detail at the MOV instruction that allows us to move data around the CPU registers or load constants from the MOV instruction into a register. We discovered the tricks of operand2 on how ARM represents a large range of values, given the limited number of bits it has at its disposal.

Finally, we covered the ADD and ADC instructions and discussed how to add both 32- and 64-bit numbers.

In Chapter 3, “Tooling Up,” we will look at better ways to build our programs and start debugging our programs with the GNU Debugger (gdb) .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.81.240