从汇编层看64位程序运行
文章平均质量分 88
专栏《从汇编层看64位程序运行》将深入剖析64位程序在汇编层面的运行机制。我们将从底层指令集出发,探讨64位架构带来的性能提升与内存管理优势,同时解析汇编代码如何直接影响程序性能。本专栏旨在为读者提供一个理解高级编程语言与底层硬件之间桥梁的视角。
breaksoftware
这个作者很懒,什么都没留下…
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
从汇编层看64位程序运行——likely提示编译器的优化案例和底层实现分析
我们在一文中介绍了likely提示编译器进行编译优化,但是我们又讲了最终优化不是对分支顺序的调换,那么它到底做了什么样的优化,让整体性能提升20%呢?原创 2024-09-03 00:15:00 · 1356 阅读 · 0 评论 -
从汇编层看64位程序运行——C++的Copy Elision(复制省略)技术的实现
Copy Elision(复制省略)技术构造的对象位于caller的栈帧中。Copy Elision(复制省略)技术构造的对象在callee中构造。Copy Elision(复制省略)技术构造的对象在calller中析构。Copy Elision(复制省略)技术将预先分配好的地址通过rdi寄存器传递给callee进行对象构造。原创 2024-08-27 00:15:00 · 1499 阅读 · 0 评论 -
从汇编层看64位程序运行——安全的ROP攻击以控制程序执行流程
在一文中,我们介绍了如何使用ROP攻击来修改程序执行流程。但是这个方案存在一个问题,就是中所说的,会导致foo回到main函数后,RSP寄存器的值会比正确的大0x08(即栈缩小了0x08)。这是因为整个方案call了一次,ret了两次,即压栈一次,退栈两次。虽然不会导致程序执行出现问题,但是终究是不完美的。原创 2024-08-13 00:15:00 · 737 阅读 · 0 评论 -
从汇编层看64位程序运行——有惊无险的栈溢出
在一文中,我们通过“篡改”函数的返回地址来达成修改程序执行流程的目的。但是这个方案存在一个问题:它会导致栈溢出。这是因为ret指令会导致栈的pop,call指令会导致栈的push。而案例中,foo7调用foo函数时,使用的是ROP攻击方法——没有使用call指令。这样就导致整个流程call了1次(main–>foo7),即压栈1次;而ret了2次,foo7一次,foo一次。在foo7进入foo之后,我们修改了foo的返回地址。这个地址是0x7fffffffdef8,而它属于main函数的栈帧。原创 2024-08-12 00:15:00 · 1563 阅读 · 0 评论 -
从汇编层看64位程序运行——栈保护
在中,我们看到可以通过“微操”栈空间控制程序执行流程。现实中,黑客一般会利用栈溢出改写Next RIP地址,这就会修改连续的栈空间。而编译器针对这种场景,设计了“栈保护”机制。原创 2024-07-17 00:30:00 · 1175 阅读 · 0 评论 -
从汇编层看64位程序运行——ROP攻击以控制程序执行流程
一般我们运行有调用关系的代码,就像套娃一样,一个套着一个。比如main函数调用foo7函数,foo7调用foo函数,则main函数要先进入foo7,然后进入foo。等foo执行完,会回到foo7;等foo7执行完,再回到main。那么我们有办法脱离这种套娃模式,然后foo函数执行返回到main函数吗?当然是有的。这就需要使用ROP攻击。ROP攻击全称是:Return Oriented Programming (ROP) attacks。原创 2024-07-17 00:15:00 · 1641 阅读 · 0 评论 -
从汇编层看64位程序运行——所见非所写(编译器的优化),别做无用代码优化
我们发现函数的输入和输出对编译器的优化没有什么影响。但是最后一个例子我们发现,一个函数如果对外部产生影响,则其内部逻辑才不会被抛弃。编译器会按照它认为最优的方式优化代码,导致我们的C语言代码和最终编译结果在逻辑层面有非常大的差别,但是最终执行结果却是一致的。所以很多时候,我们在优化代码的时候,要深入汇编层确认后再去做,否则可能做的就是无用功,因为聪明的编译器已经帮我们优化了很多代码了。原创 2024-07-16 00:30:00 · 1213 阅读 · 0 评论 -
从汇编层看64位程序运行——栈帧(Stack Frame)的组成
foo10函数的栈帧起始地址就是代码执行到+120处(+118行执行完,但是+120行没执行)时的rsp寄存器的值。这就意味着call指令压栈的Next RIP也属于子函数的栈帧,而通过栈向子函数传递参数的空间则属于上一个栈帧。比如在调用超过6个参数的函数时,子函数内部就会访问到上个栈帧中的数据,以获取第6个以外的其他参数。人们只是借用它来方便表述函数在运行过程中的栈的变化。之前+105,+109,+113和+117处的压栈空间,都是属于main函数的栈帧。这四篇的讲解,栈帧的组成基本清晰了。原创 2024-07-16 00:15:00 · 480 阅读 · 0 评论 -
从汇编层看64位程序运行——函数的调用和栈平衡
不知道有没有人想过一个问题:A函数调用B函数,B函数是如何知道在调用结束后回到A函数中的?比如下面的代码,main函数调用foo。当foo执行完毕,需要执行main函数的return 0语句。但是main和foo是割裂的,foo是怎么回到main函数中继续执行的呢?我们对上述代码进行反汇编。在main函数中,我们没有看到任何蛛丝马迹,只看到call指令。上图显示,当前代码执行到+8处(但是此行并没有执行,它的上一行执行了)。原创 2024-07-15 00:30:00 · 1631 阅读 · 0 评论 -
从汇编层看64位程序运行——栈上变量的rbp表达
如果发生了诸如0x00005555555553bb处的压栈行为,变量a的表达就要变成+0x10(%rsp),这对于汇编的阅读和编写会造成很大的麻烦。这样诸如-0x28(%rbp)表达变量a的方式,也可以通过+0x08(%rsp)的形式来表达。这是编译器让rsp的进行改变的,表达这个函数需要0x30的栈上空间存储局部变量(a,b,c,d,e,f,g,h,i,j)。我们让所有的变量初始化完成,然后再查看栈上变量的变化,可以看到栈上数值已经发生了改变。我们将所有的参数都压栈,然后观察rsp和rbp的变化。原创 2024-07-15 00:15:00 · 2090 阅读 · 0 评论 -
从汇编层看64位程序运行——参数传递的底层实现
一个参数时直接使用edi寄存器传递参数。两个参数时,参数分别通过edi、esi寄存器传递。三个参数时,参数分别通过edi、esi和edx寄存器传递。四个参数时,参数分别通过edi、esi、edx和ecx寄存器传递。五个参数时,参数分别通过edi、esi、edx、ecx和r8d寄存器传递。六个参数时,参数分别通过edi、esi、edx、ecx、r8d和r9d寄存器传递。参数不超过6个时,参数按需使用edi、esi、edx、ecx、r8d和r9d寄存器传递到子函数。原创 2024-07-13 00:30:00 · 2898 阅读 · 0 评论 -
从汇编层看64位程序运行——栈帧(Stack Frame)边界
在中,我们简单介绍了栈帧的概念,以及它和函数调用之间的关系。如文中所述,栈帧是一种虚拟的概念,它表达了一个执行中的函数的栈空间区域。在这个区域中,该函数的局部非静态变量便被置于这个空间中,即我们常常说起的栈上变量。那么这个区间是通过什么划分的?这就需要引入寄存器这个概念。寄存器(Register)是中央处理器内用来暂存指令、数据和地址的存储器。寄存器的存贮容量有限,读写速度非常快。在计算机体系结构里,寄存器存储在已知时间点所作计算的中间结果,通过快速地访问数据来加速计算机程序的执行。需要注意的是,原创 2024-07-13 00:15:00 · 1317 阅读 · 0 评论 -
从汇编层看64位程序运行——栈帧(Stack Frame)入门
然后我们给main函数下断点,让其断在main的起始代码处。此时foo函数没有被调用,于是只有main函数的栈帧——即只有一个栈帧。一个函数所有的局部变量以及一些辅助信息,构成了这个函数的栈帧。一文中,我们讲解了X86体系架构下,程序的栈结构的特点。栈帧是栈上一个抽象的结构,即栈上一个起始地址和截止地址中间的一段内存块。然后给foo函数下断点,让程序运行到这个函数的入口处,然后查看栈帧的变化。可以看到此时有两个栈帧,因为main函数和foo函数都在运行态。此时我们看到,栈帧又变成1个。原创 2024-07-12 01:15:00 · 751 阅读 · 0 评论 -
从汇编层看64位程序运行——程序中的栈(Stack)结构及其产生的历史原因
如果要讲程序在系统层的运行,一个绕不开的名词就是“栈”。所以深入理解“栈”是这个系列重要的基础。本文也将深入浅出,只讲明白程序运行中使用的栈是什么。原创 2024-07-12 01:00:00 · 1731 阅读 · 0 评论 -
从汇编层看64位程序运行——静态分析和动态分析入门
本文我们只是做一个简单的介绍,后续我们将使用这些工具以及其他更多的工具,来深入分析软件在系统上运行的情况,以期展现出计算机微观世界的精彩设计。原创 2024-07-11 20:46:48 · 1222 阅读 · 0 评论
分享