32位x86处理器编程架构

Posted raindayinrain

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了32位x86处理器编程架构相关的知识,希望对你有一定的参考价值。

         处理器架构,或处理器编程架构,是指一整套的硬件结构,及与之相适应的工作状态。架构内的资源对程序员来说是可见的,可访问的,受程序的控制以改变处理器的运行状态。非架构的资源取决于具体的硬件实现。

        Intel 32位处理器架构简称IA-32,以8086处理器为基础发展起来的。

        8086有20根地址线,可以寻址1MB内存。但是它内部的寄存器是16位的,无法在程序中访问整个1MB内存。所以,它是第一款支持内存分段模型的处理器。还有,8086处理器只有一种工作模式,即实模式。

        尽管8086是16位处理器,但它也是32位架构内的一部分。原因在于,32位的处理器架构是从8086那里发展起来的,是基于8086的,具有延续性和兼容性。

        就曾经用过的产品而言,32位处理器有32根地址线,数据线的数量是32根或者64根。因此,它可访问2^32,即4GB的内存,且每次可读写连续的4字节或者8字节,这称为双字或者4字访问。当然,如你要按字节或字来访问内存,也是允许的。

        处理器虽小,功能却异常复杂。它不单单是地址线和数据线的扩渣,实际上还有更多的部分,包括高速缓存,流水线,浮点处理器,多处理器管理,多媒体扩展,乱序执行,分支预测,虚拟化,温度和电源管理等。

10.1.IA-32架构的基本执行环境

10.1.1.寄存器的扩展

        在16位处理器内,有8个通用寄存器AX,BX,CX,DX,SI,DI,BP和SP。其中,前4个还可拆分成两个独立的8位寄存器来用。即AH,AL,BH,BL,CH,CL,DH,DL。

        32位处理器在16位处理器的基础上,扩展了这8个通用寄存器的长度,使之达到32位。

        为了在汇编语言程序中使用经过扩展的寄存器,需要给它们命名。它们的名字分别是EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP。可以在程序中使用这些寄存器,即使在实模式下:

mov eax, 0xf0000005
mov ecx, eax
add edx, ecx

        可以在32位处理器上运行16位处理器上的软件。但是,它并不是16位处理器的简单增强。事实上,32位处理器有自己的32位工作模式,本书中,32位模式特指32位保护模式。这种模式下,可完全,充分发挥处理器性能。可使用全部的32根地址线,能够访问4GB内存。

        32位模式下,为生成32位物理地址,处理器需使用32位的指令指针寄存器。为此,32位处理器扩展了IP,使之达到32位,即EIP。当它工作在16位模式下时,依然使用16位的IP;工作在32位模式下时,使用的是全部的32位EIP。

        即使在32位模式下,EIP寄存器也只能由处理器内部使用,无法直接访问。对IP和EIP的修改通常用某些指令隐式进行的,包括JMP,CALL,RET和IRET等。

 

        另外,在16位处理器中,标志寄存器FLAGS是16位的,在32位处理器中,扩展到了32位,低16位和原先保持一致。关于EFLAGS中的各个标志位,将在后面章节一一介绍。

        在32位模式下,对内存的访问理论上来说不再需要分段,因为它有32根地址线,可自由访问任何一个内存位置。但,IA-32架构的处理器是基于分段模型的,因此,32位处理器依然需要以段为单位访问内存,即使它工作在32位模式下。

        不过,它也提供了一种变通的方案,即,分一个段,段的基地址是0x00000000,段的长度是4GB。这种情况下,可以视为不分段。

        每个程序都有属于自己的内存空间。在16位模式下,一个程序可自由地访问不属于它的内存位置,甚至可以对那些地方进行修改。这是不安全,也是不合法的,但却没有任何机制来限制这种行为。在32位模式下,处理器要求在加载程序时,先定义该程序所拥有的段,然后,允许使用这些段。定义段时,除了基地址外,还附加了段界限,特权级别,类型等属性。当程序访问一个段时,处理器将用固件实施各种检查工作,以防止对内存的违规访问。

        在32位模式下,传统的段寄存器,如CC,SS,DS,ES,保存的不再是16位段基地址,而是段的选择子,即,用于选择要访问的段。因此,严格说,它的新名字叫做段选择器。除了段选择器外,每个段寄存器还包括一个不可见部分,称为描述符高速缓存器,里面有段的基地址和各种访问属性。这部分内容程序不可访问,由处理器自动使用。

        32位处理器增加了两个额外的段寄存器FS和GS。

