12.ARM伪指令操作
首先ARM伪指令包括:
ARM机器码:
其实任何一种处理器可以运行的叫机器码,机器码是从汇编程序通过汇编器转换来的。接下来看看机器码的信息。流程:图1-1.
图1-1
在上一节里,建立好了一个简单的汇编工程,在start.S只有三行代码:图1-2:
图1-2
接下来对产生的elf文件来进行反汇编,命令是:
arm-linux-objdump -D -S gboot.elf >dump,将反汇编的代码存到dump文件,dump文件的内容是:
gboot.elf: file format elf32-littlearm
Disassembly of section .text:
50008000 <_start>:
.text
.global _start
_start:
mov r1,r2
50008000: e1a01002 mov r1, r2
moveq r2,#0xee
50008004: 03a020ee moveq r2, #238 ; 0xee
mov r3,#0x1
50008008: e3a03001 mov r3, #1 ; 0x1
Disassembly of section .debug_aranges:
00000000 <.debug_aranges>:
0: 0000001c andeq r0, r0, ip, lsl r0
4: 00000002 andeq r0, r0, r2
8: 00040000 andeq r0, r4, r0
c: 00000000 andeq r0, r0, r0
10: 50008000 andpl r8, r0, r0
14: 0000000c andeq r0, r0, ip
...
Disassembly of section .debug_info:
00000000 <.debug_info>:
0: 00000045 andeq r0, r0, r5, asr #32
4: 00000002 andeq r0, r0, r2
8: 01040000 tsteq r4, r0
c: 00000000 andeq r0, r0, r0
10: 50008000 andpl r8, r0, r0
14: 5000800c andpl r8, r0, ip
18: 72617473 rsbvc r7, r1, #1929379840 ; 0x73000000
1c: 00532e74 subseq r2, r3, r4, ror lr
20: 6d6f682f stclvs 8, cr6, [pc, #-188]!
24: 61732f65 cmnvs r3, r5, ror #30
28: 2f61626d svccs 0x0061626d
2c: 34364b4f ldrtcc r4, [r6], #-2895
30: 625f3031 subsvs r3, pc, #49 ; 0x31
34: 00657261 rsbeq r7, r5, r1, ror #4
38: 20554e47 subscs r4, r5, r7, asr #28
3c: 32205341 eorcc r5, r0, #67108865 ; 0x4000001
40: 2e38312e rsfcsep f3, f0, #0.5
44: 01003035 tsteq r0, r5, lsr r0
48: Address 0x00000048 is out of bounds.
Disassembly of section .debug_abbrev:
00000000 <.debug_abbrev>:
0: 10001101 andne r1, r0, r1, lsl #2
4: 12011106 andne r1, r1, #-2147483647 ; 0x80000001
8: 1b080301 blne 200c14 <_start-0x4fe073ec>
c: 13082508 movwne r2, #34056 ; 0x8508
10: 00000005 andeq r0, r0, r5
Disassembly of section .debug_line:
00000000 <.debug_line>:
0: 00000033 andeq r0, r0, r3, lsr r0
4: 001e0002 andseq r0, lr, r2
8: 01020000 tsteq r2, r0
c: 000d0efb strdeq r0, [sp], -fp
10: 01010101 tsteq r1, r1, lsl #2
14: 01000000 tsteq r0, r0
18: 00010000 andeq r0, r1, r0
1c: 72617473 rsbvc r7, r1, #1929379840 ; 0x73000000
20: 00532e74 subseq r2, r3, r4, ror lr
24: 00000000 andeq r0, r0, r0
28: 00020500 andeq r0, r2, r0, lsl #10
2c: 15500080 ldrbne r0, [r0, #-128]
30: 02022f2f andeq r2, r2, #188 ; 0xbc
34: Address 0x00000034 is out of bounds.
Disassembly of section .ARM.attributes:
00000000 <.ARM.attributes>:
0: 00001741 andeq r1, r0, r1, asr #14
4: 61656100 cmnvs r5, r0, lsl #2
8: 01006962 tsteq r0, r2, ror #18
c: 0000000d andeq r0, r0, sp
10: 00543405 subseq r3, r4, r5, lsl #8
14: 01080206 tsteq r8, r6, lsl #4
从上面的反汇编代码知道,程序的入口是:图1-3:
图1-3
与在Makefile中指定的起始地址相同:图1-4:
图1-4
可以看到汇编代码中,的最右边是程序里的汇编代码,图1-5.
图1-5
上面的反汇编代码中,最右边可以看到是汇编文件的汇编代码。多了分号,这是系统加的,表示后面是注释。注释里是16进制数,跟立即数是对应的。最左边,可以看到内存地址,指定程序在50008000开始运行,由于ARM核默认四字节对齐运行方式,所以下一条指令在50008004地址开始。可以看到中间还有一串数字。这就是机器码。
有关机器码的知识,接下来打开ARM Architecture Reference Manual.pdf文档。打开之后找到The ARM Instruction Set
这一章。可以找到有关机器码的知识。如下图1-6:
图1-6
可以看到ARM机器码是32位整数,32的ARM机器码被分成了多个段,每个段有每个段的含义。接下来以一种机器码为例进行分析,以MOV指令为例。
在start.S汇编文件里有有两条指令:
mov r1,r2
moveq r2,#0xee
对应的机器码:
e1a01002
03a020ee
如下图1-7:
图1-7
下面是mov机器码的格式:图1-8:
图1-8
接下来转化为2进制的:
汇编指令:
mov r1,r2
机器码:
e1a01002= 11100001101000000001000000000010
汇编指令:
moveq r2,#0xee
机器码
03a020ee= 00000011101000000010000011101110
分析机器码:
汇编指令:
mov r1,r2
机器码:
e1a01002= 1110 00 0 1101 0 0000 0001 000000000010
由下面的condition表格知道,mov是没有条件的,没条件执行condition,[31:28]=1110,是对的。[27:26]这两位是保留位为00,也是对的。[25]对应的是I位,对应的是[11:0]的数,如果这12的操作数是立即数,I位1,如果这12位为寄存器,则I位为0。可以看到I位为0,后面的[11:0]是一个寄存器r2,也是正确的。[24:21]对应的是opcode,指明指令的类型。不同的指令,这里的值不同,这也操作系统识别不同指令的地方。[20]位是S位,是指明该指令的运行是否影响CPSR寄存器,mov指令的运行不会影响CPSR的任意位。所以是0。在mov指令中,没有使用Rn[19:16],所以四位都是0000。[15:12]四位是目的寄存器,这里目的寄存器我使用了r1,所以[15:12]=0001。后面[11:0]12位是源操作数,使用的寄存器是r2=000000000010。与前面的S位对应。
汇编指令:
moveq r2,#0xee
机器码
03a020ee= 0000 00 1 1101 0 0000 0010 000011101110
由下面的condition表格知道,moveq是有条件的,条件是eq,对应的[31:28]=0000,是对的。[27:26]这两位是保留位为00,也是对的。[25]对应的是I位,对应的是[11:0]的数,如果这12的操作数是立即数,I位1,如果这12位为寄存器,则I位为0。可以看到I位为1,后面的[11:0]是立即数0xee,也是正确的。[24:21]对应的是opcode,指明指令的类型。不同的指令,这里的值不同,这也操作系统识别不同指令的地方。这里用到的都是mov指令所以值是一样的。[20]位是S位,是指明该指令的运行是否影响CPSR寄存器,moveq指令的运行不会影响CPSR的任意位。所以是0。在moveq指令中,没有使用Rn[19:16],所以四位都是0000。[15:12]四位是目的寄存器,这里目的寄存器我使用了r2,所以[15:12]=0010。后面[11:0]12位是源操作数,使用的是立即数=000011101110。与前面的S位对应。
上面就是mov程序的汇编机器码位的解释。在这里,会注意到后面的12位可以通过S位来指明这12是立即数还是寄存器。后面还会讲到,这12中,只有末尾8位是用来表示立即数或者寄存器的,[11:8]是设置移位等信息的。所以表示的数最大0xff。当我们把他改为0x1ff的时候会报错:图1-9:
图1-9
所以会发现,ARM汇编的立即数的范围是很有限的。所以这就是后面要写的伪指令的知识要解决的问题。
下面是上面的图解:1-10:
图1-10
在指令中,前4位,即是[31:28],表示condition条件位:图1-11:
图1-11
Opcode指令类型的信息:21~14位:图1-12:
图1-12
二、定义类伪指令:
图2-1
上图2-1里就是要学习的伪指令。
伪指令:所谓的伪指令,可以从拆开伪和指令,之所以叫指令,就是它的执行看似起到一定效果,看似跟真实的指令一样。所以叫它指令,但是,它有一个伪字修饰。这是为什么呢?这是因为它的执行不会产生机器码,我们知道,指令只有转换成机器码才能被机器执行。它起到两种作用:
各个伪指令:
Global:定义一个全局的符号。
Data:定义数据段。数据存到数据段。
Ascii:定义字符串
Byte:定义字节
Word:字
Equ:相当于宏定义
Align:设置对齐。
第一个是.global,指明一个全局的标识,在下面的_start就是。
至于.data、.ascii、.byte、.word的操作如下:
Start.S:图2-2:
图2-2
对上面的工程的elf文件进行查看:
arm-linux-readelf -a gboot.elf
-a是全部输出的意思:输出的信息如下:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x50008000
Start of program headers: 52 (bytes into file)
Start of section headers: 33116 (bytes into file)
Flags: 0x5000002, has entry point, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 11
Section header string table index: 8
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 50008000 008000 00000c 00 AX 0 0 4
[ 2] .data PROGBITS 5001000c 00800c 00000f 00 WA 0 0 1
[ 3] .debug_aranges PROGBITS 00000000 008020 000020 00 0 0 8
[ 4] .debug_info PROGBITS 00000000 008040 000049 00 0 0 1
[ 5] .debug_abbrev PROGBITS 00000000 008089 000014 00 0 0 1
[ 6] .debug_line PROGBITS 00000000 00809d 000039 00 0 0 1
[ 7] .ARM.attributes ARM_ATTRIBUTES 00000000 0080d6 000018 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0080ee 00006c 00 0 0 1
[ 9] .symtab SYMTAB 00000000 008314 000180 10 10 13 4
[10] .strtab STRTAB 00000000 008494 000087 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x008000 0x50008000 0x50008000 0x0000c 0x0000c R E 0x8000
LOAD 0x00800c 0x5001000c 0x5001000c 0x0000f 0x0000f RW 0x8000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
There is no dynamic section in this file.
There are no relocations in this file.
There are no unwind sections in this file.
Symbol table '.symtab' contains 24 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 50008000 0 SECTION LOCAL DEFAULT 1
2: 5001000c 0 SECTION LOCAL DEFAULT 2
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 6
7: 00000000 0 SECTION LOCAL DEFAULT 7
8: 5001000c 0 NOTYPE LOCAL DEFAULT 2 hello
9: 50010016 0 NOTYPE LOCAL DEFAULT 2 bh
10: 50010016 0 NOTYPE LOCAL DEFAULT 2 $d
11: 50010017 0 NOTYPE LOCAL DEFAULT 2 say
12: 50008000 0 NOTYPE LOCAL DEFAULT 1 $a
13: 5000800c 0 NOTYPE GLOBAL DEFAULT ABS __exidx_end
14: 5001001b 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__
15: 5001001b 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__
16: 5000800c 0 NOTYPE GLOBAL DEFAULT ABS __exidx_start
17: 5001001b 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__
18: 50008000 0 NOTYPE GLOBAL DEFAULT 1 _start
19: 5001001b 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
20: 5001001c 0 NOTYPE GLOBAL DEFAULT ABS __end__
21: 5001001b 0 NOTYPE GLOBAL DEFAULT ABS _edata
22: 5001001c 0 NOTYPE GLOBAL DEFAULT ABS _end
23: 5001000c 0 NOTYPE GLOBAL DEFAULT 2 __data_start
No version information found in this file.
Attribute Section: aeabi
File Attributes
Tag_CPU_name: "4T"
Tag_CPU_arch: v4T
Tag_ARM_ISA_use: Yes
数据段的起始地址:图2-3:
图2-3
可以看到数据段的起始地址是5001000c,定义的Ascii:定义字符串,Byte:定义字节,Word:字,都在这数据段里面。
Equ伪指令:
定义一个宏的指令,运行如下图:2-4:
图2-4
上面可以看到r0是0x89,宏定义成功。
最后是align伪指令的操作:
在命令行执行:
arm-linux-readelf -a gboot.elf
可以看到say出的物理地址是50010017:图2-5:
图2-5
现在来say出的标识之上加上align对齐,进行四字节对齐,再次编译的结果是:图2-6:
图2-6
可以看到say处的地址变成了50010020,是4字节对齐了。
操作类伪指令:
1.nop:是空操作。意义,进行延时,编写驱动,返回硬件的时候,由于时序的要求,需要延时,就执行该指令。反汇编看看它实际执行的代码:在下图看到,nop实际上执行的代码是mov r0,,r0。就是一个死循环的操作。起到延时的作用。反汇编代码:图3-1-0:
图3-1-0
2.ldr:前面的mov指令格式分析知道,12位的源操作数,只有8位表示数字大写,[11:8]是移位信息的。所以源操作数不能大于0x1ff,如果大于则会出错。图3-1-1:
图3-1-1
所以要操作一个大于8位的操作数,必须使用ldr伪指令。注意的是ldr伪指令的立即数是等号=开头,再加立即数。操作成功。图3-2:
图3-2
接下来看ldr伪指令是转化成哪些指令执行的。
对代码进行反汇编:
arm-linux-objdump -D -S gboot.elf
找到ldr的汇编代码:
ldr r0,=0xeff//这里的ldr是伪指令
50008000: e59f0000 ldr r0, [pc, #0] ; 50008008 <_start+0x8>//这里的ldr是存储器//访问指令。Ldr存储器指令把0xeff放到内存地址为50008008的地方去了。
mov r3,#0x1
50008004: e3a03001 mov r3, #1 ; 0x1
50008008: 00000eff .word 0x00000eff //在此地址定义了一个字的数据。反汇编代码:图3-3:
图3-3
下面看下面几个例子,来分析一下ldr伪指令的工作工程。
当start.S里只有下面这一行汇编代码的:图3-4:
ldr r0,=0xeff
对工程进行编译后反汇编:arm-linux-objdump -D -S gboot.elf
图3-4
从ldr r0,=0xeff的反汇编代码看到,ldr伪指令是在编译的时候转换成ldr存储器访问指令执行的。首先ldr伪指令把0xeff,存到了50008004地址处,同时定义了一个字的数据0x00000eff,这个数据大小跟我定义的数据大小一致的。然后通过内存访问指令,ldr r0,[pc,#-4],把这个数据取出来。我们知道pc指针总是指向正在执行指令的后第二条指令的地址。在这里伪指令ldr正在运行的地址是50008000,此时pc指针的地址是后第二条指令的地址,就是50008000+2*4=50008008。看到ldr存储器访问指令访问的地址是:
[pc,#-4]=pc-4=50008008-4=50008004,可以看到,这就是我定义一个字数据的地方,就是我定义存储数据的地方。这样,cpu就实现了存储大于8位数据。
下面是增加了汇编代码的反汇编截图,可以分析到跟这种情况是一致的。
增加一条汇编代码:图3-5:
Start.S里的汇编代码:
Ldr r0,=0xeff
Mov r1,#0x22
图3-5
增加一条汇编代码:图3-6:
Start.S里的汇编代码:
Ldr r0,=0xeff
Mov r1,#0x22
Mov r2,#0x32
图3-6
在ldr前加一行:图3-7:
Start.S里的汇编代码:
mov r3,#0x12
Ldr r0,=0xeff
Mov r1,#0x22
Mov r2,#0x32
图3-7
在第一行增加一行nop空操作,最后增加一行mov操作:图3-8:
nop
mov r3,#0x12
ldr r0,=0xeff
mov r1,#0x22
mov r2,#0x32
mov r3,#0x42
图3-8
为了温习前面的知识,这里对这个工程的反汇编代码再来分析一次,看看上面汇编代码的执行过程。首先是nop空操作,反汇编代码看到他是做mov r0,r0的无限循环操作,不会产生什么影响,只是延时作用。然后mov r3,#0x12,操作的内存地址是50008004,它是把0x12存到该地址处。转换后的机器码是e3a03012,可以对应上面对这机器码进行分析,看看对不对。
从ldr r0,=0xeff的反汇编代码看到,ldr伪指令是在编译的时候转换成ldr存储器访问指令执行的。首先ldr伪指令把0xeff,存到了50008018地址处,同时定义了一个字的数据0x00000eff,这个数据大小跟我定义的数据大小一致的。然后通过内存访问指令,ldr r0,[pc,#8],把这个数据取出来。我们知道pc指针总是指向正在执行指令的后第二条指令的地址。在这里伪指令ldr正在运行的地址是50008008,此时pc指针的地址是后第二条指令的地址,就是50008008+2*4=50008010。看到ldr存储器访问指令访问的地址是:
[pc,#8]=pc+8=500080010+8=50008018,可以看到,这就是我定义一个字数据的地方,就是我定义存储数据的地方。这样,cpu就实现了存储大于8位数据。
数据完全正确,说明前面的操作与分析都是正确的。呼呼……………