函数
这里在文件 power.s 中编写一个名为 power 的函数,该函数实现的功能是计算某个数字的幂,我们给这个函数 两个参数:某个参数 x 和指数 n。
以下是完整的程序
# 目的:展示函数如何工作的程序
# 本程序将计算 2^3+5^2
# 主程序中的所有内容都存储在寄存器中,因此数据段不含任何内容
.section .data
.section .text
.global _start
_start:
pushl $3 # 压入第二个参数
pushl $2 # 压入第一个参数
call power # 调用 power 函数
addl $8, %esp # 将指针向后移动 8 个字节:因为3和2为int类型4字节,所以一共8字节
pushl %eax # 在调用下一个函数之前,保存第一个答案
pushl $2 # 压入第二个参数
pushl $5 # 压入第一个参数
call power # 调用 power 函数
add $8, %esp # 将栈指针向后移动
popl %ebx # 第二个答案已经在 %eax 中。我们之前已经将第一个答案存储到栈中,
# 所以现在可以将其弹出到 %ebx
add %eax, %ebx # 将两者相加结果在 %ebx 中
movl $1, %eax # 退出(返回 %ebx)
int $0x80
# 目的:本函数用于计算一个函数的幂
# 输入:第一个参数-底数
# 第二个参数-底数的指数
# 输出:以返回值的形式给出结果
# 注意:指数必须大于等于1
# 变量:%ebx-保存底数 %ecx-保存指数
# -4(%ebp)-保存当前结果
# %eax 用于暂时存储
.type power, @function
power:
pushl %ebp # 保留旧基址指针
movl %esp, %ebp # 将基址指针设为栈指针
subl $4, %esp # 为本地存储保留空间
movl 8(%ebp), %ebx # 将第一个参数放入 %eax
movl 12(%ebp), %ecx # 将第二个参数放入 %ecx
movl %ebx, -4(%ebp) # 存储当前结果
power_loop_start:
cmpl $1, %ecx # 如果是 1 次方,那么我们已经获得结果
je end_power
movl -4(%ebp), %eax # 将当前结果移入 %eax
imull %ebx, %eax # 将当前结果与底数相乘
movl %eax, -4(%ebp) # 保存当前结果
decl %ecx # 指数递减
jmp power_loop_start # 为递减后的指数进行幂计算
end_power:
movl -4(%ebp), %eax # 将返回值移入 %eax
movl %ebp, %esp # 恢复栈指针
popl %ebp # 恢复基址指针
ret
将上述代码编译运行
as power.s -o power.o --32
ld power.o -o power -m elf_i386
使用 echo $?
查看该程序最后的返回值,即 23 + 52 的大小,结果为 33 ,说明程序运行正确
注意:因为我们上面使用了 pushl
和 popl
所以需要的是 32 位的环境,汇编和链接程序的时候就需要为 32 位模式,故在命令后添加参数指明程序环境。
使用GDB调试代码
下面逐步剖一下,上面是怎么和寄存器配合实现相关的功能的,首先使用
as power.s -o power.o --gstabs --32
ld power.o -o power -m elf_i386
在命令中添加 —gstabs 添加调试信息,随后开始调试。
输入 gdb power
进入调试界面
在命令的开始 _start 处打断点:
输入 run
命令开始执行程序
输入 step
命令单步调试程序
输入 info registers
查看寄存器状态
另外,使用命令 print 加上指定寄存器也可以显示寄存器的值:
查看内存地址值
gdb 查看内存数据,格式为
x/nfu Address
- n 表示要显示的内存单元的个数,比如:20
- f 表示显示方式, 可取如下值:
- x 按十六进制格式显示变量。
- d 按十进制格式显示变量。
- u 按十进制格式显示无符号整型。
- o 按八进制格式显示变量。
- t 按二进制格式显示变量。
- a 按十六进制格式显示变量。
- i 指令地址格式
- c 按字符格式显示变量。
- f 按浮点数格式显示变量。
- u 表示一个地址单元的长度:
- b表示单字节,
- h表示双字节,
- w表示四字节,
- g表示八字节