10.1.2.基本的工作模式

        8086具有16位的段寄存器,指令指针寄存器和通用寄存器【CS,SS,DS,ES,IP,AX,BX,CX,DX,SI,DI,BP,SP】,因此,我们称它为16位的处理器。尽管能访问1MB的内存,但只能分段进行,且由于只能使用16位的段内偏移量,故段的长度最大只能是64KB。8086只有一种工作模式,即实模式。

        1982,推出80286处理器,也是一款16位的处理器,大部分寄存器和8086处理器一样。因此,80286和8086一样,因为段寄存器是16位的,且只能用16位的偏移地址,在实模式下只能使用使用64KB的段。尽管它有24根地址线,理论上可访问2^24,即16MB的内存,但依然只能分成多个段来进行。

        80286和8086不一样地方在于,它第一次提出了保护模式的概念。保护模式下,段寄存器中保存的不再是段地址,而是段选择子,真正的段地址位于段寄存器的描述符高速缓存中,是24位的。因此,运行子保护模式下的80286处理器可访问全部16MB内存。

        80286处理器访问内存时,不再需要将段地址左移,因为在段寄存器的描述符高速缓存器中有24位的段物理基地址。这样,段可位于16MB内存空间的任何位置,不再限于低端1MB范围内,也不必非得是16字节对齐的地方。由于80286的通用寄存器是16位的,只能提供16位的偏移地址,因此,和8086一样,即使运行在保护模式下,段的长度依然不能超过64KB。对段长度的限制妨碍了80286处理器的应用,这就是16位保护模式很小为人所知的原因。

        实模式等同于8086模式。本书中,实模式和16位保护模式统称16位模式。在16位模式下,数据的大小是8位或16位的;控制转移和内存访问时,偏移量也是16位的。

        1985年的80386是Intel第一款32位产品,获得极大成功。是后续所有32位产品的基础。和8086/80286不同,80386处理器的寄存器是32位的,且拥有32根地址线,可访问2^32,即4GB的内存。

        80386,以及所有后续的32位处理器,都兼容实模式。可以运行实模式下的8086程序。且,刚加电时,这些处理器都自动处于实模式下,此时,它相当于一个非常快速的8086处理器。只有进行一番设置后,才能运行在保护模式下。

        保护模式下,所有的32位处理器都可访问多达4GB的内存,它们可工作在分段模型下,每个段的基地址是32位,段内偏移量也是32位的,因此,段的长度不受限制。

        32位保护模式兼容80286的16位保护模式。

        除了保护模式,32位处理器还提供虚拟8086模式【V86模式】,在这种模式下,IA-32处理器被模拟成多个8086处理器并行工作。V86模式是保护模式的一种,可以在保护模式下执行多个8086程序。传统上,要执行8086程序,处理器需工作在实模式下。这种情况下,为32位保护模式写的程序就不能运行。但,V86模式提供了让它们在一起同时运行的条件。

        V86曾经很有用。现在,基本无用。

        本书中,32位模式特指IA-32处理器上的32位保护模式,不存在所谓的32位实模式,实模式概念实质上是8086模式。

10.1.3.线性地址

        为IA-32处理器编程,访问内存时,需在程序中给出段地址和偏移量。因为分段是IA-32架构的基本特征之一。传统上,段地址和偏移地址称为逻辑地址,偏移地址叫做有效地址,在指令中给出有效地址的方式叫做寻址方式。比如:

inc word [bx + si + 0x06]

        这里,指令中使用的是基址加变址的方式来寻找最终的操作数。

        段的管理由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。

        IA-32处理器支持多任务。在多任务环境下,任务的创建需分配内存空间;任务终止后,还要回收它所占用的内存空间。在分段模型下,内存的分配是不定长的,程序大时,就分配一大块内存;程序小时,就分配一小块。时间长了,内存空间会碎片化,就可能出现:内存空间有,都是小块,无法分配给某个任务。为解决这问题,IA-32处理器支持分页功能,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为4KB,通过使用页,可简化内存管理。        

        当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址。线性地址还要经页部件转换后,才是物理地址。

        线性地址的概念用来描述任务的地址空间。IA-32处理器上的每个任务都拥有4GB的虚拟内存空间,这是一段长4GB的平坦空间,就是一段平直的线,因此叫线性地址空间。相应地,由段部件产生的地址,就对应着线性地址空间上的每一个点,就是线性地址。

        IA-32架构下的任务,分段,分页等内容,是本书的重点。

