基础

栈(stack) 保存动态分配的自动变量时值,并且栈又操作系统自动分配内存。

测试程序:

cat sum.c

#include <stdio.h>#include <ctype.h>#include <stdlib.h>#define MAX (1UL << 20)typedef unsigned long u64;typedef unsigned int u32;u64 max_addend = MAX;u64 sum_till_MAX(u32 n){
u64 sum;n++;
sum = n;if(n < max_addend)sum += sum_till_MAX(n);return sum;}int main(int argc, char **argv){
u64 sum = 0;if((argc == 2) && isdigit(*(argv[1])))max_addend = strtoul(argv[1], NULL, 0);if(max_addend > MAX || max_addend == 0){fprintf(stderr, "Invalid number is specified\n");return 1;}sum = sum_till_MAX(0);printf("sum(0..%lu) = %lu\n", max_addend, sum);return 0;}

下面求出从0到10的总和

$ gcc -o sum -g sum.c
$ ./sum 10
sum(0..10) = 55
(gdb) disassemble main
…….0x0000000000000846 <+172>:   li      a0,0                 //调用函数时,先将参数赋值给a00x0000000000000848 <+174>:   jal     ra,0x744 <sum_till_MAX>   //jal将pc+4的值赋值给ra,然后将pc值设置为调用函数的地址
(gdb) disassemble sum_till_MAX
Dump of assembler code for function sum_till_MAX:0x0000000000000744 <+0>:     addi    sp,sp,-48        //分配一个栈空间0x0000000000000746 <+2>:     sd      ra,40(sp)        //将返回地址压入栈中0x0000000000000748 <+4>:     sd      s0,32(sp)        //将fp压入栈中,在riscv中,s0为fp寄存器0x000000000000074a <+6>:     addi    s0,sp,48        //fp指向当前的栈0x000000000000074c <+8>:     mv      a5,a0            //将参数a0赋值给a50x000000000000074e <+10>:    sw      a5,-36(s0)     //将参数存入栈上0x0000000000000752 <+14>:    lw      a5,-36(s0)     //从栈上读取参数0x0000000000000756 <+18>:    addiw   a5,a5,1        //n++0x0000000000000758 <+20>:    sw      a5,-36(s0)     //下面两行取n的低32位0x000000000000075c <+24>:    lwu     a5,-36(s0)0x0000000000000760 <+28>:    sd      a5,-24(s0)0x0000000000000764 <+32>:    lwu     a4,-36(s0)  //sum = n0x0000000000000768 <+36>:    auipc   a5,0x2   //将当前pc值加上2<<12赋值给a50x000000000000076c <+40>:    addi    a5,a5,-1888 # 0x2008 <max_addend>0x0000000000000770 <+44>:    ld      a5,0(a5)  //得到max_addend的值0x0000000000000772 <+46>:    bgeu    a4,a5,0x78c <sum_till_MAX+72>0x0000000000000776 <+50>:    lw      a5,-36(s0)0x000000000000077a <+54>:    mv      a0,a50x000000000000077c <+56>:    jal     ra,0x744 <sum_till_MAX>0x0000000000000780 <+60>:    mv      a4,a00x0000000000000782 <+62>:    ld      a5,-24(s0)0x0000000000000786 <+66>:    add     a5,a5,a40x0000000000000788 <+68>:    sd      a5,-24(s0)0x000000000000078c <+72>:    ld      a5,-24(s0)0x0000000000000790 <+76>:    mv      a0,a50x0000000000000792 <+78>:    ld      ra,40(sp)        //恢复返回地址0x0000000000000794 <+80>:    ld      s0,32(sp)         //恢复调用者的fp0x0000000000000796 <+82>:    addi    sp,sp,48        //恢复sp地址0x0000000000000798 <+84>:    ret                    //将ra赋值给pc

调试器的backtrace

GDB等调试器的backtrace功能是通过搜索栈中保存的 信息来实现的。

下面在第二次调用sum_till_MAX()时中断执行程序,

