Intel call指令
转载:http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/
最近有一个需求,给你个地址,看看这个地址前面是不是一个CALL指令(请同学们自行联想该需求的来源)。作为团队的救火队员+炮灰,这个简单的事情自然落在了我的头上。
这个事情很简单,作为一个善于站在别人肩膀上的程序员我们可以考虑使用 libdisasm;如果要考虑x64,就试试udis86;如果需要用Python,就有Python包装好的 pydasm。不过这两个400KB+的库,显然不值得为了一个CALL指令导入到编译出来大小仅仅100K不到的项目代码里面。
那么就自己抽一个CALL指令解码逻辑出来好了。这个逻辑的复杂性在于,你无法知道前面一个CALL指令有多长。因此,首先需要枚举出所有的CALL指令格式。
Intel有公开的指令集格式文档,你需要的是第二卷的上半部分,指令集从A到M。这篇文档的难度超出一般人想象,里面有众多晦涩的标识、与硬件紧密相关的介绍,拿到这后,即使直接翻到目录的CALL 指令一节,也不见得能够弄清楚。不相信?我们就翻到那里看看:
CALL指令格式一览表
虽然很明确的列出,第一列是指令的二进制形式,第二列是指令的汇编形式,但是面对着 E8 cw, FF/2这样的标识,一样不知道究竟对应的二进制格式是什么样的。
那好,我们就从理解这些标识开始。文档向前翻,有一个专门的节(3.1.1 Instruction Format)讲述这些标识的含义。这里抽出其中两个用得着的翻译一下:
表格中的“Opcode”列列出了所有的所有可能的指令对应的二进制格式。有可能的话,指令代码使用十六进制显示它们在内存当中的字节。除了这些16进制代码之外的部分使用下面的标记:
cb, cw, cd, cp, co, ct — opcode后面跟着的一个1字节(cb),2字节(cw),4字节(cd),6字节 (cp),8字节(co) 或者 10字节(ct) 的值。这个值用来表示代码偏移地址,有可能的话还包括代码段寄存器的值。
/digit — digit为0到7之间的数字,表示指令的 ModR/M byte 只使用 r/m字段作为操作数,而其reg字段作为opcode的一部分,使用digit指定的数字。
红字部分不知道什么含义?没关系,我们先不看它。对于cb/cw之类的,基本上能够简单看明白其中的一些指令含义了:
E8 cw 的含义是:字节 0xE8 后面跟着一个2字节操作数表示要跳转到的地址与当前地址的偏移量。
E8 cd 的含义是:字节 0xE8 后面跟着一个4字节的操作数表示要跳转的地址与当前地址的偏移量。
9A cd 的含义是:字节 0x9A 后面跟着一个6字节的操作数表示要跳转的地址和代码段寄存器的值。
那么,同样的0xE8开头的指令,CPU如何区分后面的操作数是2字节还是4字节?答案是和CPU的模式有关,在实模式下,0xE8接受2字节操作数,而32位保护模式下接受4个字节,64位保护模式下同样接受4字节,同时需要对该操作数进行带符号扩展。
因此,CALL指令的前两种格式是:E8 xx xx xx xx,和 9A xx xx xx xx xx xx。一个是5字节长,一个是7字节长。其实E8 那种,就是我们在汇编指令里面写 CALL lable之后产生的,最常见的CALL指令。
然后是下面的FF /2。这个是0xFF字节后面跟上一个blablabla的东西。这个blablabla的东西是什么呢?要解释这个,首先需要知道红字标出来的部分,即ModR/M是什么东西。
这个要先回到最基本的一个问题:IA32的指令格式。
IA-32,Intel 64指令格式
其中每个部分是什么含义呢?
首先是指令前缀。有印象的应该记得当年学习微机原理的时候提到过得循环前缀 repnz/repne,这个前缀就是被编码在指令的前面部分的。每个前缀最多一个字节,一条指令最多4个前缀。
然后是指令代码(opcode),这部分标识了指令是什么。这个是指令当中唯一必需的部分。前面例子当中的 0xE8,0xFF都是opcode。
再后面就是我们要重点关心的 ModR/M字段了,还有和它密切相关的SIB字节。手册2.1.3当中有对于它们的详细描述。
许多指令需要引用到一个在内存当中的值作为操作数,这种指令需要一个称为寻址模式标识字节(addressing-form specifier byte),或者叫做ModR/M字节紧跟在主opcode后面。ModR/M字节包含下面三个部分的信息:
- mod(模式)域,连同r/m(寄存器/内存)域共同构成了32个可能的值:8个寄存器和24个寻址模式。
 - reg/opcode(寄存器/操作数)域指定了8个寄存器或者额外的3个字节的opcode。究竟这三个字节用来做什么由主opcode指定。
 - r/m(寄存器/内存)域可以指定一个寄存器作为操作数,或者可以和mod域联合用来指定寻址模式。有时候,它和mod域一起用来为某些指令指定额外的信息。
 
