函数

这里在文件 power.s 中编写一个名为 power 的函数,该函数实现的功能是计算某个数字的幂,我们给这个函数 § 函数的运行和寄存器的运作 - 图1 两个参数:某个参数 x 和指数 n。

以下是完整的程序

  1. # 目的:展示函数如何工作的程序
  2. # 本程序将计算 2^3+5^2
  3. # 主程序中的所有内容都存储在寄存器中,因此数据段不含任何内容
  4. .section .data
  5. .section .text
  6. .global _start
  7. _start:
  8. pushl $3 # 压入第二个参数
  9. pushl $2 # 压入第一个参数
  10. call power # 调用 power 函数
  11. addl $8, %esp # 将指针向后移动 8 个字节:因为3和2为int类型4字节,所以一共8字节
  12. pushl %eax # 在调用下一个函数之前,保存第一个答案
  13. pushl $2 # 压入第二个参数
  14. pushl $5 # 压入第一个参数
  15. call power # 调用 power 函数
  16. add $8, %esp # 将栈指针向后移动
  17. popl %ebx # 第二个答案已经在 %eax 中。我们之前已经将第一个答案存储到栈中,
  18. # 所以现在可以将其弹出到 %ebx
  19. add %eax, %ebx # 将两者相加结果在 %ebx 中
  20. movl $1, %eax # 退出(返回 %ebx)
  21. int $0x80
  22. # 目的:本函数用于计算一个函数的幂
  23. # 输入:第一个参数-底数
  24. # 第二个参数-底数的指数
  25. # 输出:以返回值的形式给出结果
  26. # 注意:指数必须大于等于1
  27. # 变量:%ebx-保存底数 %ecx-保存指数
  28. # -4(%ebp)-保存当前结果
  29. # %eax 用于暂时存储
  30. .type power, @function
  31. power:
  32. pushl %ebp # 保留旧基址指针
  33. movl %esp, %ebp # 将基址指针设为栈指针
  34. subl $4, %esp # 为本地存储保留空间
  35. movl 8(%ebp), %ebx # 将第一个参数放入 %eax
  36. movl 12(%ebp), %ecx # 将第二个参数放入 %ecx
  37. movl %ebx, -4(%ebp) # 存储当前结果
  38. power_loop_start:
  39. cmpl $1, %ecx # 如果是 1 次方,那么我们已经获得结果
  40. je end_power
  41. movl -4(%ebp), %eax # 将当前结果移入 %eax
  42. imull %ebx, %eax # 将当前结果与底数相乘
  43. movl %eax, -4(%ebp) # 保存当前结果
  44. decl %ecx # 指数递减
  45. jmp power_loop_start # 为递减后的指数进行幂计算
  46. end_power:
  47. movl -4(%ebp), %eax # 将返回值移入 %eax
  48. movl %ebp, %esp # 恢复栈指针
  49. popl %ebp # 恢复基址指针
  50. ret

将上述代码编译运行

  1. as power.s -o power.o --32
  2. ld power.o -o power -m elf_i386

使用 echo $? 查看该程序最后的返回值,即 23 + 52 的大小,结果为 33 ,说明程序运行正确

§ 函数的运行和寄存器的运作 - 图2

注意:因为我们上面使用了 pushlpopl 所以需要的是 32 位的环境,汇编和链接程序的时候就需要为 32 位模式,故在命令后添加参数指明程序环境。

使用GDB调试代码

下面逐步剖一下,上面是怎么和寄存器配合实现相关的功能的,首先使用

  1. as power.s -o power.o --gstabs --32
  2. ld power.o -o power -m elf_i386

在命令中添加 —gstabs 添加调试信息,随后开始调试。

§ 函数的运行和寄存器的运作 - 图3 输入 gdb power 进入调试界面

§ 函数的运行和寄存器的运作 - 图4 在命令的开始 _start 处打断点:

§ 函数的运行和寄存器的运作 - 图5 输入 run 命令开始执行程序

§ 函数的运行和寄存器的运作 - 图6 输入 step 命令单步调试程序

§ 函数的运行和寄存器的运作 - 图7 输入 info registers 查看寄存器状态

§ 函数的运行和寄存器的运作 - 图8

另外,使用命令 print 加上指定寄存器也可以显示寄存器的值:

§ 函数的运行和寄存器的运作 - 图9

§ 函数的运行和寄存器的运作 - 图10 查看内存地址值

§ 函数的运行和寄存器的运作 - 图11

gdb 查看内存数据,格式为 x/nfu Address

  • n 表示要显示的内存单元的个数,比如:20
  • f 表示显示方式, 可取如下值:
    • x 按十六进制格式显示变量。
    • d 按十进制格式显示变量。
    • u 按十进制格式显示无符号整型。
    • o 按八进制格式显示变量。
    • t 按二进制格式显示变量。
    • a 按十六进制格式显示变量。
    • i 指令地址格式
    • c 按字符格式显示变量。
    • f 按浮点数格式显示变量。
  • u 表示一个地址单元的长度:
    • b表示单字节,
    • h表示双字节,
    • w表示四字节,
    • g表示八字节