10.2.现代处理器的结构和特点

10.2.1.流水线

        早在8086是时代,处理器已经有了指令预取队列。指令执行时,如总线是空闲的【没访问内存的操作】,就可以在执行的同时预取指令并提前译码。

        执行一条指令需从内存取指令,译码,访问操作数和结果,并进行移位,加法,减法,乘法,及其他任何需要的操作。

        为提高处理器的执行效率,速度,可把一条指令的执行过程分解为若干细小步骤,分配给相应的单元。各个单元是独立,并行的。

        比如,一个指令的执行过程分为取指令,译码,执行。若每个步骤花1个时钟周期。则执行三条指令,顺序执行需9个时钟周期,流水线下需5个时钟周期。

        简单的流水线不过如此,但,它仍有很大的改进空间。原因很简单,指令的执行过程仍然可继续细分。一般,流水线的效率受执行时间最长的那一级限制。要缩短各级执行时间,需让每一级任务减少,需把复杂任务再次分解。

        Pentium 4采用了31级超深流水线。

10.2.2.高速缓存

        寄存器速度是最快的,原因在于它使用了触发器,这是一种利用反馈原理制作的存储电路。触发器的工作速度是纳秒级别的,当然也可以用来作为内存的基本单元,即静态存储器【SRAM】,缺点是成本太高。所以,制作内存芯片的材料一般是电容和单个的晶体管,由于电容需要定时刷新,使得它的访问速度变得很慢,通常是几十个纳秒。因此,它常叫做:动态存储器。我们所用的内存芯片,大部分都是DRAM。最后,硬盘是机电设备,是机械和电子的混合体,它的速度很慢,通常在毫秒级。

        高速缓存是处理器也内存之间的一个静态存储器,容量较小,但速度可与处理器匹配。

        高速缓存的用处源于程序在运行时具有局部性。首先,程序常访问最近刚刚访问过的指令和数据,或与它们相邻的指令和数据。

        利用程序运行时的局部性原理,可把处理器正在访问和即将访问的指令和数据块从内存调入高速缓存中。每个处理器要访问内存时,先检索高速缓存。如要访问内容在其中,从其中取得。称为命中。否则,为不中。不中下,处理器需先装载高速缓存。装载以块为单位。为此,需额外的时间来等待块从内存载入高速缓存,该过程中损失的时间称为不中惩罚。

        一些处理器可能有多级Cache。

10.2.3.乱序执行

        为实现流水线,需将指令拆分成更小的可独立执行部分,即拆分成微操作。

        有些,只需一个微操作,如:

add eax, ebx

        有些需两个微操作,如:

add eax, [mem]

        有的可拆分成3个,如:

add [mem], eax

        一个从内存读数据,一个执行相加,一个将相加结果写回内存。

        一旦将指令拆分为微操作,处理器就可在必要时候乱序执行程序。考虑以下:

mov eax, [mem1]
shl eax, 5
add eax, [mem2]
mov [mem3], eax

        这里,指令add eax, [mem2]可拆分为两个微操作。如此,执行逻辑左移指令的同时,处理器可提前从内存读取mem2的内容。典型地,如数据不在高速缓存中,则处理器获取mem1的内容后,会立即开始获取mem2的内容。与此同时,shl指令的执行早就开始了。

        指令拆分成微操作,也可使栈操作更有效率。

push eax
call func

        这里,push eax可拆分为两个微操作。即可表述为以下的等价形式:

sub esp, 4
mov [esp], eax

        这样带来一个好处,即使EAX寄存器内容还没准备好,sub esp, 4也可先执行。call指令执行时,需在当前栈中保存返回地址。以前,该操作只能等待push eax指令执行结束。因为它需要esp的新值。现在,call在sub esp, 4结束就可无延迟立即开始执行。

10.2.4.寄存器重命名

        考虑以下例子:

