Machine Code Opcodes on the x86 -TI #1- Compiled by: Cyclone [Disclaimer] I am NOT responsible for whatever you decide to do with this information. I am also not responsible for the correctness of the information provided, although I did my best to see to it that it is correct (the book I used had a ton of errors, so one or two might have survived. If that's the case sorry). [Introduction] This text assumes you already have a working knowledge of the workings of the x86 assembly language. I have, therefore, decided against typing up the function of each of the instructions, as I didn't feel like doing that much typing, and the x86 instruction set is fairly well documented anyway. For full use of this text, I recommend you have a list of the various instructions and their function. So without further adieu... The structure of a typical assembly instruction is as follows: ---------- ---------- ---------- ------------ ------------ Prefix Opcode Address Displacement Data 1-byte 1/2-byte 1/2-byte 1/2/4-byte 1/2/4-byte (optional) (optional) (optional) (optional) ---------- ---------- ---------- ------------ ------------ [Prefixes] There are 5 types of prefixes: Operand Size Address Size Segment Override - cs,ds,es,ss,fs,gs Bus LOCK - lock Repeat - repz, repnz/rep More than one prefix can be uses for one instruction. The order of the prefixes does not matter as long as the prefixes are of different types. If the prefixes are of the same type than the last one takes effect. For example: cs: es: mov [si],5 is equivalent to: es: mov [si],5 Note: use32 \ repnz = rep movsd (in real mode) movsw / is equivalent to: repnz \ use32 = rep movsd (in real mode) movsw / It is worth while to point out that the REP/REPNZ instruction can only precede: LODS, STOS, MOVS, INS, OUTS, CMPS, SCAS. The REPZ instruction can only precede CMPS and SCAS. Putting these repeat instructions with other instructions will have no effect. Also, The LOCK instruction can only precede certain instructions. These are: XCHG mem, ADD mem, ADC mem, SUB mem, SBB mem, NEG mem OR mem, AND mem, XOR mem, NOT mem BTC mem BTR mem BTS mem if a lock precedes an instruction other than one listed above the CPU will not be a happy camper and issue an exception 6 (invalid opcode) Other prefixes like the address, operand, and segment prefixes are much more forgiving. When they are paired with something silly (like NOP), they are just ignored. One final note about prefixes: There is an upper limit to the size of an instruction. For example, the 386's largest instruction is 15 bytes. This can only be achieved ny excess prefixing. If the instruction becomes too large for the processor to handle, the CPU screams (issues an exception 13). [Opcodes] Opcodes tell the computer which instructions are to be executed. These can be one to two bytes long. Originally, way back on the 8086, all the opcode bytes were a single byte. Well, eventually, this proved to be too little for our friends at Intel, and the size was expanded to up to two bytes. To do this one opcode was scrapped from the "POP segreg" family. The Instruction "POP CS" was removed and now its bit pattern basically means: "more opcodes coming". Pop Seg. Reg. - 000xx111 Pop CS - 00001111 Thus POP CS doesn't work on an 286 or higher. The Opcode does contain some useful info. Let's look at the SUB opcode. The byte to subtract a register from another register is: ----------------- |0|0|1|0|1|0|d|w| ----------------- What the heck is a "w" and a "d" bit you might ask? Fair enough. A "w" bit decides whether the operand is a byte size, or bigger. How big? Well, that depends on the mode of the processor. If the CPU is in protected mode or 32-bit mode, then it's 32-bits. If, on the other hand, the CPU is running real or virtual mode, then it's 16-bit. The "d" bit decides which operand comes first. A value of '0' means the "mod R/M" is the first operand while "reg" is operand 2 (for more info on what the "mod R/M" and "reg" fields are, see the next section). If the bit was '1', then the "reg" field is operand 1, while "mod R/M" is operand 2. For example: (001010dw = sub, "mod R/M"= [bx], "reg"= AX ) 00101000 ... - sub [BX], AL The opcode can also sometimes have a reg field, as in the case of "inc reg", "dec reg", "xchg ax,reg", "push", "pop". Also, sometimes the opcode byte alone, does not determine the entire instruction. This happens mainly in cases where an immediate value is involved. In these cases, the rest of the operation is usually stored in the "reg" field. [Addresses] It's time we talk about one of the ugliest things in the x86 language - the Address byte(s). It's not hard, but it is a wee bit messy. Lets start in Real mode. In real mode the Address byte - is just that (1 byte). It contains a "Mod" field, a "R/M" field, and a "Reg" field as follows: ----------------- |mod| reg | R/M | ----------------- The "Mod" & "R/M" fields make up the first operand while the "reg" field makes up the second. The full table of values is in the accompanying text. Notice that the combination of the mod & R/M fields is 5-bits. This allows many more options for the first operand than the second. If mod = 0 then the thing specified by R/M is a pointer (ex. mod=00, R/M=100 => [SI]). When mod = 1, an 8-bit displacement is added (ex. mod=01, R/M=100 => [SI+d8]). When mod is 2, a full 16-bit displacement is added (ex mod=10, R/M=100 => [SI+d16]). When mod = 3, the second field is a register - not a memory location. I think it's time for an example. (001010dw = sub, reg: AX=000 CX=001 DX= 010 BX=011) 00101001 00000000 - sub [BX+SI], AX ; mod=00 R/M=000 => [BX+SI] 00101001 01001100 - sub [SI+d8], CX ; mod=01 R/M=100 => [SI+d8] 00101011 00000100 - sub AX, [SI] ; mod=00 R/M=100 => [SI] 00101011 11001010 - sub CX, DX ; mod=11 R/M=010 => DX Hopefully that's clear enough, so I can mess with your mind some more in protected mode. If you don't care about Protected Mode, feel free to skip to the next section. Protected mode isn't so easy - what else is new? IN Protected mode can have upto 2 Address bytes of the following structure: ----------------- [ ----------------- ] |mod| reg | R/M | [ |ss |index| base| ] ----------------- [ ----------------- ] The second byte only appears if the R/M field is 100. If the R/M field is not 100, then the structure is almost the same as in real mode with one exception. The values mean different things. For example: mod=00 RM=000 in real mode = [BX+SI] in protected mode = [EAX] When the R/M field does equal 000 then the second byte is added. It contains: ss - A scale factor of the index (times 1,2,4,8) index - index (like in an array) base - holds the info the R/M couldn't For example: mov [EAX+EBX*8],EDX ; (mod=00) | | | |--- reg field | | |------- ss (scale factor) | |---------- index |-------------- base [Displacements] In the last section you might have noticed I mention displacements. They were labelled as d8 and d16 in my examples. There's noting hard about displacements. Displacements are a constant number added to a memory location. Example: mov [SI],ax ; just [si] - no displacement mov [SI+2],ax ; 2 - usually a byte (8-bit) displacement mov [SI+1000],ax ; 1000 - 16-bit displacement. Displacements are signed numbers. That means they do represent negative numbers (FFh = -1, FEh= -2, etc). This allows things like: mov [SI-2],ax The size (if any) of the displacement field is determined by the Address byte. And that's really all there is to know about that. [Data] Some Opcodes require immediate data. This is where that data is put (at the end of the instruction). Examples: mov ax,1234h ; data = 1234h int 21h ; data = 21h shl ax,4 ; data = 4 Some opcodes require more than one piece of data (like direct far jumps and calls). These are well labeled in the other text though. [So Example time (again)] So let's say you want to create: sub ax, es:[BX+6] You look up SUB and you find: -SUB-------Integer Subtract ----------------- ----------------- Register/Memory |0|0|1|0|1|0|d|w| |mod| reg | R/M | ----------------- ----------------- Immediate to ----------------- ----------------- Register/Memory |1|0|0|0|0|0|s|w| |mod|1|0|1| R/M | -Data- ----------------- ----------------- Immediate to ----------------- AL/AX/EAX Reg. |0|0|1|0|1|1|0|w| -Data- ----------------- Well, we have no immediate data so we're concerned only with the first one. ----------------- ----------------- |0|0|1|0|1|0|d|w| |mod| reg | R/M | ----------------- ----------------- The first thing the instruction needs is a prefix. The prefix is es:. So we look up ES: and find: ----------------- |0|0|1|0|0|1|1|0| ----------------- This is the first byte of the instruction. (26h) The instruction "sub ax,es:[bx+6]" is word sized (we're dealing with ax). Thus the "w" bit is set to 1. The more "complex" instruction which can be represented only by the mod R/M part of the address is operand 2, thus the "d" bit is set. Thus the opcode looks like: 00101011b = 2Bh Now we need reg= 000 => AX, and mod=01 & reg=110 => [BX+d8] Thus slapping these values into the address byte we get: 01 000 110 = 46h The displacement is 6 and it's a byte => 06h And we have no immediate data. Thus sub ax,es:[bx+6] = 26h, 2Bh, 46h, 06h prefix opcode addr. disp. [Final Example] cs:0100h jb 1009h (numbers are memory locations) No prefixes to worry about. The instruction we need has Full 16-displacement. It follows: ----------------- ----------------- Full Displ. - |0|0|0|0|1|1|1|1| |1|0|0|0| cond. | Full displacement ----------------- ----------------- the condition we need (b = below) = 0010. Thus our opcode is: 0Fh, 82h Displacements are calculated by, finding out the ending address of the current instruction and subtracting it from the destination instruction. Thus we have: 1009h [dest] - (0100h (start of instruction) + 4 (length of instruction)) = 1005h Thus our displacement is: 1005h However intel stores it's numbers backward, so in memory 1005h looks like: 05h, 01h Our instruction thus is: 0Fh, 82h, 05h, 01h Please note: only numbers are flipped. Opcode are not. Well, I hope that this was at least a little informative and that you enjoyed this doc at least as much as I hate typing :) Let me know what you think and good luck with your endeavours, whatever they may be. Cyclone