前言
🎈大家好,我是何小侠🎈
🍃大家可以叫我**小何或者小侠🍃**
💐希望能通过写博客加深自己对于学习内容的理解💐
🌸也能帮助更多人理解和学习🌸
花不尽,柳无穷。应与我情同。觥船一棹百分空。何处不相逢。
朱弦悄。知音少。天若有情应老。劝君看取利名场。今古梦茫茫。
— 宋代·晏殊《喜迁莺·花不尽》
这篇博客我们一起来简单的了解一下,在函数传参,调用过程中的种种细节,由于是汇编语言,我自己会用查找的资料和一些讲解或图片展示来
帮助大家理解,由于这个过程真的很难用博客来讲解,所以我可能讲的不是很好,希望大家谅解。
前言🍊
我们先让大家了解一下汇编与反汇编的区别:
词性 | 汇编 | 反汇编 |
名词 | 就是指汇编语言 | 指的是由机器语言经过反汇编过程生成的汇编语言。 |
动词 | 指的是将汇编语言指令转换为机器语言指令的过程。 | 指的是由已生成的机器语言(二进制语言)转化为汇编语言的过程,也可以说是汇编的逆向过程 |
那么我们再介绍一下什么是机器语言:机器语言是一种由计算机硬件直接执行的编程语言。
它使用二进制代码表示指令和数据,与计算机的底层硬件架构紧密相关
。
机器语言是计算机能够理解和执行的唯一语言,它包括了一系列的机器指令,每个指令对应着计算机硬件中的一个操作。
由于机器语言使用的是二进制代码,因此对人类来说很难直接阅读和编写,通常需要使用汇编语言或高级编程语言来进行编写和转换。
但是啊,重点来了:
反汇编可以将机器语言指令转换为汇编语言指令,也可以将高级语言程序转换为汇编语言代码。这取决于我们想要分析的程序是已经编译成机器语言的可执行文件,还是高级语言的源代码。
在调试过程中,高级语言通常会被编译成汇编语言代码,然后再被转换成机器语言指令执行。当我们有一个已编译的可执行文件时,反汇编可以将其中的机器语言指令转换为对应的汇编语言指令,使其更具可读性和可理解性。这对于逆向工程、代码审计和漏洞分析等任务非常有用。
另一方面,当我们有高级语言的源代码时,反汇编可以将其转换为汇编语言代码,以便更深入地理解程序的执行过程和指令的细节。这对于调试、性能优化和代码分析等任务非常有帮助。
总之,反汇编可以用于将机器语言转换为汇编语言,也可以将高级语言转换为汇编语言,具体取决于我们想要分析的程序的形式和需求。
而我们今天要介绍的,就是我们调试的过程中的反汇编。
越高级的编译器就越不容易观察,希望大家不要选择太新的编译器,VS2022是不行的.
寄存器与函数栈帧🍊
要了解函数栈帧就必须理解寄存器
由于32位和64位的寄存器差别很大,而且我也没有时间去了解64位的寄存器所以今天我们只讨论32位的。
EAX(Extended Accumulator)是累加器寄存器。它在计算过程中常用于存储临时数据、计算结果和函数返回值。
EBX(Extended Base)是基址寄存器。它通常用于存储指向数据段的指针,或者作为通用寄存器来存储数据。
ECX(Extended Counter)是计数器寄存器。它常用于循环计数和字符串操作,也可以用作通用寄存器。
EDX(Extended Data)是数据寄存器。它可以用于存储数据和执行I/O操作,也可以作为通用寄存器使用
大家只要看看就行,没必要现在就记住。
每个寄存器都能够存储一个32位的二进制数据(一个地址)。寄存器在计算机中被用来存储和处理指令和数据。也就是说32位下一个寄存器的大小就是4个字节。
由于栈顶是一个动态变化的,所以我们要知道栈顶指针会经常变动`
从标题可以看出我们要描述的是函数栈帧,那么函数栈帧到底是什么?
函数栈帧(Function stack frame)是指在程序执行过程中,每个函数被调用时所创建的一块内存区域,用于保存函数的局部变量、参数、返回地址等信息。这只是简单的描述,不敢太过于复杂。
大家只要记得是一块在栈区上所开辟的一块内存区域就行。
这里还有一个重要到底知识点,esp和ebp是用来维护函数栈帧的,也就是说,现在在调用哪个函数我esp,ebp就维护哪个函数的函数栈帧。
main函数居然被调用!🍊
我在Microsoft VC上展示的不是很全面:
这是调试过程中调用堆栈的例子:大家可能不是很理解我再换一张图:
这里就很明显的能看出特点,我们看到main函数是在Add函数的下面,也就意味着Add函数是后调用的,那么main函数下面还有一个函数,叫main
CRTStartup,说明这个函数其实是调用了main函数。但是不是这么简单
这里由于这个软件的原因,实际上是一个叫__tmainCRTStartup调用了main函数,__tmainCRTStartup是被mainCRTStartup调用的。
内存中的栈区🍊
这就是我们还未进入到main函数时栈区的情况,我们在开头介绍过栈顶其实是动态的,只要我们往栈区里面放东西,顶就会变高。
这里再补充说明一下我们所用的代码是这个:
push
现在我们调试起来转到反汇编,如果猜的没错你第一眼看到的就是push,
英文中的push是推动,压,强迫等意思,而在栈区这里的意思就是压栈,
压栈是指: 压栈就是把数据放如栈中,从栈顶放入。
既然说到压栈我们就说说出栈,出栈也是从栈顶取出。
而栈区具有有先进后出的特点!
就好像往盒子里放东西,先放入的东西后拿出来,后放入的东西先拿出来
我们来看看这个汇编代码
push ebp的含义是:
"push ebp"这一条汇编指令,用于将ebp寄存器的值压入栈中。 这个指令通常出现在函数的开头,用于保存上一个函数的栈帧指针。
我们知道ebp其实可以理解成指针变量,用来存放地址。
ebp指向的是栈底。
这里还有一个补充的知识:
我们知道内存里的变量能够取地址,但是寄存器是集成在CPU上的,不在内存当中。
具体的操作步骤如下:
- 将ebp寄存器的值压入栈中,此时ebp的值被保存在栈的顶部。
那为什么要这样做呢?通过"push ebp"指令,可以将上一个函数的栈帧指针保存在栈中,以便在当前函数执行完毕后能够正确返回到上一个函数的位置。
大家只要记得还有一个返回的作用就行了。
在 这条汇编代码执行后,
我们一再强调esp是指向栈顶的所以,esp和栈顶都是一起在动态变化的,而这个ebp保存的就是_tmainCRTSartup的地址。
然后我们再关注下一条指令,
move🍊
在汇编语言中,move(或者是mov)是一条指令,用于将数据从一个位置复制到另一个位置。它的语法通常是:
move destination , source
其中,destination表示目标位置,source表示源位置。这条指令将源位置的数据复制到目标位置。
我们在一开始就已经介绍过,ebp和esp都是指针,如果说
int * p = &a ,那么p中存放的就是a的地址。这里也一样,把esp的地址赋给ebp,也就意味着,ebp指向的位置改变了,但我们之前说这个ebp不是栈底指针吗?怎么还可以移动?别急请听我慢慢道来!
我们再给出图像
sub 🍊
在汇编语言中,sub是一条指令,用于执行减法操作。它的语法通常是:
sub destination, source
以下是一些示例:
将寄存器eax中的值减去立即数5,并将结果存储在eax中:
sub eax, 5
将寄存器ebx中的值减去寄存器ecx中的值,并将结果存储在ebx中:
sub ebx, ecx
也就是说虽然是减去但不是只有减去,还要减去之后放在一个寄存器中
那么这行代码呢?
4Ch是什么呢?
HEX:hexadecimal,十六进制的;
打开程序员计算器就可以看到
也就是说4C就是个16进制数,转换为十进制就是
4 * 16 ^ 1+12*16 ^ 0= 76
也就是说这行代码的意思是:将esp减去76,然后再放到esp,
也就是说esp这个栈顶指针要移动了,为什么是减去呢?
我们知道在栈区是先使用高地址再使用低地址的。
那么为什么要移动这么大一块空间,实际上就是为main函数开辟的函数栈帧。
我们再次给出栈区变化图。
这个-76个地址画的可能不太准确但是为了方便截图就这样简单画一下。