这一段有些晦涩。其意思解释一下是这样的:一个指令往往需要引用一个在内存当中的值,典型的就是如mov:
MOV eax, dword ptr [123456]
MOV eax, dword ptr [esi]
这其中的 123456 或者 esi 就是 MOV 指令引用的内存地址,而MOV关心的是这个地址当中的内容。这个时候,需要某种方式来为指令指定这个操作数的类型:是一个立即数表示的地址,还是一个存放在寄存器当中的地址,或者,就是寄存器本身。
这个用来区分操作数类型的指令字节就是 ModR/M,确切的说是其中的5个位,即mod和r/m域。剩下的三个位,可能用来做额外的指令字节。因为,IA32的指令个数已经远超过一个字节所能表示的256个了。因此,有的指令就要复用第一个字节,然后依据ModR/M当中的reg/opcode域进行区分。
现在回头看前面的红字标识的部分,能不能理解 /digit 这种表示法了?
对于SIB的介绍,我们先忽略,看看对于CALL指令的枚举我们已经能做什么了。
CALL指令的表示法:FF /2,是 0xFF 后面跟着一个 /digit 表示的东西。就是说,0xFF后面需要跟一个 ModR/M 字节,ModR/M字节使用 reg/opcode 域 = 2 。那么,reg/opcode = 2 的字节有32个,正如ModR/M的解释,这32个值代表了32种不同的寻址方式。是哪32种呢?手册上面有张表:
32字节寻址模式下的ModR/M字节
非常复杂的一张表。现在就看看这张表怎么读。
首先是列的定义。由于 reg/opcode 域可以用来表示opcode,也可以用来表示reg,因此同一个值在不同的指令当中可能代表不同的含义。在表当中,就表现为每一列的表头都有很多个不同的表示。我们需要关心的就是 opcode 这一个。注意看我用红圈圈出来的部分,这一列就是 opcode=2 的一列。而我们需要的 CALL 指令,也就是在这一列当中,0xFF后面需要跟着的内容。
行的定义就是不同的寻址模式。正如手册所说,mod + R/M域,共5个字节,定义了32种寻址模式。0x10 – 0x17 对应于寄存器寻址。例如指令 CALL dword ptr [eax] :[eax]寻址对应的是 0x10,因此,该指令对应的二进制就是 FF 10。同理, CALL dword ptr [ebx] 是 FF 13,CALL dword ptr [esi] 是 FF 16,这些指令都是2个字节。有人也许问 CALL word ptr [eax] 是什么?抱歉,这不是一个合法的32位指令。
0x50-0x57部分需要带一个 disp8,即 8bit 立即数,也就是一个字节。这个是基地址+8位偏移量的寻址模式。例如 CALL dword ptr [eax+10] 就是 FF 50 10 。注意虽然表当中写的是 [eax] + disp8 这种形式,但是并不表示是取得 eax 指向的地址当中的值再加上 disp8,而是在eax上加上disp8再进行寻址。因此写成 [eax+disp8] 更不容易引起误解。后面的disp32也是一样的。这个类型指令是3个字节。
0x90 – 0x97部分需要带 disp32,即4字节立即数。这个是基地址+32位偏移量。例如 CALL dword ptr [eax+12345] 就是 FF 90 00 01 23 45。有趣的是, CALL dword ptr [eax+10] 也可以写成 FF 90 00 00 00 10。至于汇编成哪个二进制形式,这是汇编器的选择。这个类型的指令是6个字节。
0xD0 – 0xD7部分则直接是寄存器。这边引用的寄存器的类型有很多,但是在CALL指令当中只能引用通用寄存器,因此 CALL eax 就是 FF D0,臭名昭著的 CALL esp 就是 FF D4。注意 CALL eax 和 CALL [eax] 是不一样的。这些指令也是2个字节。
仔细的人也许主要到了,在表当中,0x14, 0x15, 0x54和0x94是不一样的。0x15比较简单,这个要求 ModR/M后面跟上一个32位立即数作为地址。即常见的 CALL dword ptr [004F778e] 这种格式的,直接跳转到一个固定内存地址处存放的值,常见于调用Windows的导出表。对应的二进制是 FF 15 00 4F 77 8E ,有6个字节。
0x14,0x54,0x94部分是最复杂的,因为这个时候,ModR/M不足以指定寻址方式,而是需要一个额外的字节,这个字节就是指令当中的第4个字节,SIB。同样在手册的2.1.3,紧跟着ModR/M的定义:
某些特定的ModR/M字节需要一个后续字节,称为SIB字节。32位指令的基地址+偏移量,以及 比例*偏移量 的形式的寻址方式需要SIB字节。 SIB字节包括下列信息:
- scale(比例)域指定了放大的比例。
 - index(偏移)域指定了用来存放偏移量 的寄存器。
 - base (基地址)域用来标识存放基地址的寄存器。
 
