RISCV汇编指令调试,立即数使用分析
新建文件 test.s
1 | .global main |
riscv.....gcc test.s
编译成汇编代码riscv.....gdb test.s
用调试器运行
进入调试器
1 | (gdb) disassemble main |
在windows 和Ubuntu上打印的汇编信息由所区别,Windows上会打印额外的很多信息,
但是从0x0000000001019c开始的3行信息都是和Ubuntu上一致。一般情况下RISCV程序地址从0x10000开始
其余信息可能是一些stdio程序,并不是很清楚
用Spike 调试
1 | $: $RISCV/bin/spike -d pk a.out |
试验
1 | main: |
编译后的汇编指令1
2
3
4
5
6
7
8
9
10(gdb) disassemble main
Dump of assembler code for function main:
0x000000000001019c <+0>: lui a5,0x12345
0x00000000000101a0 <+4>: addiw a5,a5,1656
0x00000000000101a4 <+8>: lui a1,0x1 # a1=0x00001000 , 低12bit=0
0x00000000000101a8 <+12>: addiw a1,a1,-1348 # hex(0x1000 - 1348) = 0xabc
0x00000000000101ac <+16>: lui a2,0x12
0x00000000000101b0 <+20>: addiw a2,a2,837
0x00000000000101b4 <+24>: ret
End of assembler dump.
即便是小于12bit 的立即数也不能被立即load到,因为LUI指令加载的最小数是0x1000(除了0),
所以所有小于12bit的数val还是要用两条指令完成1
2LUI rd, 0x1 #load rd 0x1000
ADDIW rd, rs, (val-0x1000)
C代码编译调试
例子1
1 | void main() { |
1 | .file "a.c" |
编译后进gdb调试 a.out1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) disassemble main
Dump of assembler code for function main:
0x000000000001019c <+0>: addi sp,sp,-32
0x000000000001019e <+2>: sd s0,24(sp) #表示sp寄存器的内容+立即数偏移24
0x00000000000101a0 <+4>: addi s0,sp,32
0x00000000000101a2 <+6>: li a5,1
0x00000000000101a4 <+8>: sw a5,-20(s0)
0x00000000000101a8 <+12>: lw a5,-20(s0)
0x00000000000101ac <+16>: addiw a5,a5,1
0x00000000000101ae <+18>: sw a5,-20(s0)
0x00000000000101b2 <+22>: nop
0x00000000000101b4 <+24>: ld s0,24(sp)
0x00000000000101b6 <+26>: addi sp,sp,32
0x00000000000101b8 <+28>: ret
End of assembler dump.
定义大于32bit的整数1
2
3
4
5long main(){
long val = 0x876543210; //超过32位的长整数
val+=1;
return val;
}
不开启优化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 .file "a.c"
.option nopic
.text
.align 1
.globl main
.type main, @function
main:
addi sp,sp,-32
sd s0,24(sp)
addi s0,sp,32
lui a5,%hi(.LC0)
ld a5,%lo(.LC0)(a5) #load doubleworld from (.LC0)地址
sd a5,-24(s0)
ld a5,-24(s0)
addi a5,a5,1
sd a5,-24(s0)
ld a5,-24(s0)
mv a0,a5
ld s0,24(sp)
addi sp,sp,32
jr ra
.size main, .-main
.section .rodata
.align 3
.LC0:
.dword 36344967696 #开辟一块双字节的MEM空间,
.ident "GCC: (GNU) 8.1.0"
发现超过32bit的整数,不能通过立即数加载到寄存器(立即数受限于指令的长度最大只能是32bit,而且需要两条指令完成), 只能在内存中开辟一块地址先存放0x876543210,然后ld a5,该地址
例子2
1 | #include <stdio.h> |
hello.s 查看汇编代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 .file "hello.c"
.option nopic
.text
.section .rodata
.align 3
.LC0:
.string "hello world"
.text
.align 1
.globl main
.type main, @function
main:
addi sp,sp,-16 #sp 开辟16byte栈空间 sp=-16
sd ra,8(sp) #将ra的8Byte(64bit)内容从sp=-8处压栈 , 0 ~-8 存ra的值
sd s0,0(sp) #将s0的8Byte(64bit)内容从sp=-16处压栈,-8 ~-16存s0的值
addi s0,sp,16 #将sp=offset0的值存入s0
lui a5,%hi(.LC0) #计算string .LC0的地址,存入a0
addi a0,a5,%lo(.LC0) #这两条指令等价于 伪指令 li &(.LC0)
call printf #调用 printf 函数 ,printf 使用a0的内容作为参数
nop
ld ra,8(sp) #恢复栈 0~-8 的内容到ra
ld s0,0(sp) #恢复栈-8~-16的内容到s0
addi sp,sp,16 #栈指针恢复到0
jr ra #返回ra
.size main, .-main
.ident "GCC: (GNU) 8.1.0"
gdb hell.out 查看汇编以后的机器码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000010198 <+0>: addi sp,sp,-16
0x000000000001019a <+2>: sd ra,8(sp)
0x000000000001019c <+4>: sd s0,0(sp)
0x000000000001019e <+6>: addi s0,sp,16
0x00000000000101a0 <+8>: lui a5,0x1a # (.LC0)的起始地址就是0x19a10
0x00000000000101a2 <+10>: addi a0,a5,-1520 # 0x19a10
0x00000000000101a6 <+14>: jal ra,0x10354 <printf> # pc+4 存入ra, 跳转到pc+0x10354*2的printf函数地址
0x00000000000101aa <+18>: nop
0x00000000000101ac <+20>: ld ra,8(sp) # 恢复ra
0x00000000000101ae <+22>: ld s0,0(sp) # 恢复s0
0x00000000000101b0 <+24>: addi sp,sp,16 # 释放栈
0x00000000000101b2 <+26>: ret
End of assembler dump.
print内容稍加修改1
2
3
4#include <stdio.h>
void main(){
printf("Hello %s!\n","World");
}
查看效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000010198 <+0>: addi sp,sp,-16
0x000000000001019a <+2>: sd ra,8(sp)
0x000000000001019c <+4>: sd s0,0(sp)
0x000000000001019e <+6>: addi s0,sp,16
0x00000000000101a0 <+8>: lui a5,0x1a
0x00000000000101a2 <+10>: addi a1,a5,-1504 # 0x19a20 #加载字符串"Hello %s!\n" 的地址
0x00000000000101a6 <+14>: lui a5,0x1a
0x00000000000101a8 <+16>: addi a0,a5,-1496 # 0x19a28 #加载字符串"Wrold"的地址
0x00000000000101ac <+20>: jal ra,0x1035a <printf> #调用printf, printf会从a0,a1内获取参数
0x00000000000101b0 <+24>: nop
0x00000000000101b2 <+26>: ld ra,8(sp)
0x00000000000101b4 <+28>: ld s0,0(sp)
0x00000000000101b6 <+30>: addi sp,sp,16
0x00000000000101b8 <+32>: ret
End of assembler dump.
字符串的只需要知道起始地址即可,结束位由隐藏的固定格式构成
例3
1 | #include <stdio.h> |
查看机器码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000010198 <+0>: addi sp,sp,-32 # 开辟32Byte栈空间
0x000000000001019a <+2>: sd ra,24(sp) # 0 ~-8 存ra
0x000000000001019c <+4>: sd s0,16(sp) #-9 ~-16存s0
0x000000000001019e <+6>: addi s0,sp,32 # 栈根给s0
0x00000000000101a0 <+8>: li a5,2
0x00000000000101a2 <+10>: sw a5,-20(s0) # 2存入a5 压栈到-16~-20
0x00000000000101a6 <+14>: lui a5,0x1a # a0
0x00000000000101a8 <+16>: addi a1,a5,-1488 # 0x19a30 #加载字符串"Hello %s!\n" 的地址到a1
0x00000000000101ac <+20>: lui a5,0x1a
0x00000000000101ae <+22>: addi a0,a5,-1480 # 0x19a38 #加载字符串"World" 的地址到a0
0x00000000000101b2 <+26>: jal ra,0x1036e <printf>
0x00000000000101b6 <+30>: lw a5,-20(s0) #从栈-20 读出32bit=4Byte 出来
0x00000000000101ba <+34>: addiw a5,a5,1
0x00000000000101bc <+36>: sw a5,-20(s0)
0x00000000000101c0 <+40>: lw a5,-20(s0)
0x00000000000101c4 <+44>: mv a0,a5 #a++结果从栈中读出给a0 ,a0 a1 寄存器常用作返回值
0x00000000000101c6 <+46>: ld ra,24(sp)
0x00000000000101c8 <+48>: ld s0,16(sp)
0x00000000000101ca <+50>: addi sp,sp,32
0x00000000000101cc <+52>: ret #返回
End of assembler dump.
开启优化选项 -O1
2
3
4
5
6
7
8
9
10
11
12
13
14(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000010198 <+0>: addi sp,sp,-16
0x000000000001019a <+2>: sd ra,8(sp)
0x000000000001019c <+4>: lui a1,0x1a
0x00000000000101a0 <+8>: addi a1,a1,-1520 # 0x19a10
0x00000000000101a4 <+12>: lui a0,0x1a
0x00000000000101a8 <+16>: addi a0,a0,-1512 # 0x19a18
0x00000000000101ac <+20>: jal ra,0x10358 <printf>
0x00000000000101b0 <+24>: li a0,3 # 2 + 1 =3 在编译阶段就计算完
0x00000000000101b2 <+26>: ld ra,8(sp)
0x00000000000101b4 <+28>: addi sp,sp,16
0x00000000000101b6 <+30>: ret
End of assembler dump.
总结
LUI 指令会讲20比特的立即数放到 寄存器的31:12位置,并且将低12bit覆盖为0
ADDIW 指令会将 rs1 寄存器的值导入到rd 寄存器,并且用立即数[11:0] 覆盖 rd的低12比特
LU是伪指令,因为汇编指令的立即数肯定小于32bit,所以为了实现完整的32bit的立即数load
编译器使用了2条指令LUI,ADDIW 实现了LU的功能
- 对于一般变量比如int类型,会分配给栈来存放
- 如果比较大的常数变量,超过32bit或者64bit的数, 地址会分配堆内存给地址
- 对于数字串分配堆内存地址