呢,另外PC 本身PC是不是特殊功能寄存器寄存器,和栈指针有关系吗

什么是堆栈,堆栈有何作用。程序设计时。为何要对堆栈指针Sp重新赋值,如果CPU在操作中使用2组工作寄存器,Sp初始值应该为多大?
var sogou_ad_id=731549;
var sogou_ad_height=160;
var sogou_ad_width=690; 上传我的文档
 上传文档
 下载
 收藏
粉丝量:27
该文档贡献者很忙,什么也没留下。
 下载此文档
PC 汇编入门指
下载积分:200
内容提示:PC 汇编入门指
文档格式:PDF|
浏览次数:19|
上传日期: 04:36:12|
文档星级:
全文阅读已结束,如果下载本文需要使用
 200 积分
下载此文档
该用户还上传了这些文档
PC 汇编入门指
关注微信公众号程序的内存布局——函数调用栈的那点事
[注]此文是《程序员的自我修养》的总结,其中掺杂着一些个人的理解,若有不对,欢迎拍砖。
程序的内存布局
现代的应用程序都运行在一个虚拟内存空间里,在32位的里,这个内存空间拥有4GB的寻址能力。现代的应用程序可以直接使用32位的地址进行寻址,整个内存是一个统一的地址空间,用户可以使用一个32位的指针访问任意内存位置。
在进程的不同地址区间上有着不同的地位,Windows在默认情况下会将高地址的2GB空间分配给内核,而默认将高地址的1GB空间分配给内核,具体的内存布局如下图:
(1)代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行。
(2)数据区:用于存储全局变量、常量。
(3)堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点。
(4)栈区:用于动态地存储函数之间的关系,以保证被调用函数在返回时恢复到母函数中继续执行。
高级语言写出的程序经过编译链接,最终会变成可执行文件。当可执行文件被装载运行后,就成了所谓的进程。
可执行文件代码段中包含的二进制级别的机器代码会被装入内存的代码区(.text);
处理器将到内存的这个区域一条一条地取出指令和操作数,并送入运算逻辑单元进行运算;
如果代码中请求开辟动态内存,则会在内存的堆区分配一块大小合适的区域返回给代码区的代码使用;
当函数调用发生时,函数的调用关系等信息会动态地保存在内存的栈区,以供处理器在执行完被调用函数的代码时,返回母函数。
如果把计算机看成一个有条不紊的工厂,我们可以得到如下类比:
* CPU是干活的工人。
* 数据区、堆区、栈区等则是用来存放原料、半成品、成品等各种东西的场所。
* 存放在代码区的指令则告诉CPU要做什么,怎么做,到哪里去领原材料,用什么工具来做,做完以后把成品放到哪个货仓去。
在经典的操作系统里,栈总是向下增长的。栈顶由esp寄存器定位。压栈操作使栈顶的地址减小,弹出操作使栈顶地址增大。
当函数调用的时候发生了什么?
int main(void)
foo(1,2,3) ;
return 0 ;
当方法main需要调用foo时,它的标准行为:
1、在main方法的调用栈中,将 foo的参数从右向左 依次push到栈中。
2、把main方法当前指令的 下一条指令地址 (即return address)push到栈中。(隐藏在call指令中)
3、使用call指令调用目标函数体foo。
请注意,以上3步都处于main的调用栈,其中ebp保存其栈底,而esp保存其栈顶。
接下来,在foo函数中:
1、push ebp: 将ebp的当前值push到栈中,即保存ebp。
2、mov ebp,esp: 将esp的值赋给ebp,则意味着进入了foo方法的调用栈。
3、[可选]sub esp, XXX: 在栈上分配XXX字节的临时空间。(抬高栈顶)(编译器根据函数中的局部变量的总大小确定临时空间的大小)
4、[可选]push XXX: 保存(push)一些寄存器的值。
【注意:push寄存器的值,这一操作,可以在分配临时空间之前,也可在其之后,《程序员的自我修养》写的是在开辟临时变量之后】
(编译器中保存的有相应的变量名对应的临时空间中的位置)
而在foo方法调用完毕后,便执行前面阶段的逆操作:
1、保存返回值: 通常将函数的返回值保存在寄存器eax中。
2、[可选]恢复(pop)一些寄存器的值。
3、mov esp,ebp: 恢复esp同时回收局部变量空间。(恢复原栈顶)
4、pop ebp: 将栈顶的值赋给ebp,即恢复main调用栈的栈底。(恢复原栈底)
5、ret: 从栈顶获得之前保留的return address,并跳转到此位置继续执行。
main方法先将foo方法所需的参数压入栈中,然后再改变ebp,进入foo方法的调用栈。
因此,如果在foo方法中需要访问那些参数,则需要根据当前ebp中的值,再向高地址偏移后进行访问&&因为高地址才是main方法的调用栈。
也就是说,地址ebp + 8存放了foo方法的第1个参数,地址ebp + 12存放了foo方法的第2个参数,以此类推。那么地址ebp + 4存放了什么呢?它存放的是return address,即foo方法返回后,需要继续执行下去的main方法指令的地址。
若需在函数中保存被调函数保存寄存器(如ESI、EDI),则编译器在保存EBP值时进行保存,或延迟保存直到局部变量空间被分配。在栈帧中并未为被调函数保存寄存器的空间指定标准的存储位置。
【注:几个相关的寄存器(关于详细的介绍,见王爽汇编)】
(1)esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。(ebp在当前栈帧内位置固定,故函数中对大部分数据的访问都基于ebp进行)
(3)eip:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。 可以说如果控制了EIP寄存器的内容,就控制了进程&&我们让eip指向哪里,CPU就会去执行哪里的指令。eip可被jmp、call和ret等指令隐含地改变(事实上它一直都在改变)(ret指令就是把当前栈顶保存的返回值地址 弹到eip中)
函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。
函数的调用方和被调用方对于函数如何调用需要遵守同样的约定,函数才能被正确地调用,这样的约定称为**调用惯例**。
* 函数参数的传递顺序和方式
调用惯例要规定参数压栈的顺序:是从左至右,还是从右至左。有些调用惯例还允许使用寄存器传递参数,以提高性能。
* 栈的维护方式
(谁负责弹出形参?)
在被调函数返回时,需要将被压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由函数的调用方完成,也也可以由被函数完成。
* 名字修饰规则
为了链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰,不同的调用惯例有不同的名字修饰策略。
参数压栈方向
下划线+函数名
下划线+函数名@参数字节数
头两个参数放入寄存器,其它从右至左
@函数名字名@参数字节数
是CDeclaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。(典型的如printf函数)
是Standard Call的缩写,是C++的标准调用方式:所有参数从右到左依次入栈。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。
几乎我们写的每一个WINDOWS API函数都是_stdcall类型的,因为不同的编译器产生栈的方式不尽相同,调用者不一定能正常的完成清除工作。如果使用_stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨平台的调用中,我们都使用_stdcall(虽然有时是以WINAPI的样子出现)。
但当我们遇到这样的函数如printf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用\_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用_stdcall关键字。
函数返回值传递
一般情况下,寄存器eax是传递返回值的通道,函数将返回值存储在eax中,返回后函数的调用方再读取eax。
但是eax本身只有4字节,那么大于4字节的返回值是如何传递的呢?
对于返回5~8字节数据的情况,一般采用eax和edx联合返回的方式进行的。其中eax存储返回值的低4字节,edx存储返回值的高4字节。
对于超过8字节的返回类型:
typedef struct big_thing
char buf[128] ;
big_thing return_test();
//---------------------------
int main(void)
big_thing n = return_test() ;
big_thing return_test()
b.buf[0] = 0 ;
分析这段代码:
首先,在主调函数main中,肯定有一个128字节的变量n,在被调函数return_test中,肯定有一个128字节的变量b。
那被调函数如何返回128字节的变量?直接从b拷贝到n么?你这样直接改变主调函数中变量的值,似乎不符合返回值传值的规则。
那么实际上,编译器是怎么设计大尺寸返回值传递的呢?
* main函数在其栈中的局部变量区域中额外开辟一片空间,将其一部分作为传递返回值的临时对象temp。
* 将temp对象的地址作为隐藏参数传递给return_test函数。
* return_test函数将数据拷贝给temp对象,并将temp对象的地址用eax传出。
* return_test返回后,main函数将eax指向的temp对象的内容拷贝给n。
(return_test是没有真正的参数的,只有一个&伪参数&由函数的调用方悄悄传入)
函数返回值的传递:小于8字节的返回值,以**寄存器**为中转。大于8字节的,以主调函数中新开辟的同样大小的中间变量temp为中转。
C语言对于尺寸太大的返回值类型,会使用一个临时的栈上内存区域作为中转,结果返回值对象会被拷贝两次。故不到万不得已,不要轻易返回大尺寸对象。
C++函数的返回值传递
C++处理大返回值略有不同,其可能是像C那样,1次拷贝到栈上的临时对象里,然后把临时对象拷贝到存储返回值的对象里。
但,有些编译器会进行返回值优化RVO(Return Value Optimization),这样,对象拷贝会减少一次,即没有临时对象temp了,直接拷贝到主调函数的相应对象中。
struct cpp_obj
cout&& &ctor\n& ;
cpp_obj(const cpp_obj& c)
cout&& &copy ctor\n& ;
cpp_obj& operator=(const cpp_obj& rhs)
cout&& &operator=\n& ;
~cpp_obj()
cout&& &dtor\n& ;
cpp_obj foo()
cout && &before foo return\n& ;
int main()
n = foo() ;
cout && &before main return\n& ;
return 0 ;
//---------运行结果---------
before foo return
before main return
此例子是在g++下编译运行。此例就没有设置一个临时变量temp,而是直接把被调函数局部变量的值直接拷贝到主调函数中去。
C++对于返回值还有一种更&激进&的优化策略&&NRV(Named Return Value)具名返回值优化
这种优化是甚至连被调函数中的局部变量都不要了!直接在主调函数中操作对象(根据隐藏参数传入的对象的引用)。
关于NRV要注意两点:(自己总结的,若有不对,请拍砖)
1、在被调函数foo中,其局部变量声明处即是调用主调函数main中对象的默认构造函数处。main中的对象定义处,只是开辟一个空间,当时并不调用构造函数。
2、为何在主调函数中 CObj obj = foo() 会触发NRV优化
而分开写: CO obj = foo() ; 没有NRV优化呢?
程序员必须给class X定义拷贝构造函数才能触发NRV优化,不然还是按照最初的较慢的方式执行。(我们的第二种方式没有涉及到拷贝构造函数,故不会触发NRV优化)
但现在的编译器即使去掉类中的拷贝构造函数,也一样会有NRV优化,但必须是向在对象初始化时调用子函数才会有NRV。
(若没有NRV优化,则被调函数中会生成局部对象,但这个局部对象直接拷贝到主函数相应的对象中,也不会像C那样还要生成一个临时变量)
若把上面的例子的调用方式改为: cpp_obj n = foo() ;
则会触发NRV优化,执行结果就是:
//cpp_obj n = foo() ;改为:
//foo实际就被改为:
void foo(cpp_obj& __result)
// 调用__result的默认构造函数
__result.cpp_obj::cpp_obj();
// 处理__result
//---------NRV后的运行结果---------
before foo return
before main return
(一定注意:只有CObj obj = foo();形式的调用才会有NRV优化!)
关于NRV优化详细见《深入理解C++对象模型》
堆是一块巨大的内存空间,常常占据整个虚拟地址空间的绝大部分。在这片空间里,程序可以请求一块连续内存,并自由地使用,这块内存在程序主动放弃之前都会一直保持有效。在C语言中我们可以用malloc函数在堆上申请空间。
malloc的实现:
操作系统内核管理着进程的地址空间,它通过的有系统调用,若让malloc调用这个系统调用实现申请内存,可完成这个工作。
但是,这样做性能较差,因为每次进行申请释放空间都需要进行系统调用,系统调用的开销比较大,会进行内核态和用户态的切换。
比较好的做法是程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间,管理着堆空间分配的往往是程序的运行库(一般是操作系统提供的共享库)。
malloc实际上就是对这共享库中函数的包装。
&批发-零售&类比:
运行库相当于是向操作系统批发了一块较大的堆空间,然后零售给程序用。运行库在向程序零售空间时,必须管理此空间,不能把一块空间出售两次。
当空间不够用时,运行库再向操作系统批发(调用OS相应的系统调用)。
注意:这个运行库一般也是操作系统或语言提供给我们的,其包含了管理堆空间的算法,其运行在用户态下。
(我们自己也可以实现这个分配算法,但常用的分配算法已经被各种系统、库实现了无数遍,没有必要重复发明轮子)
每个进程在创建时都会有一个默认堆,这个堆在进程启动时创建,并且直到进程结束都一直存在。在Windows中默认堆大小为1MB。
(注意:在Windows中堆不一定是向上增长的)
问:malloc申请的空间是不是连续的?
答:若&空间&指的是虚拟空间的话,那么答案是连续的,即每一次malloc分配后返回的空间都可以看做是一块连续的地址。(进程中可能存在多个堆,但一次能够分配的最大堆空间取决于最大的那个堆)
如果空间值的是物理空间,则不一定连续,因为一块连续的虚拟地址空间有可能是若干个不连续的物理页拼凑成的。
堆空间管理算法
* 1、空闲链表法
把堆中各个空闲块按链表的方式连接起来,当用户请求时遍历链表找到合适的块。
* 2、位图(这个思想好)
将整个堆划分为大量的大小相同的块。当用户请求时分配整数个空间给用户。我们可以用一个整数数组的位来记录分配状况。
(每个块只有头/使用/空闲三种状态,即用两个位就可表示一个块,因此称为位图。头是用来标记定界的作用)分析堆栈段中数据的存储方式及栈指针变化之间的关系-
分析堆栈段中数据的存储方式及栈指针变化之间的关系
作者:本站编辑
&&&&&投稿日期:
堆栈段指针(寄存器SS)是用于确定堆栈在内存中的起始位置的寄存器;堆栈段寄存器的作用和其他段寄存器(CS,DS,ES)相同,都是为了在一整段的内存中划分区域:由于内存为一整段储存单元,CS(代码段寄存器)中存放代码段的起始位置
分析堆栈段中数据的存储方式及栈指针变化之间的关系:
堆栈段指针(寄存器SS)是用于确定堆栈在内存中的起始位置的寄存器;堆栈段寄存器的作用和其他段寄存器(...
堆栈数据区的存取原则是:
一般计算机都要在随机储存器RAM中开辟出某个区域用于重要数据的储存。但这个区域中数据的存取方式却和R...
8086堆栈中数据的操作方式是什么?:
先进后出是正解!这是堆栈段的特点,与堆栈段不同的是指令序列缓冲器——先进先出。 PUSH指令:将一个...
堆栈的存储方式:
堆栈 堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性: 最后一个放入堆栈中...
用于存放堆栈段中一个数据区基地址的寄存器通常是SP还是BP:
SP是堆栈指示器 BP是基址指示器 SP,BP一般与段寄存器SS 联用,以确定堆栈寄存器中某一单元的...
RAM中设置堆栈区有什么用 什么是堆栈区:
RAM的特点就是高速存储。形象的说:堆栈是一种存储部件,即数据的写入跟读出不需要提供地址,而是根据写...
数据段,堆和堆栈区别:
数据段,是保存常量的 比如代这样的代码: char* buf=&abcdef&; 那么 abcdef...
用汇编如何把数据存入堆栈 并取出 显示出来 要完整的程序:
比如说01,02,03入栈再显示, STACK SEGMENT PARA STACK 'STACK'...
汇编语言中的堆栈段指针是干什么的:
堆栈段指针(寄存器SS)是用于确定堆栈在内存中的起始位置的寄存器;堆栈段寄存器的作用和其他段寄存器(...
汇编语言中代码段,数据段,附加段,堆栈段这四个段中哪个是唯一的?:
,堆栈段是唯一[译] ARMv8-A架构基础之寄存器 - 简书
[译] ARMv8-A架构基础之寄存器
ARMv8-A提供了31个64位的通用寄存器,始终可以访问,并且可以在所有异常级别访问。 在AArch64执行状态下,每个寄存器(X0-X30)都是64位宽度。 宽度增加有助于减少大部分应用程序中的寄存器压力。
每个64位通用寄存器(X0 - X30)也有一个32位的格式(W0 - W30)。
64bit_register.png
32位W寄存器构成相应的64位X寄存器的下半部分。 即W0形成X0的低位字,W1形成X1的低位字。
从W寄存器中读取时,忽略对应的X寄存器的高32位并且保持他们不变。 写入W寄存器将X寄存器的高32位设置为零。 因此,将0xFFFFFFFF写入W0会将X0设置为0xFFFFFFFF。
有时Rn用来指定一个ARMv8-A寄存器。 这意味着寄存器可以是Xn或Wn。
特殊寄存器
除了31个(X0到X30)个ARMv8-A核心寄存器之外,还有几个特殊的寄存器。
special_registers.png
没有名为X31或W31的寄存器。 一些指令被编码了,以使数字31代表零寄存器ZR(WZR / XZR)。 还有一组受限制的指令,其中一个或多个参数被编码,以使数字31代表堆栈指针(SP)。
当前栈指针
当前栈指针
程序计数器
64位格式的堆栈指针不使用X前缀。
在AArch64中执行时,对于每个异常级别,异常返回状态将保存在以下专用寄存器中:
异常链接寄存器(ELR)。
保存的处理器状态寄存器(SPSR)。
下表按异常级别标识特殊寄存器:
栈指针 (SP)
异常链接寄存器 (ELR)
保存的处理器状态寄存器 (SPSR)
异常级别的特殊寄存器
程序调用标准(PCS)还定义了一个专用的帧指针(FP),通过可靠地展开堆栈,它使调试和调用关系图分析变得更加容易。
零寄存器的名字暗示着什么。
零寄存器忽略所有对它的写操作,并且所有对它的读操作都返回0.您可以在大多数(但不是全部)指令中使用零寄存器。
堆栈指针(SP)是一个指向堆栈顶部的寄存器。 选择使用的堆栈指针在某种程度上与“异常”级别是分开的。 默认情况下,发生异常时会为目标异常级别选择堆栈指针(SP_ELn)。 例如,发生EL1异常时选择SP_EL1。 每个异常级别都有自己的堆栈指针。
但是,当在AArch64中,异常级别的不是E0时,处理器可以使用下面中的任何一种:
与该异常级别(SP_ELn)关联的64位堆栈指针,或者,
与EL0(SP_EL0)关联的堆栈指针。 EL0只能访问SP_EL0。
SP不能被大多数指令引用。 但是,一些算术指令,例如ADD指令,可以读写当前的堆栈指针来调整函数中的堆栈指针。 例如:
ADD SP, SP, #0x10
// 将SP调整为当前值之前的0x10字节
ADD SP, SP, #256
// SP = SP + 256
程序计数器
程序计数器(PC)保存当前的程序地址。 它不能被数字引用(就像通用寄存器文件的一部分一样),因此不能用作算术指令的源或目的地,或作为加载和存储指令的基址,索引或转移寄存器。
那些可以读取PC的指令是那些具有计算PC相对地址功能的指令(ADR,ADRP,字面加载和直接分支),以及在链接寄存器中存储返回地址的“分支并链接”指令(BL和BLR)。 修改程序计数器的唯一方法是使用分支,异常生成和异常返回指令。
使用计算PC相对地址的指令读取PC时,读取的PC值是该指令的地址。 与ARMv7-A不同,不存在4或8字节的隐含偏移量。
异常链接寄存器(ELR)
异常链接寄存器保存要在异常后返回的地址。
现就职于厦门雅迅网络股份有限公司——安全网关产品线,着力于车辆信息安全解决方案研发
8086汇编 本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,在此感谢他和像他一样共享资源、帮助他人的筒子们==本文比较长,由于笔者个人能力有限,错漏在所难免,欢迎读者们批评指正。 本文首发于我的CSDN博客,同时也发布于我的博客园 一、基础...
一. ARM 寄存器 ARM共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器.这些寄存器不能被同时访问,但在任何时候,通用寄存器R0~R14,程序计数器PC,一个或两个状态寄存器都是可访问的. arm64有32个64bit长度的通用寄存器x0~x30,sp,...
[数据传送指令] 一、通用数据传送指令 1、传送指令 MOV (move) 指令的汇编格式:MOV DST,SRC指令的基本功能:(DST)&-(SRC)将原操作数(字节或字)传送到目的地址。指令支持的寻址方式:目的操作数和源操作数不能同时用存储器寻址方式,这个限制适用于所...
ART世界探险(3) - ARM 64位CPU的架构快餐教程 前面我们说过,Dalvik如果没有JIT的话,可以做到架构无关,让Dalvik指令都解释执行。但是ART是AOT,要编译成针对芯片具体的机器指令。所以,研究Dalvik的时候可以不用太关心目标指令,而我们研究AR...
pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 1. 面向对象都有哪些特性以及你对这些特性的理解 21 2. 访问权限修饰符public、private、protected, 以及不写(默认)时的区别(201...
近日,西王食品(000639)宣布正式携手2017瑞士女排精英赛,成为赛会官方赞助商。由此,这家中国山东食品企业再次踏出国门,在遥远的中欧相伴女排一路前行,同时将西王食品品牌与产品推向了世界。 政策与市场双重认可,西王引领行业发展 现如今,含有高“非饱和脂肪酸”的玉米油等产...
在北京进入了第十个年头,几乎人生有一半的时间是呆在了这座城市。在来北京的前几年,没有太多钱,信息也没有现在发达,于是,在吃这件事情上投入的时间和金钱太少。直到工作以后,有了一些闲暇的时间,还有了一些闲钱,各种美食信息获取也方便许多,至此便走上了吃货的道路。 今天北京雾霾了,...
近日,在学修中对“坚定”略有所感,故记下,以待日后摇摆时再来观之,以定己心。 先提问,别人的小孩都补奥数,不让孩子输起跑线上,我们能坚持因材施教,顺其性而教吗?13年当大家都说茅台是腐败酒,反腐后将无人买,此时其股价跌至120元,而您虽曾测算其至少能值400元,但那时您能坚...
秦东魁 纪念专栏第 78 天 纪念革命英雄 |朱学会 牺牲年龄:22 岁 今日纪念人物:朱学会(),湖南省人,第一野战军四军十一师三十二团一连战士,在扶眉战役小寨战斗中光荣牺牲,牺牲时年仅22岁。 资料来源:宝鸡市扶眉战役革命烈士陵园管...

我要回帖

更多关于 PC是不是特殊功能寄存器 的文章

 

随机推荐