0x14, 0x54, 0x94就是这里所说的“特定的ModR/M字节。这个字节后面跟着的SIB表示了一个复杂的寻址方式,典型的见于虚函数调用:
CALL dword ptr [ecx+4*eax]
就是调用ecx指向的虚表当中的第eax个虚函数。这个指令当中,因为没有立即数,因此FF后面的字节就是0x14,而 [ecx+4*eax] 就需要用SIB字节来表示。在这个指令当中,ecx就是 Base,4是Scale,eax是Index。
那么,Base, Scale和Index是如何确定的呢?手册上同样有一张表(又是巨大的表):
32位寻址模式当中的SIB字节
列是Base,行是Index*Scale,例如[ecx+4*eax] 就是0x81。
根据这张表,CALL dword ptr [ecx+4*eax] 就是 FF 14 81 。由此可见,对于 0x14系列的来说,CALL指令就是 3个字节。
而 0x54 带 8bit 立即数,就是对应于 CALL指令:CALL dword ptr [ecx+4*eax+xx],这个指令就是 FF 54 81 xx,是4个字节。
同理,0x94带32位立即数,对应于CALL指令:CALL dword ptr [ecx+4*eax+xxxxxxxx],这个指令就是 FF 94 81 xx xx xx xx,是7个字节。
OK,截止到目前,我们基本上能够列出常见的CALL指令的格式了:
| 指令 | 二进制形式 | 
| CALL rel32 | E8 xx xx xx xx | 
| CALL dword ptr [EAX] | FF 10 | 
| CALL dword ptr [ECX] | FF 11 | 
| CALL dword ptr [EDX] | FF 12 | 
| CALL dword ptr [EBX] | FF 13 | 
| CALL dword ptr [REG*SCALE+BASE] | FF 14 xx | 
| CALL dword ptr [abs32] | FF 15 xx xx xx xx | 
| CALL dword ptr [ESI] | FF 16 | 
| CALL dword ptr [EDI] | FF 17 | 
| CALL dword ptr [EAX+xx] | FF 50 xx | 
| CALL dword ptr [ECX+xx] | FF 51 xx | 
| CALL dword ptr [EDX+xx] | FF 52 xx | 
| CALL dword ptr [EBX+xx] | FF 53 xx | 
| CALL dword ptr [REG*SCALE+BASE+off8] | FF 54 xx xx | 
| CALL dword ptr [EBP+xx] | FF 55 xx | 
| CALL dword ptr [ESI+xx] | FF 56 xx | 
| CALL dword ptr [EDI+xx] | FF 57 xx | 
| CALL dword ptr [EAX+xxxxxxxx] | FF 90 xx xx xx xx | 
| CALL dword ptr [ECX+xxxxxxxx] | FF 91 xx xx xx xx | 
| CALL dword ptr [EDX+xxxxxxxx] | FF 92 xx xx xx xx | 
| CALL dword ptr [EBX+xxxxxxxx] | FF 93 xx xx xx xx | 
| CALL dword ptr [REG*SCALE+BASE+off32] | FF 94 xx xx xx xx xx | 
| CALL dword ptr [EBP+xxxxxxxx] | FF 95 xx xx xx xx | 
| CALL dword ptr [ESI+xxxxxxxx] | FF 96 xx xx xx xx | 
| CALL dword ptr [EDI+xxxxxxxx] | FF 97 xx xx xx xx | 
| CALL EAX | FF D0 | 
| CALL ECX | FF D1 | 
| CALL EDX | FF D2 | 
| CALL EBX | FF D3 | 
| CALL ESP | FF D4 | 
| CALL EBP | FF D5 | 
| CALL ESI | FF D6 | 
| CALL EDI | FF D7 | 
| CALL FAR seg16:abs32 | 9A xx xx xx xx xx xx | 
Intel call指令的更多相关文章
- intel instruction 指令速查
		