mov eax, [mem1]
shl eax, 3
mov [mem2], eax
mov eax, [mem3]
add eax, 2
mov [mem4], eax

        以上代码做了两件事:将mem1里的内容左移3次,将mem3里的内容加2。

        如为最后三条指令使用不同的寄存器,将更明显看出这两件事无关性。事实上,处理器也是这么做的。处理器为最后三条指令使用了另一个不同的临时寄存器,因此,左移和加法可以并行处理。

        IA-32架构的处理器只有8个32位通用寄存器。不过,在处理器内部,却有大量的临时寄存器可用,处理器可以重命名这些寄存器以代表一个逻辑寄存器,比如EAX。

        寄存器重命名以一种完全自动和简单的方式工作。每当指令写逻辑寄存器时,处理器就为那个逻辑寄存器分配一个新的临时寄存器。

mov eax, [mem1]
mov ebx, [mem2]
add ebx, eax
shl eax, 3
mov [mem3], eax
mov [mem4], ebx

        假定mem1内容在高速缓存,mem2不在。这意味着,左移可在加法前开始【用临时寄存器代替EAX】。为左移结果使用一个新的临时寄存器。

        所有操作完成后,代表EAX寄存器最终结果的临时寄存器的内容被写入真实的EAX寄存器,该处理过程称为引退。

        所有通用寄存器,栈指针,标志,浮点寄存器,甚至段寄存器都有可能被重命名。

10.2.5.分支目标预测

        如遇到一条转移指令,后面那些已经进入流水线的指令就都无效了。必须清空流水线,从要转移到的目标位置处重新取指令放入流水线。

        现代处理器中,流水线操作分很多步骤,包括取指令,译码,寄存器分配,重命名,微操作排序,执行,引退。

        分支预测的核心问题是:转移是发生还是不会发生。

        在处理器内部,有一个小容量的高速缓存器,叫分支目标缓存器。当处理器执行了一条分支语句后,它会在BTB中记录当前指令的地址,分支目标地址,本次预测结果。下次,在那条转移指令实际执行前,处理器查找BTB,看有没有最近的转移记录。如能找到对应的条目,则推测执行和上次相同的分支,把该分支指令送入流水线。

        当该指令实际执行时,如预测失败,则清空流水线。同时,刷新BTB中的记录。

10.3.32位模式的指令系统

10.3.1.32位处理器的寻址方式

        16位处理器上,指令中的操作室可以是8位,或16位寄存器,指向8位或16实际操作数的16位内存地址,及8位或16位立即数,

        如指令中包含了内存地址操作数,则必然是一个16位的段内偏移地址,称为有效地址。通过有效地址,可间接取得8位或16位的实际操作数。指定有效地址可使用基址寄存器BX,BP,变址寄存器SI和DI,同时还可加上一个8位或16位的偏移量。比如:

mov ax, [bx]
mov ax, [bx + di]
mov al, [bx + si + 0x02]

 

         32位处理器出现后,让16位指令和32位指令共用相同的指令码,但通过不同的指令前缀,结合处理器当前的运行状态来决定该指令的寻址方式。

        如,当处理器运行在16位模式时,如没有指令前缀0x66,则认为指令是传统的16位寻址方式;若有指令前缀0x66,则指令是新的32位寻址方式。如处理器运行在32位模式下且没指令前缀0x66,则视为默认的32位寻址方式,否则,就是传统的16位寻址方式。        

        32位处理器兼容16位处理器的工作模式,可运行传统的16位代码。但,它有自己独立的32位运行模式,且只有在这种模式下,才能发挥最高的运行效率。

        在32位模式下,默认用32位宽度的寄存器。如指令用了立即数,数值默认是32位的。如果指令中的操作数是指向内存单元的地址,则,该地址默认是32位的段内偏移地址。通过32位有效地址,可以间接取得32位的实际操作数。指定有效地址可使用全部的32位通用寄存器作为基址寄存器。同时,还可再加上一个除ESP外的32位通用寄存器作为变址寄存器。变址寄存器还允许乘以1,2,4或8作为比例因子。最后,还允许加上一个8位或32位的偏移量。

       

        值得说明的是,在16位模式下,内存寻址方式的操作数不允许使用栈指针寄存器SP。但是,32位模式下,允许在内存操作数中使用栈指针寄存器ESP。