(gdb) bt
#0  sum_till_MAX (n=2) at sum.c:14
#1  0x0000002aaaaaa780 in sum_till_MAX (n=2) at sum.c:17
#2  0x0000002aaaaaa780 in sum_till_MAX (n=1) at sum.c:17
#3  0x0000002aaaaaa84c in main (argc=1, argv=0x3ffffffbe8) at sum.c:32
(gdb) i r pc sp
pc             0x2aaaaaa752     0x2aaaaaa752 <sum_till_MAX+14>
sp             0x3ffffff9a0     0x3ffffff9a0
(gdb) x/48x $sp
0x3ffffff9a0:   0xf7fd8570      0x0000003f      0xf7fd8368      0x00000002
0x3ffffff9b0:   0xffffffff      0x00000000      0x00000000      0x00000000
0x3ffffff9c0:   0xfffffa00      0x0000003f      0xaaaaa780      0x0000002a
0x3ffffff9d0:   0x00000001      0x00000000      0x00000000      0x00000002
0x3ffffff9e0:   0x00000001      0x00000000      0x00000002      0x00000000
0x3ffffff9f0:   0xfffffa30      0x0000003f      0xaaaaa780      0x0000002a
0x3ffffffa00:   0xffffffff      0x00000000      0xf7fe24f8      0x00000001
0x3ffffffa10:   0xf7ffea88      0x0000003f      0x00000001      0x00000000
0x3ffffffa20:   0xfffffa60      0x0000003f      0xaaaaa84c      0x0000002a
0x3ffffffa30:   0xfffffbe8      0x0000003f      0xffffffef      0x00000001
0x3ffffffa40:   0x00000000      0x00000000      0x00000000      0x00000000
0x3ffffffa50:   0xfffffbe8      0x0000003f      0xf7ecc66c      0x0000003f

从sum_till_MAX()反汇编可以知道每个栈帧都分配了48字节

手动解析栈帧:

(gdb) x/48x $sp
当sum_till_MAX 参数为2进入时,栈情况0x3ffffff9a0:   0xf7fd8570      0x0000003f      0xf7fd8368      0x00000002
0x3ffffff9b0:   0xffffffff      0x00000000      0x00000000      0x00000000
0x3ffffff9c0:   0xfffffa00      0x0000003f      0xaaaaa780      0x0000002a——————————————————————————      --------------------------0x3ffffffa00 上层fp                  0x2aaaaaa780  返回地址
当sum_till_MAX 参数为1进入时,栈情况0x3ffffff9d0:   0x00000001      0x00000000      0x00000000      0x00000002——————n的值
0x3ffffff9e0:   0x00000001      0x00000000      0x00000002      0x00000000————————————————————————         -------------------------参数1                             sum的值
0x3ffffff9f0:   0xfffffa30      0x0000003f      0xaaaaa780      0x0000002a-------------------------        -------------------------0x3ffffffa30 上层fp             0x2aaaaaa780  返回地址当sum_till_MAX 参数为0进入时,栈情况
0x3ffffffa00:   0xffffffff      0x00000000      0xf7fe24f8      0x00000001
0x3ffffffa10:   0xf7ffea88      0x0000003f      0x00000001      0x00000000
0x3ffffffa20:   0xfffffa60      0x0000003f      0xaaaaa84c      0x0000002a0x3ffffffa30:   0xfffffbe8      0x0000003f      0xffffffef      0x00000001
0x3ffffffa40:   0x00000000      0x00000000      0x00000000      0x00000000
0x3ffffffa50:   0xfffffbe8      0x0000003f      0xf7ecc66c      0x0000003f

需要注意的是n为32bits,sum为64bits

总结:得出如下公式

PC_f 指的是父函数调用子函数时的pc值

LR_c 指的是父函数调用子函数时,返回子函数的pc值

FP_c 指的是指向父函数的栈帧。

PC_f = *LR_c - 4 = *(FP_c -  8) - 4

父函数FP = *(子函数FP - 16)

