A program on an ARM processor in user mode has access to 16 registers:
• R0 to R12: 通用寄存器
• R13: 栈指针寄存器
• R14: 连接寄存器。R13 and R14 are used in the context of calling functions, and we’ll explain these in more detail when we cover subroutines.
• R15: 程序计数器。 The memory address of the currently executing instruction.
• Current Program Status Register (CPSR): This 17th register contains bits of information on the last
instruction executed. More on the CPSR when we cover branch instructions (if statements).
.global _start
_start: mov R0, #1
ldr R1, =helloworld
mov R2, #13
mov R7, #4
svc 0
mov R0, #0
mov R7, #1
svc 0
.data
helloworld: .ascii "Hello World!\n"
• .global把 _start定义为一个全局标号, 这样链接器(the ld command in our build file)才可以访问它. 汇编程序把包含 _start的标号作为程序的入口, then the linker can find it because it has been defined as a global variable. All our programs will contain this somewhere.
• 我们的程序可以有很多个.s 文件, but only one can contain _start.
We only use three different Assembly language statements in this example:
Next, we have .data which indicates the following instructions are in the data section of the program:
• In this, we have a label “helloworld” followed by an .ascii statement and then the string we want to print.
• .ascii语句告诉汇编程序只需将我们的字符串放入数据部分,然后就可以像在LDR语句中一样通过标号访问它。稍后我们将讨论文本如何用数字表示,此处的编码方案称为ASCII。
• The last “\n” character is how we represent a new line. If we don’t include this, you must press return to see the text in the terminal window.
This program makes two Linux system calls to do its work. The first is the Linux write to file command (#4). Normally, we would have to open a file first before using this command, but when Linux runs a program, it opens three files for it:
当在Linux shell使用>,<和|时将会发生重定向。 对于任何Linux系统调用,根据需要,将参数放入寄存器R0–R4中。然后返回码存放在R0中。每个系统调用都通过将其功能编号放入R7来指定。
执行软件中断而不是分支或子程序调用的原因是,我们可以调用Linux,而无需知道该程序在内存中的位置。这意味着随着Linux的更新以及其程序在内存中的移动,我们无需更改程序中的任何地址。
软件中断的另一个好处是提供了一种标准的机制来切换优先级。我们将在第7章“ Linux操作系统服务”中讨论Linux系统调用。
• Condition: Allows the instruction to execute depending on the bits in the CPSR. We’ll examine this in detail when we get to branching instructions.
• Operand type: 指定19–0位上的操作数类型. We could have specified some of these bits, since we used two registers and an immediate operand in this example.
• Opcode: Which instruction are we performing, like ADD or MUL.
• Set condition code:这条指令是否更改CPSR. If we don’t want the result of this instruction to affect following branch instructions, we would set it to 0.
• Operand register: One register to use as input.
• Destination register: Where to put the result of whatever this instruction does.
• Immediate operand: 通常这是一小部分数据,可以在指令中直接指定。因此,如果要将1加到一个寄存器中,则可以将其设为1,而不是将1放入另一个寄存器中并将两个寄存器相加。该字段的格式非常复杂,需要较大的部分来解释所有细节,但这是基本思想。
ARM CPU之所以称为bi-endian,是因为它可以使用大小端的任一种。 CPSR中有一个程序状态标志,指出要使用的字节序。默认情况下,Raspbian和你的程序使用小端存储(类似于Intel处器)。
优点是无需任何地址运算就能更改整数的存储大小。如4字节的数变成1字节表示,取第1个字节即可;变成2字节表示,取前2个字节即可。
尽管Raspbian使用的是小端,但互联网上使用的许多协议(如TCP / IP)使用了大端,因此在将数据从树莓派迁移到外界时需要进行转换。
In this section, we are going to look at several forms of the MOV instruction:
我们已经见过了第一种情况,将一小部分存入寄存器。这里的立即数可以是任何16位的数,它会被放在指定寄存器的低16位中。这种形式的MOV指令非常简单。因此,我们经常使用它。
这条指令用于将16位的立即数加载到指定寄存器的高16位而不会影响它的低16位。例如,如果我们想要将0x4F5D6E3A加载到寄存器R2,我们可以这样做:
MOV R2, #0x6E3A
MOVT R2, #0x4F5D
There are two formats for Operand2:
MOV R1, R2, LSL #1
把R2逻辑左移1位的结果保存在R1中。
可以简写成:
LSL R1, R2, #1
这个比较难理解。回顾一下上面的指令格式,立即数操作数使用了11-0共计12位,这12位由两部分组成:
我们要做的,首先是把这一个字节大小的“小”数扩展为32位,然后循环右移,然后我们会得到另一个数,比如:
• 0 - 255 [0 - 0xff ]
• 256,260,264,…,1020 [0x100-0x3fc, 步长 4, 通过0x40-0xff 循环右移30位得到]
• 1024,1040,1056,…,4080 [0x400-0xff0, 步长 16, 通过0x40-0xff 循环右移28位得到]
• 4096,4160, 4224,…,16320 [0x1000-0x3fc0, 步长 64, 通过0x40-0xff 循环右移26位得到]
这意味着,很多32位的立即数可以通过一个8位的数+4位的循环位数来表示,也就是说,有些情况下我们把一个32位的立即数通过MOV指令送给寄存器也是被允许的,例如:
@ 超过了16位立即数的范围但可以被表示
MOV R1, #0xAB000000
这个是被允许的,因为0xAB000000可以通过AB循环右移8位得到
而
@ 超过了16位立即数的范围且不能被表示
MOV R1, #0xABCDEF11
因为汇编器找不到这样一个字节数和循环位数的组合来表示它,所以会报错:
Error: invalid constant (abcdef11) after fixup
要加载它到寄存器,需要搭配使用MOV / MOVT。
仔细思考不难发现,当一个数中1的个数超过8位时,它不能被表示(或取反,往下看)。当然,还有其它条件,这些留给你们慢慢思考。
和MOV类似,只不过它将源数据取反码后加载到目的寄存器中。
MVN是一个独特的操作码,而不是另一个带有隐秘参数的指令的别名。 ARM32指令集只有16个操作码,因此这是一条重要的指令,具有三个主要用途:
ADD R0, R1, #1
把R1和1相加的结果载入R0
ADD R1, #1等价于ADD R1, R1, #1
ADC:带进位的加法
ADDS:允许这条指令改变CPSR
其它指令也一样,在后面加上S即可改变CPSR。
r 执行程序
l 看十行代码
disassemble _start 看实际的二进制编码
b _start 断点
s 单步执行
i r 查看寄存器
c 继续执行直到下一断点或程序结束
i b 查看所有断点的号码
delete num 删除num号断点
x /Nfu addr 查看内存
q 退出GDB
where
N is the number of objects to display
• f is the display format where some common ones are
• t for binary
• x for hexadecimal
• d for decimal
• i for instruction
• s for string
u is unit size, and is any of
• b for bytes
• h for halfwords (16 bits)
• w for words (32 bits)
• g for giant words (64 bits)