①函数参数和函数返回值 ②临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量) ③保持上下文信息(包括在函数调用前后需要保持不变的寄存器)
函数是任何一门高级语言中必须要存在的,使用函数式编程可以让程序可读性更高,充分发挥了模块化设计思想的精髓,今天我将带大家一起来探索函数的实现机理,探索编译器到底是如何对函数这个关键字进行实现的,并使用汇编语言模拟实现函数编程中的参数传递调用规范等。
✨作者:@平凡的人1 ✨专栏:《C语言从0到1》 ✨一句话:凡是过往,皆为序章 ✨说明: 过去无可挽回, 未来可以改变 📷 ---- 目录 前言😄 什么是栈🔑 什么是函数的栈帧🔑 认识相关寄存器和汇编指令🔑 寄存器🔥 相关的汇编指令:🔥 函数的调用堆栈🔑 函数栈帧的创建🔑 分析栈帧的创建:💧 为什么会出现“烫烫烫”:💧 分析main函数中的核心代码:💧 分析Add函数的传参💧 函数调用过程💧 函数栈帧的销毁下🔑 结语✍ ---- 前言😄 好的,各位,我们前面就已经学过函数的一些相关知识了
函数调用是编程语言都有的概念,也许你听说过函数调用栈,但是大家都知道函数调用是如何完成的吗?我们为什么要了解这个过程:
最近发现有很多漏洞利用或木马程序样本会通过一些技术手段,达到使自动化检测系统或分析人员调试工具的栈回溯机制失效的目的。在本文中我将会简单分析和推测一下这类恶意样本都是通过哪些套路来实现和栈回溯机制的对抗。需要注意的是,文中讨论的堆栈都是代指线程在用户层的堆栈,并未涉及内核层的堆栈。
C语言的使用是面向过程的, 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。所以C语言的程序都是以函数作为基本单位的,如果能够深入理解函数,无疑对于c语言会有更深刻地理解,修炼自己的内功,那么函数是如何调用的?函数返回值是如何返回的?函数的形参是如何传递的…………等等的问题,其实都和函数栈帧有关系!
本文稿费80软妹币 砸个广告:各位在网络安全方面有新创作的小伙伴,快将你们的心得砸过来吧~ 文章以word形式发至邮箱: minwei.wang@dbappsecurity.com.cn 有偿投稿,
函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:
我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。 那函数是如何调用的?函数的返回值又是如何返回的?函数参数是如何传递的?这些问题都和函数栈帧有关系。
函数栈帧的创建和销毁是程序执行过程中的核心环节,它们直接影响了程序的运行效率和内存管理。在深入探讨这两个过程之前,我们需要先理解什么是函数栈帧。
带着以下一个问题来探索: (1)形参的内存空间的开辟和清理是由调用方还是由被调用方执行的? (2)主函数调用函数结束后,主函数从哪里开始执行?从头开始还是从调用之后开始? (3)返回值是如何带出来的?
一个限定表尾进行删除(出栈)和插入(入栈)操作的线性表,其过程类似与压子弹与退子弹(后进先出)。 一个由系统自动分配的内存空间,譬如调用函数、创建临时变量时内存空间的创建与销毁。 用于存储函数内部的局部变量、方法调用、函数传参数值等。 由高地址向低地址生长。
帧指针使得访问函数的参数很容易。所以任何函数调用进来的第一件事都是保护调用者的帧指针,以使得返回时可以恢复调用者的帧指针,
每个函数调用都对应一个栈帧。每个栈帧由ESP和EBP寄存器来确定。每个函数执行时,其局部变量都是在自己对应的栈帧内分配内存。假设A函数调用B函数,此时正在执行B函数,需要指出的是,当执行完当前函数B后,返回调用函数A,此时执行函数B时,为B函数的局部变量分配的的内存空间也就不存在了。也就是说,函数返回值不能是函数体内局部变量的地址,也不能是局部变量的引用。即如不能出现如下两种形式之一:
程序的运行需要数据,而数据就存放在内存中。首选的存放地址肯定是寄存器中(运行速度快),但是寄存器也就几个,数据很多,所以就把数据存放在了堆栈中。
C语言作为面向过程的语言,函数是其中最重要的部分,同时函数也是C种的一个难点,这篇文章希望通过汇编的方式说明函数的实现原理。
这段代码包含两个函数,因此可以测试函数调用,此外还包含了静态变量、局部变量、返回值等
这绝对不是标题党。而是C/C++开发中你必须要掌握的基础知识,也是高级技术岗位面试中高频题。我真的真的真的希望无论是学生还是广大C/C++开发者,都该掌握此文中介绍的知识。
我们用下面的C代码来研究函数调用的过程。 int bar(int c, int d) { int e = c + d; return e; } int foo(int a, int
最近在学习C语言的过程中遇到了一些问题,在询问老师和查询相关资料的基础上了解到了函数栈帧的相关概念,对下列问题也有了答案。
eax,ebx,ecx,edx,ebp,esp。画横线的这两个寄存器存放的是地址。这两个地址是用来维护函数栈帧的。
将想到的烧烤食物写在便条上,一个食材一个便条,最先想到的食材写在便条上后,放在最下面,依次往上放,最后想到的写在便条上后,放在最上面。
今天,我们来通过反汇编看一下函数调用的过程(顺便学习下汇编),如下图,为一个函数调用的例子。主函数里面调用了test()函数。
马马虎虎学完了Python课程,一直想学下linux,看到里面有个linux的就选上了。当初没细看,如今听完第一节课有点傻眼,竟然糊里糊涂给自己找了一科汇编语言的课程,静心看下去庆幸自己还知道点堆栈的知识并出现了轻微的自虐倾向。闲话少说,现开正题。注:本文具有总结兼作业性质,如有雷同,纯属巧合。
http://blog.csdn.net/jnu_simba/article/details/25158661
注:本文章所使用的编译器是VS2010,由于不同编译器的函数栈帧与销毁略有差异,所以具体细节请读者自行实践!
栈是一种线性存储的数据结构,向下增长。其存在栈底和栈顶,栈对其中的数据元素有进栈和出栈的操作,遵循‘First In last Out’即FILO原则。
函数调用的过程实际上也就是一个中断的过程,那么C++中到底是怎样实现一个函数的调用的呢?参数入栈、函数跳转、保护现场、回复现场等又是怎样实现的呢?本文将对函数调用的过程进行深入的分析和详细解释,并在VC 6.0环境下进行演示。分析不到位或者存在错误的地方请批评指正,请与作者联系。
栈是计算机科学里最重要的且最基础的数据结构之一。 从技术上讲,栈就是CPU寄存器里面的某个指针所指向的一片内存区域。这里所说的某个指针通常位于x86/x64平台的ESP寄存器/RSP寄存器,以及ARM平台的SP寄存器。 操作栈最常见的指令是PUSH和POP,在 x86 和 ARM Thumb 模式的指令集里都有这两条指令。 PUSH指令会对ESP/RSP/SP寄存器的值进行减法运算,使之减去4(32位)或8(64位),然后将操作数写到上述寄存器里的指针所指向的内存中。 POP指令是PUSH的逆操作:他先从栈指针(Stack Pionter,上面三个寄存器之一)指向的内存中读取数据,用以备用(通常是写到其他寄存器里面),然后再将栈指针的数值加上4或8. 在分配栈的空间之后,栈指针,即Stack Pointer所指向的地址是栈的底部。PUSH将减少栈指针的数值,而POP会增加它的数值。栈的“底”实际上使用的是整个栈的最低地址,即是整个栈的启始内存地址。 ARM的栈分为递增栈和递减栈。递减栈(descending stack)的首地址是栈的最高地址,栈向低地址增长,栈指针的值随栈的增长而减少,如STMFA/LMDFA、STMFD/LDMFD、STMED、LDMEA等指令,都是递增栈的操作指令。
注: 本次讲解使用的是vs2013,不要使用太高级的编译器,越高级的编译器,越不容易学习和观察;同时,在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。
这篇文章是从头贯穿到尾的,让你更加详细的了解函数是什么样在内存里创建,怎么样销毁的,相信家人们读完这篇文章之后能让你眼里的代码变得透明起来(本章不需要过多了解汇编语言,重点是了解函数栈帧怎么创建和销毁的) 注意:这里我们用的是32位平台,用VS2013作为参考。
《缓冲区溢出攻击实践》以实践者角度介绍了初级缓冲区溢出攻击方法,本文从原理上对该方法做原理性介绍。
对于vs来说__tmainCRTStartup函数也由其他函数调用(取决于编译器)
首先从main函数开始分析,18行将ebp推入栈中,19行中将esp的值赋给ebp(左边值赋给右边),20行开辟4个字节(32位)的栈空间给后面的局部变量7。函数传递传递参数的方式有三种:堆栈方式、寄存器方式、以及通过全局变量进行隐含参数的传递。这里是利用堆栈传递参数,堆栈是一种“后进先出”的存储区,栈顶指针ESP指向堆栈中第一个可用的数据项。21行传入7,22行然后调用f函数,call指令可以看成是执行了
函数栈帧的创建和销毁在所有编译器中都是大同小异的,不同的编译器会有不同的方式,但是了解到了简单的底层的这些方法后,其他的编译器都是在此基础上修饰,不必深究。
程序在内存中的存储分为三个区域,分别是动态数据区、静态数据区和代码区。函数存储在代码区,全局变量以及静态变量存储在静态数据区,而在程序执行的时候才会在动态数据区产生数据。程序执行的本质就是代码区的指令不断执行,驱使动态数据区和静态数据区产生数据变化。
如下为一个c语言程序中的函数及其在32位系统下编译得到的汇编语言程序代码,请详细说明每条汇编语句的意义,并将这个函数补充完整。
(1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中。物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间。地址空间大小与逻辑储存器大小不一定相等。
简单来说,栈 是一种 LIFO(Last In Frist Out,后进先出) 形式的数据结构。栈一般是从高地址向低地址增长,并且栈支持 push(入栈) 和 pop(出栈) 两个操作。如下图所示:
1、x86架构 x86架构是intel开发的一种32位的指令集。8个32位通用寄存器 eax,ebx,ecx,edx,ebp,esp,esi,edi。
压栈操作,他会改变esp所指向的位置,从而适应栈帧空间的扩大,操作方式就是将操作数直接压栈到栈帧空间
程序运行后的内存布局 : 从高地址 到 低地址 介绍, 顺序为 栈 -> 堆 -> bss段 -> data 段 -> text段 ;
通过寻找危险函数,我们快速确定程序是否可能有栈溢出,以及有的话,栈溢出的位置在哪里。常见的危险函数如下
的空间保存数据,用户地址空间3G从0x0000000到0xC0000000,内核空间1G从0xC0000000到0xFFFFFFFF。
的空间保存数据,地址从0x00000000到0xFFFFFFFF(一个十六进制为对应4个二进制位,所以是2的32次方)。
重大错误说明 : 栈顶的指针始终是指向最后一个入栈元素的位置的,不是最后一个入栈元素的位置上面!请读者留意 (PS : 后来又看了一下,好像也不是什么大问题...)
很简单,内联汇编使用asm(“.intel_syntax noprefix/n”)声明一下,以后的内联汇编就可以用intel风格了,构建可执行文件时给gcc加上-masm=intel参数。 先写一个小程序测试一下:
在上一篇博客 【Android 逆向】x86 汇编 ( 使用 IDA 解析 x86 架构的动态库文件 | 使用 IDA 打开动态库文件 | IDA 中查找指定的方法 ) 中 , 使用 IDA 反编译 Android SDK 中的 D:\001_Develop\001_SDK\Sdk\build-tools\26.0.3\renderscript\lib\intermediates\x86\libc.so 文件 , 并查找其中的 fork 方法 ;
领取专属 10元无门槛券
手把手带您无忧上云