参考:http://ref.x86asm.net/ http://ref.x86asm.net/coder32.html
 - Intel 移位指令的陷阱(转)
		
今天发现了一个Intel逻辑左移指令shl的一个bug. 逻辑左移的概念是对给定的目的操作数左移COUNT次,每次移位时最高位移入标志位CF中,最低位补零. 其中OPRD1为目的操作数, 可以是通 ...
 - 从X86指令深扒JVM的位移操作
		
概述 之所以会写这个,主要是因为最近做的一个项目碰到了一个移位的问题,因为位移操作溢出导致结果不准确,本来可以点到为止,问题也能很快解决,但是不痛不痒的感觉着实让人不爽,于是深扒了下个中细节,直到看到 ...
 - 可怕!CPU暗藏了这些未公开的指令!
		
大家好,我是轩辕. 我们知道,我们平时编程写的高级语言,是经过编译器编译以后,变成了CPU可以执行的机器指令: 而CPU能支持的指令,都在它的指令集里面了. 很久以来,我都在思考一个问题: CPU有没 ...
 - [转]Linux下的lds链接脚本详解
		
转载自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 一. 概论 每一个链接过程都由链接脚本(lin ...
 - Chrome的Crash Report服务
		
<本文转自:http://www.cppblog.com/woaidongmao/archive/2009/10/22/99211.aspx> 本文翻译自debugInfo网站上一篇文章g ...
 - Linux下的lds链接脚本基础
		
转载:http://soft.chinabyte.com/os/104/12255104.shtml 今天在看uboot引导Linux部分,发现要对链接脚本深入了解,才能知道各个目标文件的内存分布 ...
 - Linux下的lds链接脚本简介
		
转载:http://hubingforever.blog.163.com/blog/static/171040579201192472552886/ 一. 概论 每一个链接过程都由链接脚本(lin ...
 - RHEL 7特性说明(七):编译程序及工具
		
转载自:RedHat https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/7/html/7.0_Release ...
 
随机推荐
- HTML5 iframe sandbox javascript getTime
			
1 1 HTML5 iframe sandbox 1 1 <!DOCTYPE html> <html> <body> <iframe src="de ...
 - 【】Python】异常处理try...except、raise
			
一.try...except 有时候我们写程序的时候,会出现一些错误或异常,导致程序终止.例如,做除法时,除数为0,会引起一个ZeroDivisionError 例子: 1 2 3 4 a=10 b= ...
 - hdu 1851(A Simple Game)(sg博弈)
			
A Simple Game Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/65535 K (Java/Others)Tot ...
 - Dom-直接 /间接选择器
			
Dom:Document Object Model的缩写, 把html转换成了文本对象. 1. 直接选择器 1.直接查找 document.getElementById 根据ID获取一个标签 docu ...
 - JavaScript 面向对象开发知识基础总结
			
JavaScript 面向对象开发知识基础总结 最近看了两本书,书中有些内容对自己还是很新的,有些内容是之前自己理解不够深的,所以拿出来总结一下,这两本书的名字如下: JavaScript 面向对象精 ...
 - [CF551E]GukiZ and GukiZiana
			
题目大意:一个长度为$n(n\leqslant5\times10^5)$的数组,有两个操作: $1\;l\;r\;x:$把区间$[l,r]$加上$x$ $2\;x:$询问$x$第一次出现和最后一次出现 ...
 - hdu1950 Bridging signals
			
LIS nlogn的时间复杂度,之前没有写过. 思路是d[i]保存长度为i的单调不下降子序列末尾的最小值. 更新时候,如果a[i]>d[len],(len为目前最长的单调不下降子序列) d[++ ...
 - BZOJ5319 & 洛谷4559 & LOJ2551:[JSOI2018]军训列队——题解
			
https://www.lydsy.com/JudgeOnline/problem.php?id=5319 https://www.luogu.org/problemnew/show/P4559 ht ...
 - 关于Javac编译器的那点事(一)
			
Javac是什么? 它是一种编译器,将Java对人非常友好的语言,编译转化对所有机器都非常友好的语言,即:JVM能够识别的语言,也就是Java字节码.而Java字节码,说白了就是一连串二进制数字. J ...
 - ContestHunter暑假欢乐赛 SRM 15
			
菜菜给题解,良心出题人!但我还是照常写SRM一句话题解吧... T1经典题正解好像是贪心...我比较蠢写了个DP,不过还跑的挺快的 f[i]=min( f[j-a[j]-1] )+1 { j+a[j ...