这是汇编语言基础最后一篇,以后还会更新更高级的汇编知识,并且这部分知识会应用到
逆向编程的环节,这一章介绍汇编基础--机器指令。
一个16比特位的汇编指令:
opcode操作码占用3个比特位,可以表示2的3次方为8种操作
寄存器占用2个比特位,可表示2的2次方为4种可用寄存器
地址空间为2的11次方为2048个可能的内存单元地址可用。
inc 指令和dec指令
从图中可以看出这些指令长度仅为一字节,因为每个指令地址相差为1字节。
inc eax 这条指令的机器码为40, 40为16进制表示,转为二进制为 0100 0000
inc ecx 这条指令机器码为41,41位16进制表示,转换为二进制为0100 0001
依次展开会发现机器码的规律,开头都为01000XXX
而XXX实际就是寄存器在机器中表示的二进制机器码。
下面是各个寄存器的机器码:
下面看下dec 寄存器指令图:
dec 寄存器指令的格式可以总结为
01001xxx, xxx为寄存器二进制指令格式。
mov 指令
mov eax, reg指令图
每个指令长度为2字节,从地址偏移可以看出。
将上面指令机器码转为二进制
mov ecx, eax 十六进制为8B C8,二进制表示第二个字节 1100 1000
mov ecx, ecx 十六进制为 8B C9,二进制表示第二个字节为 1100 1001
通过对比二进制,发现第一个字节都为8B,第二个字节分别为 C0,C1,C2,C3...C9
mov reg, reg 指令格式为
10001011 11XXXYYY
XXX为目的寄存器, YYY为源寄存器
mov reg, imm 即将一个立即数移动到寄存器中指令的机器码会是什么样呢?
下图为将立即数移动到寄存器的图示:
mov eax, 1 指令机器码为 B8 00000001
mov ecx, 10 指令机器码为 B9 0000000A
两条指令地址相差5个字节,每个字节8bit,可计算出每条指令为40bit长度。
即10个十六进制数表示。而 B8 00000001 和 B9 0000000A 恰巧为10个16进制数字组成。
机器码B8 00000001 从左向右数,去掉B8占用的一个字节,剩下的四个字节可以看出用来表示
立即数 1。同样的道理,可以看出 B9 0000000A也是这个原理。
如果移动的为负数,怎么表示呢?
mov edx, -1 这个指令我们分析一下 -1在机器中的表现形式
负数在机器中以补码的形式表现,-1 的补码计算规则为:
1的源码为 0000 0000 0000 0000 0000 0000 0000 0001
按位取反 为 1111 1111 1111 1111 1111 1111 1111 1110
末尾+1 位 1111 1111 1111 1111 1111 1111 1111 1111
转换为十六进制为 F F F F F F F F 恰好就是 机器码的最后三个字节表示。
同样的道理适用于mov ebx, -10
下面分析前两个字节 BA, BB, B8, B9 分别有什么关联。
同步对比可以看出 前几个比特位是一样的,都为 10111
后三个比特位分别为 000, 001, 010, 011,这四个二进制码恰好为
几个寄存器的二进制表示方法。所以
mov reg, imm 机器指令为
10111XXX YYYYYYYY YYYYYYYY YYYYYYYY YYYYYYYY
mov reg, mem 将内存数据移动到寄存器中 的机器指令怎么表示?
mov mem, reg 将寄存器中的数据移动到内存中,机器指令如何表示?
下图定义了变量num1, num2, num3
num1 地址为 0000 0000
num2 地址为 0000 0004
num3 地址为 0000 0008
下图为指令对应的机器码:
除去A1,A3开始的一个字节,剩下的4个字节分别为 十六进制4 和十六进制8,分别为num2的地址
和num3 的地址。
下面分析第一个字节A1和A3 规律:
可以得出结论,无论将内存数据移动到eax中,还是将eax中的数据移动到内存中,
最后的4个字节表示的都是内存的地址,第一个字节表示的不同,用来表示两种移动方式的区别。
总结规律如下:
mov eax, mem 10100001 YYYYYYYY YYYYYYYY YYYYYYYY YYYYYYYY
mov mem , eax 10100011 YYYYYYYY YYYYYYYY YYYYYYYY YYYYYYYY
下面图表表示了ebx,ecx,edx三种寄存器和内存数据移动指令
可以看出其他寄存器(eax, ecx, edx)和内存之间移动数据的操作指令大小为6字节,多出的为第二字节,
0D,15,1D。
第一字节8B表示从内存移动数据到寄存器,89表示从寄存器移动到内存,如下图所示:
第二个字节图表如下:
通过二进制可以看出 前两位都为 00, 中间三位为 001 ,010, 011 分别表示ecx, edx, ebx
最后三位为101, 其实这个字节不仅仅用于表示移动,还可以表示很多操作,因为ecx为循环控制,
ebx为基址寄存器, edx可用于存余数等等,所以 前两位为00,且最后三位为101,这个组合表示移动
操作。中间三位表示操作的寄存器是什么。
该字节概括为如下图所示:
mod 字段为00, 且r/m字段值为101,它表示地址模式数据置换,也就是指向内存地址模式。
add 指令 sub 指令
add指令很简单,给出图表读者自己分析。
字节转为二进制可以看出从右往左数第3到1位为第二个操作的寄存器,
从右往左数第6到4位表示第一个操作的寄存器, 两个寄存器操作模式为 第7~16位所表示。
movoffset 指令 lea 指令
num2 的值为5, 地址为 00000004, 分别将num2 的内容移动到esi和edi,再通过lea指令将num2地址
放入esi和edi
通过对比可以看出后四个字节都为 0000 0004 , 但是前两个字节是不一样的。将前两个字节展开
为二进制
由于mov esi, num2 是将num2数据存入esi,而 lea esi, num2 是将num2地址放入esi,所以
第一个字节的倒数第二位不同,第一个字节分别为10001011 , 10001101,mov和lea第二个字节是相同的。
下面对比两个lea指令前两个字节 ,第一个字节是相同的,第二个字节为 00 110 101 和 00 111 101
第二个字节中间三位不同,分别为110(esi), 111(edi)表示寄存器。
所以可以总结一下, mov 指令和 lea指令区别在于第一个字节,计算机用第一个字节区别mov和lea指令。
计算机用第二个字节中间三位区别lea指令操作的不同寄存器。
第二个字节和我们上面说过的:
mod 字段为00, 且r/m字段值为101,它表示地址模式数据置换,也就是指向内存地址模式。
下面看一下 mov esi, offset mem 和 mov edi, offset mem两条指令。
可以看出mov offset指令为5个字节,比 mov 和lea指令少了一个字节,因为mov offset仅仅在编译的时候加载地址,
所以不需要lea的第二个字节表示 数据移动操作。 mov offset是静态的。
mov offset 指令esi和 edi区别仅仅在第一个字节,展开后可以看到:
第一个字节的后三位 分别为 110(esi), 111(edi)。
可以得出结论mov offset 的指令第一个字节后三位区别esi还是edi,其余不变。
jmp指令
看一则jmp指令操作
jmp 指令 机器码为EB + 相对偏移地址 ,
如 jmp around 为EB 04 ,通知计算机跳转到当前指令指针位置+4字节的位置,
需要普及一个知识,当程序运行的时候,指令指针或者CPU中的指令指针指向下一条将要取到CPU中
被后续执行的指令。当运行到 jump around时,指令指针实际指向了 地址000000D8,指令 above:nop的位置,
EB 04指向 为 000 000 DB 加上4个字节地址即为 000 000 DC, 恰好是 around:nop指令地址。
符合逻辑。
下面看下 jump above指令会跳转到哪里。
EB FC 指令 FC 为 1111 1100 , 该数值为某个负数的补码,负数补码的计算规则为
符号位不变,其他位按位取反末位+1。
同样的道理,负数补码转为原码,符号位不变,按位取反末位+1
1111 1100 符号位不变,按位取反 1000 0011, 末位+1,
变为 1000 0100 表示-4.
jmp above 机器指令 EB FC 跳转到 指令指针地址向前移动四个字节的位置。
jmp above 指令运行时,指令指针指向下一条将要取出的指令位置,即 000 000 DC,
000 000 DC - 4 为 000 000 08,即 above:nop 的位置。
到此为止机器指令的知识介绍完毕,以后会介绍高级汇编和反汇编的知识,
汇编基础介绍告一段落。
我的微信公众号,谢谢关注。