本次实验内容:将一个简单的C程序汇编成x86或x86-64汇编代码,并逐步分析程序的执行过程,深入理解存储程序计算机和函数调用堆栈框架在执行过程中所起的作用。
我选择使用RISC-V汇编代码。
首先安装对应的gcc交叉编译环境:
sudo apt-get install gcc -riscv64-linux-gnu
安装成功:
然后编写一段简单的C语言代码:
执行编译指令:
riscv64-linux-gnu-gcc -S -o main_riscv64.s main.c
编译成功:
查看汇编代码:
.file "main.c"
2 .option nopic
3 .text
4 .align 1
5 .globl g
6 .type g, @function
7 g:
8 add sp,sp,-32 #sp=sp-32 为栈顶预留四个字节的存储空间
9 sd s0,24(sp)#store指令,s0为栈底指针,将s0的值存放到sp+24的位置
10 add s0,sp,32#s0 = sp+32 构造g函数的栈空间,形成g函数的逻辑空栈
11 mv a5,a0#将a0的值赋给a5即a5 = a0
12 sw a5,-20(s0)#store指令 将a5的值存放至s0-20的位置中
13 lw a5,-20(s0)#load指令,将s0-20的值存放至a5
14 addw a5,a5,2023#a5 = a5 + 2023
15 sext.w a5,a5
16 mv a0,a5#a0 = a5 将g函数的返回值存放至a0中
17 ld s0,24(sp)#s0 = sp+24 将sp+24的值重新存储道s0栈底指针中,指向f函数的栈空间
18 add sp,sp,32 #栈顶指针指向f函数栈空间的栈顶
19 jr ra#无条件跳转指令 函数返回
20 .size g, .-g
21 .align 1
22 .globl f
23 .type f, @function
24 f:
25 add sp,sp,-32#sp = sp -32 sp为栈顶指针预留出四个字节存储空间
26 sd ra,24(sp)#store指令,ra存放的是返回地址
27 sd s0,16(sp)#store指令 s0为栈底指针,将s0的值存放到sp+16指向的位置中
28 add s0,sp,32#s0=sp+32,构造f函数的栈空间,形成f函数的逻辑空间
29 mv a5,a0#a5=a0,a0存放的是函数参数,此处保存一次参数的作用是用来给后面的g函数进行传递参数,此时a5中存放的是23,可以发现RISC-V使用 寄存器 进行参数传递
30 sw a5,-20(s0)#store指令,将a5的值存放到s0-20指向的位置中
31 lw a5,-20(s0)#load指令,将s0-20位置中的值存放到a5中
32 mv a0,a5#a0=a5,此时a0保存的是函数参数,为调用g(x)做准备
33 call g#调用g函数
34 mv a5,a0#a5=a0
35 mv a0,a5#a0=a5,将f函数返回值保存在a0中
36 ld ra,24(sp)#ra=sp+24,获取main函数的返回地址
37 ld s0,16(sp)#s0=sp+16,将sp+16的值重新存储到s0栈底指针中,指向main函数的栈空间
38 add sp,sp,32#sp,sp,32 ;指向main函数栈空间栈顶
39 jr ra#无条件跳转指令,返回main函数
40 .size f, .-f
41 .align 1
42 .globl main
43 .type main, @function
main:
45 add sp,sp,-16#sp=sp-16,64位指令集,一条指令需要8个字节,这里预留16字节分别存储上一个程序的堆栈的返回地址和栈底指针
46 sd ra,8(sp)#store指令,ra存放的是返回地址,将ra存放到sp+8指向的位置中
47 sd s0,0(sp)#store指令,s0为栈底指针,将s0的值存放到sp指向的位置中
48 add s0,sp,16#s0=sp+16,构造main函数的栈空间,形成main函数的逻辑空栈
49 li a0,23#保存f函数参数23,li是RISC-V中的一条伪指令,意思是load immediate,即将一个立即数加载到寄存器中
50 call f#调用函数f,返回值会保存在a0中
51 mv a5,a0#a5 = a0
52 addw a5,a5,1#将寄存器 a5 的值加上 1,然后将结果作为一个 32 位的有符号整数写回 a5 中
53 sext.w a5,a5#将寄存器 a5 的值作为一个 32 位的有符号整数扩展到 64 位,然后写回 a5 中。这是一种伪指令,相当于 addiw a5,a5,0。
54 mv a0,a5
55 ld ra,8(sp)#从栈指针 sp 加上 8 的地址处加载 64 位的数据,然后存入寄存器 ra 中。ra 是返回地址寄存器,用于保存函数调用的返回地址
56 ld s0,0(sp)#从栈指针 sp 的地址处加载 64 位的数据,然后存入寄存器 s0 中。s0 是一个被调用者保存的寄存器,用于保存函数内部的状态或者参数
57 add sp,sp,16#将栈指针 sp 的值加上 16,然后将结果存回 sp 中。这样做的目的是为了释放栈上分配的空间,恢复原来的栈指针位置
58 jr ra#无条件跳转到寄存器 ra 的值所表示的地址处继续执行。这样做的目的是为了返回到函数调用者处,结束当前函数的执行
59 .size main, .-main
60 .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
main_riscv64.s
任课老师:孟宁老师