1 寄存器
EAX : 累加器,加减和比较运算都借助 EAX 来达到指令优化的效果,乘除必须在 EAX 中进行。
EDX:数据寄存器,EAX 的延伸。
ECX:计数器
ESI:源变址寄存器,存储输入数据流位置信息,“读”
EDI:目的变址寄存器,指向相关数据操作结果存放位置,“写”
ESP:栈指针,始终指向函数栈的最顶端
EBP:基址指针,被用于指向函数栈的最顶端
EBX:通用寄存器
EIP:始终指向当前正在执行的指令
2 函数调用堆栈的过程
(引用开始:https://blog.51cto.com/u_6871414/5896878)
main 函数 :
- 将调用 main 的栈底地址入栈。
- 让原本指向调用方栈底的 ebp(栈底指针)指向当前函数 main 的栈底。
- 给当前函数 main 开辟栈帧。
- 对开辟的栈帧进行初始化。
- 将三个局部变量放入栈中。
- 开辟 sum 函数的形参内存,从右往左,依次把 b 的值,a 的值入栈。
- 将 main 函数的下一行指令入栈,然后调用函数 sum
sum 函数:
- 将 main 的栈底地址入栈。
- 让原本指向 main 函数栈底的 ebp(栈底指针)指向当前函数 sum 的栈底。
- 给当前函数 sum 开辟栈帧。
- 对开辟的栈帧进行初始化。
- 将形参变量放在 eax 寄存器中。
- 将 eax 寄存器中的值赋值给了局部变量 tmp。
- 局部变量的值,通过 eax 寄存器返回。
- 出栈,并把出栈元素赋值给 ebp(栈底指针),因为调用 sum 函数刚开始时,把 main 函数的栈底地址放入栈中,出栈操作,让 ebp(栈底指针)指向了 main 函数的栈底地址。
- 最后,函数结束 ret 指令干了两件事:先出栈;再将出栈的值放到 CPU 的 PC 寄存器中。因为 PC 寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行。
补充:
C 语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。
总结:
1、函数的运行都是在栈上开辟内存。
2、栈是通过 esp(栈顶指针)、ebp(栈底指针)两个指针来标识的。
3、对于栈上的访问都是通过 ebp(栈底指针)的偏移来访问的。
4、在调用一个函数时,有两件事情要做:先将调用函数的下一行指令的地址压入栈中;再进行跳转。
5、在函数调用时检查函数是否申明、函数名是否相同、函数的参数列表是否匹配、函数的返回值多大。
-
① 如果 【函数的返回值<=4 个字节】,则返回值通过寄存器 eax 带回。
-
② 如果 【4<函数的返回值<=8 个字节】,则返回值通过两个寄存器 eax 和 edx 带回。
-
③ 如果 【函数的返回值>8 个字节】,则返回值通过产生的临时量带回。
6、函数结束 ret 指令干了两件事:先出栈;再将出栈的值放到 CPU 的 PC 寄存器中。因为 PC 寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行。
(引用结束:https://blog.51cto.com/u_6871414/5896878)
3 断点
3.1 软断点
一条常见的指令
0x44332211: 8BC3 MOV EAX, EBX
#指令地址 操作码 汇编指令
为了在此设置一个软断点,使得 CPU 执行到此能够停止,没错,需要中断指令 INT3。这里采用的方式是将双字节操作码替换为 INT3 中断指令(0xCC),设置成为断点后:
0x44332211: CCC3 MOV EAX, EBX
#指令地址 操作码 汇编指令
注意这里操作码发生了变化
注意概念: 指令的机器码 = 操作码 + 操作数
当程序执行至此触达中断后,调试器会捕获这个事件,接着调试器会检查 EIP 时候正指向一个此前被我们设置了断点的内存地址,如果这个地址在调试器内部断点列表中,那么调试器就会将之前存储的操作码字节数据写回对应内存地址。当程序恢复后,正确的操作码将被执行。下一次再运行程序将不会有该断点的信息。
以上是一次性断点的操作,而永久性断点还会再多做一步:在 CPU 执行完正确操作码后将断点恢复,也就是保留断点状态。下一次再运行程序该断点依然存在。
但是需要注意一个问题,当你修改了可执行程序的某个字节数据后,也就改变了他的 CRC(循环冗余校验)值。CRC 用于校验一个程序是否被篡改,一些程序会在程序执行前检验程序是否被篡改,还有一些软件往往会检测自己在内存中运行代码的 CRC,一旦检测被篡改就会立即自行崩溃,这是一个有效防御软断点的技术。
3.2 硬件断点
硬件断点是通过位于 CPU 上的一组特殊寄存器来实现的,称为调试寄存器。比如 x86 架构的 CPU 上有 8 个调试寄存器(DR0-DR7),分别用于设置和管理硬件断点。
- DR0-DR3 负责存储硬件断点的内存地址,所以最多只能同时使用 4 个硬件断点。
- DR4 和 DR5 保留使用。
- DR6 为调试状态寄存器,记录上一次断点触发所产生的调试事件类型信息。
- DR7 是硬件断点的激活开关,存储着各个断点的触发信息条件。
与软断点不同的是,硬件断点使用 1 号中断(INT1)实现,INT1 一般被用于硬件断点和单步事件。
CPU 每次试图执行一条指令时,都会首先检查当前指令所在地址是否被设置了有效的硬件断点,除此之外还会检查当前指令包含的操作数是否位于被设置了硬件断点的内存地址。
3.3 内存断点
内存断点本质上不是一个真正的断点。当调试器设置一个内存断点时,实际上是改变一个内存区域或一个内存页的权限。操作系统对内存页会设置访问权限,可执行、可读、可写、保护页,这些访问权限可以组合。
保护页的特性可以帮助我们实现断点机制。
任何对于有页保护的区域的内存访问都会导致 CPU 暂停执行当前进程并处发一个保护页调试异常,然后我们就可以对访问缓冲取得指令代码进行仔细的检查,并判断出应用程序如何处理缓冲区中的内容。
内存断点可以绕过软断点的指令篡改问题。
标签:函数,基础知识,指令,内存,寄存器,栈底,断点 From: https://www.cnblogs.com/Zyecho/p/17297141.html