9. 指令解析
一、 assemble
这里我想写一段汇编代码,作为源代码,然后手工译为机器编码
1、 assembly 源代码
这段是摘自我的 mouseOS 里面的一段代码 kstrcmp() 用来比较这个字符串是否相等:
;------------------------------------------------------- kstrcmp_loop: movzx r10, byte [rdi] kstrcmp_eql: inc rax kstrcmp_done: |
2、 encodes (机器编码)
下面是我完全用人工译出来的机器码:
0: 48 31 c0 3: 4c 0f b6 17 7: 4c 0f b6 1e b: 4d 39 da e: 75 10 * (jnz kstrcmp_done) 10: 4d 85 d2 13: 74 08 * (jz kstrcmp_eql) 15: 48 ff c7 18: 48 ff c6 1b: eb e6 * (jmp kstrcmp_loop) 1d: 48 ff c0 20: c3 |
下面再看一看 nasm 编译的情况:
00000000 4831C0 xor rax,rax 00000003 4C0FB617 movzx r10,byte [rdi] 00000007 4C0FB61E movzx r11,byte [rsi] 0000000B 4D39DA cmp r10,r11 0000000E 7513 jnz 0x23 * (jnz kstrcmp_done) 00000010 4D85D2 test r10,r10 00000013 740B jz 0x20 * (jz kstrcmp_eql) 00000015 48FFC7 inc rdi 00000018 48FFC6 inc rsi 0000001B E9E3FFFFFF jmp dword 0x3 * (jmp kstrcmp_loop) 00000020 48FFC0 inc rax 00000023 C3 ret |
对比 nasm 编译出来的机器码,不难发现 nasm 并不能编译出最优秀的代码:
(1)nasm 译出 0x23 个 bytes,比我手工译出来的要多 3 个 bytes。
(2)nasm 对于指令 jmp kstrcmp_loop 译为 5 bytes,而实际上只需要译为 2 bytes 就行了。
这是因为 nasm 使用了 32 位的 immediate 值,而我使用了 8 位的 immediate 值。
(3)由于上面这条指令的译法与我的译法不同,从而导致了 3 条 jmp 指令都不同。
3、 指令 jnz kstrcmp_done 解析
指令 jnz 的 Opcode 是 75,它的描述为 JNZ Jb,它的 operand 是基于 rip 的 8 位 offset 值。
这个 offset 的计算方法是: target = rip + offset 所是: offset = target - rip
★ 由上所得:当前的 rip 为 0x10,而标号 kstrcmp_done 的地址 0x20
★ 所以: offset = target - rip = 0x20 - 0x10 = 0x10
因此:它的 encode 为:75 10
二、 decode
1、这是一段摘自 windows 7 的一段机器码:
fffff800`026ede60 59 65 48 8b 0c 25 20 00 00 00 80 79 20 01 77 72 fffff800`026ede70 0f 31 48 c1 e2 20 48 0b c2 48 2b 81 c0 67 00 00 fffff800`026ede80 48 01 81 f8 67 00 00 48 01 81 c0 67 00 00 48 8b |
2、看一看它到底是什么代码:
下面我用人工译码:
fffff800_026ede60: 59 pop rcx fffff800_026ede61: 65 48 8b 0c 25 20 00 00 00 mov rcx, qword ptr gs:[0x20] fffff800_026ede6a: 80 79 20 01 cmp byte ptr [rcx + 0x20], 0x01 fffff800_026ede6e: 77 72 ja .+0x72 *(.表示当前的 rip 值) fffff800_026ede70: 0f 31 rdtsc fffff800_026ede72: 48 c1 e2 20 shl rdx, 0x20 fffff800_026ede76: 48 0b c2 or rax, rdx fffff800_026ede79: 48 2b 81 c0 67 00 00 sub rax, qword ptr [rcx + 0x67c0] fffff800_026ede80: 48 01 81 f8 67 00 00 add qword ptr [rcx + 0x67f8], rax fffff800_026ede87: 48 01 81 c0 67 00 00 add qword ptr [rcx + 0x67c0], rax fffff800_026ede8e: 48 8b ... ... |
这里只是纯作译码工作,这里看不出这段代码作用。
下面再看看 ndisasm 译的结果:
00000000 59 pop rcx 00000001 65488B0C25200000 mov rcx,[gs:0x20] -00 0000000A 80792001 cmp byte [rcx+0x20],0x1 0000000E 7772 ja 0x82 *(0x10+0x72=0x82) 00000010 0F31 rdtsc 00000012 48C1E220 shl rdx,0x20 00000016 480BC2 or rax,rdx 00000019 482B81C0670000 sub rax,[rcx+0x67c0] 00000020 480181F8670000 add [rcx+0x67f8],rax 00000027 480181C0670000 add [rcx+0x67c0],rax 0000002E 48 rex.w 0000002F 8B db 0x8b |
这里除了显示格式不同外, 结果是完全一样的。
3、来看一看这个 encode:65 48 8b 0c 25 20 00 00 00 应该怎么 decode
(1)65 它是一个 prefix,是 GS - segment override prefix
(2)48 它是一个 REX prefix,REX.W = 1 使用 64 位的 operands,而 REX.R = REX.X = REX.B = 0
(3)8b 它是 Opcode 码,表示:MOV Gv, Ev 即:frist operand 是 registers 而 second operann 是 register/memory,
operand size 是 effective operand size,到目前为止,还不知道 operand 是什么
(4)0c 紧接着 Opcode 码,那么,它就是 ModRM 字节, ModRM = 00-001-100
它提供的 register 是 rcx, ModRM.r/m 提供的寻址是 100 这是 [SIB] 字节,引导出 SIB,因此,下一字节必定是 SIB 字节。
(5)25 由第 (4)步得出,它是个 SIB 字节, SIB = 00-100-101 代表 scale = 00,index = 100, base = 101
由于 index = 100(rsp),因此这里不存在 index 部分,只有 base 部分。
base = 101(rbp),因此它将代表一个 disp32 值,那么,下面接下来的 4 个 bytes 就是 displacement 值
(6)20 00 00 00 这是由第(5)得知,这 4 个 bytes 是 displacement。
----------------------------------------------------------------------------------------
经过上面 6 个步聚的分析,得出最终的汇编指令是:mov rcx, qword ptr gs:[0x20]
mik(deng zhi)写于 2008-11-26 15:40