堆栈一般是用来保存变量之类的东西(静态变量在内存中,虽然堆栈就是内存的一部分,但为了防止歧义,还是分成两部分来说),一般情况下没必要去故意读取堆栈的值,变量用变量名就可以直接访问,但我曾经想要读取函数返回后代码继续执行的地址,因此想到了来读取堆栈(函数调用时,会向堆栈中压入参数和下一个代码执行的地址,这样就可以在函数返回后继续执行)。

先来测试一下我们能否读取堆栈:

#include<stdio.h>
int main()
{volatile int a=24;/*设置一个我们要读取的变量,volatile 可以告诉gcc不要优化这行代码,仅对变量有效*/volatile int b[2]={1,2};/*建立一个数组,这个数组是关键,这时b作为数组指针,指向第一个元素,即
1在堆栈中的储存位置,因此我们就可以利用b来读取堆栈的任意位置(该程序所拥有的堆栈)*/volatile int c=b[2];printf("%dn",c);//打印出指定位置堆栈的值
return 0;
}

当然,如果不设定编译器的参数,这样的代码可能是不会编译通过的(注意:可能),命令如下:

gcc -Wno-unused -m32 -S -O0 -o test.s test.c

源文件名为test.c,参数说明:

-Wno-unused:不警告未使用的变量(上面的程序不需要,但为了方便自己分析,放在这里)

-m32:编译为32位程序

-S:编译为汇编文件

-O0:优化等级为0

-o:重命名输出文件

现在让我们看看汇编文件是什么样的:

  .file   "test.c".def  ___main;    .scl    2;  .type   32; .endef.section .rdata,"dr"
LC0:.ascii "%d120".text.globl _main.def   _main;  .scl    2;  .type   32; .endef
_main:
LFB10:.cfi_startprocpushl   %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl  %esp, %ebp.cfi_def_cfa_register 5pushl  %esipushl   %ebxandl    $-16, %espsubl  $32, %esp.cfi_offset 6, -12.cfi_offset 3, -16call   ___mainmovl $24, 28(%esp)        //将24存入堆栈,位置是28+esp的值movl  $1, %ebx            //1存入ebxmovl    $2, %esi            //2存入esimovl    %ebx, 20(%esp)        //1存入20(%esp)movl %esi, 24(%esp)        //2存入24(%esp)movl 28(%esp), %eax        //将28(%esp)的值存入eax,这里对应的代码就是c=b[2],即将24存入了eaxmovl %eax, 16(%esp)        //剩下的就是将参数压入堆栈,然后调用printf,这里不再解释movl    16(%esp), %eaxmovl  %eax, 4(%esp)movl   $LC0, (%esp)call    _printfmovl $0, %eaxleal    -8(%ebp), %esppopl  %ebx.cfi_restore 3popl  %esi.cfi_restore 6popl  %ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc
LFE10:.ident    "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0".def  _printf;    .scl    2;  .type   32; .endef

通过汇编的内容,我们可以看出可以使用一个数组来访问有效堆栈内的全部内容(超出堆栈界限会引发错误)。输出结果如下:

当调用一个函数时(使用call指令),压入参数的同时会压下一个代码的地址,使函数返回后可以继续执行。现在来尝试获取这个地址,代码如下:

#include<stdio.h>
void fun()
{volatile int a[1];/*设置一个数组,使用这个数组来访问堆栈*/a[0]=14;printf("a[4]=%dn",a[4]);/*打印出call指令压入的地址,这里很有意思,我之前以为这个地址在
a[2],a[0]=14,a[1]是esp的值(C语言中所有函数的开头都会有push ebp的代码,将ebp的值保存进堆栈,然后
将esp保存进ebp),但实际上发现总会有两个不知名的值占据着a[2],a[3]的位置,具体可以参见汇编代码*/goto *(a[4]);/*使用goto语句可以让程序跳向任何合法地址,goto不仅可以用标号或者行号,还可以是任
何void*型的变量(前提是程序可以访问该地址),goto会被程序翻译为jmp指令,而(*(void(*)
(void))0x100000)();这样的跳转方式将会被翻译为call指令,会使堆栈中多出一个地址,具体要使用哪个需要
参考实际。*/
}
int main()
{fun();
printf("hello");/*理论上如果上面的goto生效,那么hello将会被执行两次(调用fun函数时,堆栈被压入该
地址,然后使用了goto后,跳转到这里执行一次,打印出一个hello,在下面的return 0;语句中,程序会认为当
前还在fun函数,毕竟堆栈中的地址还没有释放,因此重新返回到这里,再执行一次),否则由于地址错误,程
序将被迫退出,不会在控制台看到hello*/
return 0;
}

下面是实际执行的情况,可见我们确实得到了之前压入的那个地址:

以下是上面的那个程序的汇编程序:

   .file   "test.c".section .rdata,"dr"
LC0:.ascii "a[4]=%d120".text.globl   _fun.def    _fun;   .scl    2;  .type   32; .endef
_fun:
LFB10:.cfi_startprocpushl   %ebp            //将ebp的值送入堆栈.cfi_def_cfa_offset 8.cfi_offset 5, -8movl  %esp, %ebp.cfi_def_cfa_register 5subl   $40, %esp        //空出40字节的位置用来储存变量movl  $14, -12(%ebp)    //14存入a[0],所以a的值即是ebp偏移12个字节,
//可以推断出a[1]在-8(%ebp),a[2]在-4(%ebp),a[3]在0(%esp),所以a[3]是之前保存的ebp的值
//那么a[4]就是call指令保存的值,这里比较令人好奇为什么a[0]在-12(%ebp)movl   4(%ebp), %eax    movl   %eax, 4(%esp)movl   $LC0, (%esp)call    _printfmovl 4(%ebp), %eaxjmp    *%eax                //goto被翻译为jmp指令,然后跳向了我们指定的地址.cfi_endproc
LFE10:.def  ___main;    .scl    2;  .type   32; .endef.section .rdata,"dr"
LC1:.ascii "hello0".text.globl    _main.def   _main;  .scl    2;  .type   32; .endef
_main:
LFB11:.cfi_startprocpushl   %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl  %esp, %ebp.cfi_def_cfa_register 5andl   $-16, %espsubl  $16, %espcall   ___maincall _funmovl    $LC1, (%esp)call    _printfmovl $0, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc
LFE11:.ident    "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0".def  _printf;    .scl    2;  .type   32; .endef