栈大小的限制

实际上,在本实例中,如果不带参数,会引起Segmentation fault(段错误),执行:

$ gdb ./sum
Reading symbols from ./sum...
(gdb) r
Starting program: /mnt/sum
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/riscv64-linux-gnu/libthread_db.so.1".Program received signal SIGSEGV, Segmentation fault.
0x0000002aaaaaa74e in sum_till_MAX (n=<error reading variable: Cannot access memory at address 0x3fff7ffffc>) at sum.c:12
12      {
(gdb) x/i $pc
=> 0x2aaaaaa74e <sum_till_MAX+10>:      sw      a5,-36(s0)
(gdb) p $sp
$1 = (void *) 0x3fff7ffff0

打印x/i $pc,此时的的汇编指令为:

sw      a5,-36(s0)

是将参数存储到栈上,

在看sp的值:

(gdb) p $sp
$1 = (void *) 0x3fff7ffff0

下面查看该进程的内存映射(memory map),要查看GDB attach了的进程的内存映像,可以执行一下命令,执行该命令后,GDB就会显示与被调试的进程相对应的/proc/<PID>/maps的信息。

(gdb) i proc mappings
process 2132
Mapped address spaces:Start Addr           End Addr       Size     Offset  Perms  objfile0x2aaaaaa000       0x2aaaaab000     0x1000        0x0  r-xp   /mnt/sum0x2aaaaab000       0x2aaaaac000     0x1000        0x0  r--p   /mnt/sum0x2aaaaac000       0x2aaaaad000     0x1000     0x1000  rw-p   /mnt/sum0x3ff7ea6000       0x3ff7fc6000   0x120000        0x0  r-xp   /lib/riscv64-linux-gnu/libc.so.60x3ff7fc6000       0x3ff7fc9000     0x3000   0x120000  r--p   /lib/riscv64-linux-gnu/libc.so.60x3ff7fc9000       0x3ff7fcb000     0x2000   0x123000  rw-p   /lib/riscv64-linux-gnu/libc.so.60x3ff7fcb000       0x3ff7fda000     0xf000        0x0  rw-p   0x3ff7fdf000       0x3ff7fe0000     0x1000        0x0  r--p   [vdso_data]0x3ff7fe0000       0x3ff7fe2000     0x2000        0x0  r-xp   [vdso]0x3ff7fe2000       0x3ff7ffd000    0x1b000        0x0  r-xp   /lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.10x3ff7ffd000       0x3ff7ffe000     0x1000    0x1b000  r--p   /lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.10x3ff7ffe000       0x3ff8000000     0x2000    0x1c000  rw-p   /lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.10x3fff800000       0x4000000000   0x800000        0x0  rw-p   [stack]

请注意最后一行的[stack]。它表示栈空间,栈空间的顶端是0x3fff800000。然而,刚看到的栈指针的值却是0x3fff7ffff0,超出了栈的范围。访问地址超出了栈的范围,也就是说,发生了栈溢出。

查看当前环境设置的进程栈大小:

# ulimit -s
8192

大小为8MB。

现在将栈扩大10倍。再次执行实例程序。就不会发生段错误而正常结束了。

# ulimit -Ss 81920
# ./sum
sum(0..1048576) = 549756338176

risc-v 栈分析相关推荐

  1. RISC V (RV32+RV64) 架构 整体介绍

    文章目录 riscv 市场 芯片介绍 软件介绍 开发板介绍 PC介绍 riscv 架构 编程模型(指令集/寄存器/ABI/SBI) 运行状态 指令集 寄存器 riscv32和riscv64两者的区别 ...

  2. 计组学习笔记2(RISC v版)

    指令集解释 (规定:R[r]表示通用寄存器r的内容,M[addr]表示存储单元addr的内容,SEXT[imm]表示对imm进行符号扩展,ZEXT[imm]表示对imm进行零扩展) 整数运算类 -U型 ...

  3. 【愚公系列】2023年06月 移动安全之安卓逆向(插桩及栈分析)

    文章目录 前言 一.插桩及栈分析 1.概念简介 2.Android Killer插桩 二.栈跟踪及分析 1.DDMS工具介绍 1.1 设备信息窗口 1.2 过滤器窗口 1.3 功能窗口 1.4 信息输 ...

  4. 华为鸿蒙系统源码_鸿蒙系统 IO 栈分析 | 解读鸿蒙源码

    华为的鸿蒙系统开源之后第一个想看的模块就是 FS 模块,想了解一下它的 IO 路径与 linux 的区别.现在鸿蒙开源的仓库中有两个内核系统,一个是 liteos_a 系统,一个是 liteos_m ...

  5. oracle sqlarea表结构,oracle v$sqlarea 分析SQL语句使用资源情况

    V$SQLAREA 本视图持续跟踪所有shared pool中的共享cursor,在shared pool中的每一条SQL语句都对应一列.本视图在分析SQL语句资源使用方面非常重要. V$SQLARE ...

  6. Gstreamer的一些基本概念与A/V同步分析

    http://blog.csdn.net/shenbin1430/article/details/4291963 ================================= 一.媒体流(str ...

  7. 服务外包技术培训——后端开发技术栈分析(Java)

    技术栈 http://www.atguigu.com/download.shtml 学习资源 https://space.bilibili.com/302417610/channel/detail?c ...

  8. .windbg-k*实例分析(查看调用栈分析)

    [cpp] view plaincopy #include "stdafx.h" int fun0(int i) { return i; }; int fun1(int i) { ...

  9. java中线程的状态以及线程栈分析

    java中线程的状态 状态 说明 NEW 初始状态.线程刚刚被创建,并且start()方法还未被调用 RUNNABLE 运行状态.表示线程正在java虚拟机中执行,但是可能正在等待操作系统的其他资源, ...

  10. struts2值栈分析

    前段日子对ognl表达式不是很理解,看了几本书上关于ognl表达式的描述后还是感觉很难,前几天学习了struts2中值栈的内容,现在感觉ognl表达式其实很容易. struts2中利用值栈来存储数据, ...

最新文章

  1. C# 36进制转10进制
  2. 创建一个触发器新增字段的时候设置某个字段的值
  3. 神策数据与 UCloud 达成战略合作,开启高效企业服务新模式
  4. Git 分支的创建与切换 —— Git 学习笔记 14
  5. TODO:Go语言goroutine和channel使用
  6. HDU - 4586 数学期望
  7. 大快人心!和P2P网贷彻底说再见
  8. 学fpga(在线verilog编程)
  9. 系统学习机器学习之总结(三)--多标签分类问题
  10. python 给字符串加颜色
  11. 使用 leastsq 对指定函数格式进行最小二乘拟合
  12. 单反相机tf卡用sd卡套稳定吗_存储卡可不是插上就能用 单反相机的使用细节
  13. delphi基本语法(摘自博主:沈金强)
  14. 不惑之年一次性通过软考高项的苦与乐
  15. Unity连接Photon
  16. python笔记6-python官方文档之format()格式化详解
  17. Python sklearn机器学习各种评价指标——Sklearn.metrics简介及应用示例
  18. 驾考计算机播报原理,驾考科二电脑语音提示
  19. Keil.STM32F1xx_DFP.2.4.0.pack
  20. Javascript日期和时间戳(毫秒/秒)相互转化,日期分隔符不同转化结果不同

热门文章

  1. 关于FIN_WAIT1
  2. GYM 100827 I.Salary Inequity(线段树)
  3. Java数据类型和运算符
  4. JS字符串切割 data.split();
  5. vue点击实现箭头的向上与向下
  6. RNN及变体LSTM、GRU(在NILM中的应用)
  7. 企业CDN缓存 varnish--varnish的基本搭建(1)
  8. Pycharm2020.1安装中文语言插件教程,不需要汉化
  9. addClass与className的区别
  10. 空洞卷积atrous/dilated convolution