10.3.2.操作数大小的指令前缀

        每一条处理器指令都可拥有前缀,比如重复前缀【REP、REPE、REPNE】,段超越前缀【如ES:】、总线封锁前缀【LOCK】等。前缀是可选的,每个前缀的长度是1字节,每条指令可以有1~4个前缀,或不使用前缀。

        前缀【如有的话】的后面是操作码部分,指示执行什么样的操作。比如,传送,加法,减法,乘法,除法,移位等。根据指令的不同,操作码长度是1~3字节。同时,操作码还可用来指示操作的字长。即数据宽度为字还是字节。

        操作码之后是操作数类型和寻址方式部分。可选的。这部分给出了指令的寻址方式,及寄存器的类型。

        之后的最后是立即数和偏移量。如指令中使用了立即数,则立即数就在这一部分给出。如指令使用了带偏移量的寻址方式,如:

mov cx, [0x2000]
mov ecx, [eax + ebx * 8 + 0x02]

        则,偏移量0x2000和0x02也在这部分出现。取决于具体的指令,立即数可以是1,2或4字节。偏移量部分与此相同。

        上述指令编码格式起源于16位处理器时代。32出现后,做了些修改,主要是扩展了数据的宽度。这也带来了一些问题:

mov dx, [bx+si+0x02]

         在16位指令编码格式中,这种内存单元到寄存器的传送指令操作码是0x8B。操作码0x8B之后是1字节的寻址方式和操作数类型部分。位7,位6值为01,表示用基地址变址的寻址方式,带有8位偏移量,位5~位3是010,表示目的操作数为寄存器DX;位2~位0为000,表示寻址方式为"BX+SI+8位偏移量"。该字节后,是1字节的偏移量0x02.因此这条指令编译后的机器代码:

8B 50 02

        32位处理器使用相同的编码格式,但寻址方式和寄存器的定义却是另起炉灶的,完全不同。32位处理器上,位7,位6是01,表示用了基址寻址方式,且带8位偏移量;位5~位3是010,指示目的操作数为寄存器EDX;位2~位0值是000,表示寻址方式为EAX+8位偏移量。该字节后,是1字节的偏移量0x02,因此,同样的机器指令码,却对应着不同的32位指令

mov edx, [eax + 0x02]

        相同的机器指令,在16位模式下和32位模式下解释和执行效果是不同的。但,32位可执行16位的程序,包括实模式,16位保护模式。为此,16位模式下,处理器把所有指令都看成16位的。

        当处理器在16位模式下运行时,也可用32位的寄存器,执行32位的运算。为此,需使用指令前缀0x66来临时改变这种默认状态,因为同一个指令码,在16位模式下和32位模式下有 不同的解释。因此,当处理器在16位模式下运行时,机器指令码

66 40

        对应的指令不再是inc ax,而是

inc eax

        相反地,如处理器运行在32位模式下,则处理器认为指令的操作数都是32位的,如你加了前缀,这个前缀就用来指示指令是16位的。因此,指令前缀0x66有反转当前默认操作数大小的作用。

        编写程序时候,应当考虑指令的运行环境。为指明程序的默认运行环境,编译器提供了伪指令bits,用于指明其后的指令应该被编译成16位的,还是32位的。比如:

bits 16
mov cx, dx
mov eax, ebx

bits 32
mov cx, dx
mov eax, ebx

        注意,bits 16或bits 32可以放在方括号中,也可不放。

        最后,16位模式是默认的编译模式。

10.3.3.一般指令的扩展

        由于32位的处理器都拥有32位的寄存器和算术逻辑部件,且同内存芯片之间的数据通路至少是32位的,因此,所有以寄存器或内存单元为操作数的指令都被扩充,以适应32位的算术逻辑操作。这些扩展即使在16位模式下【实模式,16位保护模式】,也可用。

        比如,ADD,在32位处理器上,除了允许8位或16位操作数外,32位的操作数现在也可用。

        除了双操作数指令,单操作数指令也同样允许32位操作数。

        我们已经接触过的逻辑移动指令,如shl,shr等,目的操作数也扩展至32位,但用于指定移动次数的源操作数足够应付32位的环境,没有变化。

        和16位时代一样,在32位处理器上,逻辑移动指令的源操作数如果是寄存器的话,则依然需使用CL。同时,32位处理器在实际执行时,要先将源操作数【CL寄存器内】同0x1F做逻辑与。也就是说,仅保留源操作数的低5位,因此,实际移动次数最大为31。

        在16位处理器上,loop指令的循环次数在寄存器CX中。在32位处理器上,如当前的运行模式是16位的【实模式,16位保护模式】,则loop执行指令时,依然用CX寄存器。否则,如运行在32位模式下,则使用ECX寄存器。

        在16位处理器上,无符号乘法指令mul的格式为:

