Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)

参考博文:《怎样深入理解堆和栈》
《关于寻址方式一篇就够了》
《堆栈、栈帧、函数调用过程》
《gdb 调试中-i frame命令之堆栈信息说明》
《【TARS】GDB 调试进阶「0x02」》

栈与栈帧的关系

一个程序在运行过程中,操作系统会在内存中分配多个区域给这个程序。有以下几个区:

栈区(stack)
堆区(heap)
全局区(静态区)(static)
文字常量区
程序代码区

栈区是一段内存,主要作用是保存函数之间需要传递的参数并且保存被调用函数的返回地址。栈帧(stack )是一种逻辑划分,就是程序占用内存中的一个逻辑单元——特定一个函数所对应的栈区,它只保留一个函数的特定信息,一般会有局部变量、返回地址和旧的rbp值,甚至还有函数参数。

实例

运行程序的环境

sudo lsb_release -a看到操作系统是Ubuntu 22.04 LTS
sudo uname -r看到内核版本是5.15.0-86-generic
sudo as --version看到as的版本是2.38
sudo ld --version看到ld的版本是2.38
sudo gcc --version看到gcc版本是11.2.0
sudo gdb --version看到gdb版本是12.1

Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第1张图片

计算乘方(只有一层调用)

代码

runexponentInStack.s(计算3的2次方)代码如下:

.global _start
.section .text
_start: 
 
    # 将rbp里边的值压入到栈中
    pushq %rbp
    movq %rsp,%rbp
    # 请注意在C语言调用约定中,要是参数少于6个,不会使用栈来传递参数,这里只是为了说明栈的使用
    # 将2压入到栈中,在下边exponent函数中可以使用24(%rbp)对值进行操作
    pushq $2
    # 将3压入到栈中,在下边exponent函数中可以使用16(%rbp)对值进行操作
    pushq $3
    call exponent

    movq %rbp,%rsp
    popq %rbp
    # rdi里边会放入给操作系统的返回值,而此处是把rax里边的值放入到rdi
    movq %rax,%rdi
    # rax里边放入60,这是退出的系统调用号
    movq $60,%rax
    syscall


.global exponent
.type exponent,@function

.section .text
exponent:
    # 将rbp里边的值压入到栈中
    pushq %rbp
    movq %rsp,%rbp
    # rax = 1
    movq $1,%rax
mainloop:
    # 此处,mulq指令会计算rax乘以16(%rbp),最后乘积还会放到rax里边
    # 16(%rbp) = 3
    mulq 16(%rbp)
    # 刚开始的时候,24(%rbp) = 2,这里表示将24(%rbp)-1,然后结果放到24(%rbp)里边
    decq 24(%rbp)
    # 如果24(%rbp)不等于0,那么就会跳转到mainloop
    jnz mainloop
    

complete:
    movq %rbp,%rsp
    popq %rbp
    ret

汇编、链接与运行

sudo vim runexponentInStack.s把上边代码写入runexponentInStack.s里边,sudo as -g runexponentInStack.s -o runexponentInStack.o进行汇编,sudo ld -g runexponentInStack.o -o runexponentInStack进行链接,sudo ./runexponentInStack运行,sudo echo $?查看运行结果。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第2张图片

堆栈图

这里使用栈在main函数和exponent函数之间传递两个参数,接下来一行代码一行代码分析堆栈图,使用sudo gdb -q ./runexponentInStack开始进行分析对照。
在这里插入图片描述

而在gdb中,btbacktrace的缩写)命令可以查看当前函数向上的堆栈的函数的堆栈信息。i frameinfo frame的缩写)可以查看当前调试函数的堆栈帧信息。
break 6可以把断点打到第6行代码处,run开始运行。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第3张图片

info frame可以看到以下信息:

Stack level 0, frame at 0x7fffffffe668:
 rip = 0x401000 in _start (runexponentInStack.s:5); saved rip = 0x1
 source language asm.
 Arglist at 0x7fffffffe658, args:
 Locals at 0x7fffffffe658, Previous frame's sp is 0x7fffffffe668
 Saved registers:
  rip at 0x7fffffffe660

Stack level 0, frame at 0x7fffffffe668表明当前_start函数(也就是相当于C语言main函数)的栈帧起始地址是0x7fffffffe668
rip = 0x401000 in _start (runexponentInStack.s:5); saved rip = 0x1表明下一行执行的代码以内存0x401000开始,调用_start函数的内存地址以0x1开始。
Arglist at 0x7fffffffe658, args:表明参数变量的内存地址从0x7fffffffe658开始。
Locals at 0x7fffffffe658, Previous frame's sp is 0x7fffffffe668表明局部变量的内存地址从0x7fffffffe658开始,而上一个函数的栈帧从0x7fffffffe668开始。

 Saved registers:
  rip at 0x7fffffffe660

表示这个函数的调用位置是在内存0x7fffffffe660处。

info register rsp可以看到rsp里边的值是0x7fffffffe660,开始画堆栈图。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第4张图片

next执行到下一步,这时是把旧的rbp压到栈里边,info register rsp rbp可以看到rsp里边的值是0x7fffffffe658,而rbp的值是0x0
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第5张图片

堆栈图如下:

Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第6张图片

next会暂时停留在pushq $2这里。
在这里插入图片描述

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第7张图片

next再次执行,会把数字2压入栈帧中。
在这里插入图片描述
堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第8张图片

next再次执行,会把数字3压入栈帧中。
在这里插入图片描述

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第9张图片

step执行,就会跳转至新的函数exponent里边。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第10张图片

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第11张图片

next会把旧的rbp的值压入栈帧中。
在这里插入图片描述
堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第12张图片

next执行一下,那么rbp=rsp
在这里插入图片描述

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第13张图片

这里可以知道16(%rbp)等于rbp+1624(%rbp)等于rbp+24

next接着执行,发现rsprbp都没有改变,所以堆栈图也没有任何改变。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第14张图片

接下来连续按五下next,都没涉及栈帧操作,堆栈图没有发生任何改变。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第15张图片

next执行,现在rsp=rbp,堆栈图没有发生改变。
在这里插入图片描述

next执行,栈顶值被弹出到rbp里边。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第16张图片

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第17张图片

next执行,返回到_start函数里边。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第18张图片
堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第19张图片

next执行,rsp=rbp
在这里插入图片描述
堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第20张图片

next执行,栈顶值被弹出到rbp里边。
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第21张图片

堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第22张图片

再连续按三下next
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第23张图片
堆栈图如下:
Linux系统上64位AT&T风格汇编语言计算乘方堆栈图分析(只有一层调用)_第24张图片

相当于全部把栈区还给操作系统了。
此文章为11月Day 12学习笔记,内容来源于极客时间《Rust 语言从入门到实战》。

你可能感兴趣的:(rust学习,Rust,汇编)