所以这里验证了我们可以通过操作数组来读取堆栈。

函数使用了堆栈的字节超过_在C语言中如何访问堆栈?相关推荐

  1. 函数使用了堆栈的字节超过_单片机地址空间,堆栈理解

    data –可寻址片内ram 0x00-0x7f bdata-可位寻址片内ram idata-可寻址片内ram,允许访问全部内部ram 0x00-0xff padata-分页寻址访问片外ram xda ...

  2. c语言读取文件字节数,怎么在C语言中利用fstat函数获取文件的大小

    怎么在C语言中利用fstat函数获取文件的大小 发布时间:2021-01-22 17:03:17 来源:亿速云 阅读:110 作者:Leah 怎么在C语言中利用fstat函数获取文件的大小?针对这个问 ...

  3. 在c语言中log函数的作用,C++_在C语言中使用对数函数的方法,C语言log()函数:返回以e为底的 - phpStudy...

    在C语言中使用对数函数的方法 C语言log()函数:返回以e为底的对数值头文件: #include log() 函数返回以 e 为底的对数值,其原型为: double log (double x); ...

  4. C语言中队列、堆栈、内存映射、多线程概念

    队列:先近先出: 栈:先近后出:栈的大小是由编译器决定的,默认大小是1M,可以更改,但是一般不建议修改,每个exe都有一个栈,无法利用较大内存,用完立刻回收:栈是自动回收内存:堆必须手动释放: 栈区存 ...

  5. golang go语言_在Go语言中无需反思即可使用Lodash的好处

    golang go语言 by Tal Kol 通过塔尔科尔 在Go语言中无需反思即可使用Lodash的好处 (The benefits of using Lodash in the Go langua ...

  6. c语言中文网_在C语言中使用中文字符

    大部分C语言教材对中文字符的处理讳莫如深,甚至只字不提,导致很多初学者认为C语言只能处理英文,而不支持中文.其实C语言是一门全球化的编程语言,它支持世界上任何一个国家的语言文化,包括中文.日语.韩语等 ...

  7. java语言变量分为_在Java语言中变量分为四种,分别是___________________________________________。_学小易找答案...

    [填空题]One day, at the registrar's office of a college, I noticed how parents are behaving with their ...

  8. r语言 rgl 强制过程中_一个R语言中操纵矢量空间数据的标准化工具—sf

    ​注: 本文是R语言sf包的核心开发者和维护者--来自德国明斯特大学的地理信息学教授:Edzer Pebesma 的一篇关于sf包的简介,发表于2018年7月的R语言期刊,主要讲述了sf的定位.功能. ...

  9. 在c语言中函数的定义变量的值为,变量定义(C语言中变量的声明和定义)

    变量定义(C语言中变量的声明和定义),哪吒游戏网给大家带来详细的变量定义(C语言中变量的声明和定义)介绍,大家可以阅读一下,希望这篇变量定义(C语言中变量的声明和定义)可以给你带来参考价值. 3.函数 ...

最新文章

  1. 彻底搞懂基于LOAM框架的3D激光SLAM全套学习资料汇总!
  2. 一点想法--- 做一个轻便的程序编辑器
  3. 酱油和gbt酱油哪个好_酱油不是越贵越好!找到这3个关键词,轻松避开勾兑酱油...
  4. SAP Webclient UI和Fiori UI的混搭
  5. android恢复联系人,如何从Android手机恢复联系人[最佳方式]
  6. 大神齐聚,算法大赛复赛晋级名单揭晓!
  7. docker 容器的常用命令及配置
  8. 定点乘法运算之原码一位乘法
  9. 什么是真正的程序员:A Little Printf Story
  10. hadoop component summary
  11. jquery mobile 中文在线文档
  12. 使用开放的showapi接口小技巧
  13. SQL SERVER代理的权限设置
  14. TCP-IP详解:SACK选项(Selective Acknowledgment)
  15. Python统计学01——数据可视化
  16. web前端开发常用的10个高端CSS UI开源框架
  17. 【进阶】使用Excel进行回归分析,预测真实值
  18. 美容美发美甲店做活动效果提升30%的营销方案18个套路
  19. 电流镜自动布局 布局对称性: 量化和应用以消除非线性过程梯度
  20. Window应急响应(六):NesMiner挖矿病毒

热门文章

  1. 10 | 递归:如何用三行代码找到“最终推荐人”?
  2. TCP/IP协议模型
  3. cmd pc如何开多个微信_抖音打击刷赞刷粉,240多个百万粉丝大V被封;微信PC版再更新...
  4. 适用于ELment-UI级联多选框,数据回填,根据子节点的值查找完整路径
  5. zookeeper 安装和使用
  6. 「BZOJ2200」[Usaco2011 Jan] 道路和航线 - 最短路+拓扑排序
  7. Tornado 自定义session,与一致性哈希 ,基于redis 构建分布式 session框架
  8. 用IIS配置反向代理
  9. poj 2503 Trie树
  10. debian添加删除用户