mul r/m8 ; AX<--AL*r/m8
mul r/m16 ; DX:AX<--AX*r/m16

        在32位处理器上,除了依然支持上述操作外,还支持以下扩展的格式:

mul r/m32 ;EDX:EAX<--EAX*r/m32

        这样,两个32位数相乘,得到一个64位的结果。

        有符号乘法指令imul与此相同。

        相应的,无符号数和有符号数除法也做了32位扩展。

div r/m32
idiv r/m32

        在这里,被除数是64位的,高32位在EDX寄存器,低32位在EAX寄存器。除数是32位的,位于32位的寄存器,或者存放有32位实际操作数的内存地址。指令执行后,32位的商在EAX寄存器,32位的余数在EDX寄存器。

        32位处理器的栈操作指令push和pop也有所扩展,允许压入双字操作数。特别是,它现在支持立即数压栈操作。立即数压栈操作的指令格式为:

push imm8
push imm16
push imm32

X86-64 汇编学习1

前言

熟悉一些关键字可以更方便看懂官方的手册:
IA-32(Intel architecture -32): 指代因特尔32位处理器架构。
X86-64:64位处理器架构
compatibility(兼容模式):在64位下运行IA-32程序或者模式

内存范围

我们知道我们的32位程序寻址范围为2^32=4G内存即 0000 0000-FFFF FFFF

具体内存寻址如下图所示:

64位处理器寻址范围为2^64但是实际上指令集架构设计和处理器都没有完全实现这么大的范围。
AMD在地址线实现了52根,但是实际往往只使用48位而已。因此64位系统往往实际只使用48位内存寻址范围,
前16位用于预留扩展。

我们利用x64Dbg调试相关用户程序,你会发现所有的地址前16位都是0,即16进制的四个0。

寄存器

这块建议参阅Intel手册卷1部分内容,这里贴出部分节选:

通用寄存器32位下有8位,64位有16个。

如果你需要访问特定大小的寄存器可以按照如下官方表格进行操作。比如 1字节R8可以使用R8B等

微软文档关于64寄存器说明

限制:

(1) 所有32位寄存器的修改,会将对应的64位寄存器的前32位用0填充(8,16位的赋值不会有影响)。

举例如下:

(1) 所有高8位寄存器访问不能与新的寄存器同时访问(r8-r15等)


Win64 调用约定

这可直接参阅官方文档:

调用约定文档

举例环节:

#include<stdio.h>

float subIntFloat(int i1, float i2, int i3, float i4) 
    printf("hello test %d %f %d %f \\r\\n", i1, i2, i3, i4);
    return i1 + i2 + i3 + i4;


int addNumber4(int i1,int i2,int i3,int i4) 
    printf("hello test %d %d %d %d \\r\\n",i1,i2,i3,i4);
    float fRet= subIntFloat(1,2.0,3,3.0);
    printf("subIntFloat fRet %f \\r\\n", fRet);
    return i1 + i2 + i3 + i4;

int main()

  
   int ret= addNumber4(1, 2, 3, 4);
   printf("addNumber4 ret %d \\r\\n", ret);

   return 0;


我们首先关注addNumber4传参和结果返还。


栈进行128位对齐

在64位系统下很多指令要求内存进行128对齐,比如MOVAPS指令
因此很多函数在开头对RSP进行栈对齐(一般末尾补8h).



相对RIP进行寻址

x86-64额外多出一个寻址方式,
在x32我们想得到EIP的地址往往需要跳一个空函数得到,而在x86-64我们可以写出如下图汇编指令:

x32下相关操作是不被支持的:

参考

wikipedia 64-bit computing 内存说明
stackoverflow 中64-bit 内存讨论
微软文档关于64寄存器说明
intel 手册

以上是关于32位x86处理器编程架构的主要内容,如果未能解决你的问题,请参考以下文章

为什么Windows 32位称为Windows x86而不是Windows x32?

x86 32位操作码,x86-x64不同或完全删除

x86 OS 的 64 位处理器上的 64 位整数运算

指令,操作系统位数,32位与64位 x86 arm 处理器 ,概念概览

X86和X86_64

现代32位或64位x86汇编