To gain a good understanding of Minix, you need to be able toread its assembly code, split between the files mpx386.s(line 5900) and klib386.s (line 8100). This handoutdescribes the Intel 386 assembly language used by Minix.
Copyright © 2001, 2006, Carl Burch(Hendrix College, Conway AR 72032,cburch at the domain cburch DOT com).Thiswork is licensed under a Creative CommonsAttribution-ShareAlike 2.5 License. |
add | and | call | cld | cli | cmp | cmpb | db |
dd | dec | decb | dw | equ | global | in | inb |
inc | incb | ins | int | iret | ja | jae | jb |
jbe | jg | jge | jl | jle | jmp | jnz | jz |
lea | lgdt | lidt | lldt | loop | ltr | mov | movs |
movsb | movzx | neg | nop | o16 | or | out | outb |
outs | pop | popad | popf | push | pushad | pushf | rep |
ret | rol | ror | section | sgdt | shl | shr | std |
sti | stos | sub | test | times | xchg | xor |
The 80386 chip represents Intel's first serious processor in along line of microprocessors. It began with the Intel 4004, thefirst general-purpose microprocessor, a simple 4-bit chiplaunched in 1971. The Intel 4004 was just meant for calculators(and in 1971, calculators weren't powerful devices).But it was a start, if a humble one.The 8-bit Intel 8080 (1974) was the CPU for thefirst successful personal computer, the Altair.And then the 16-bit Intel 8088 (1979) became the CPU for thefirst IBM PC. Subsequent generations became new CPUs for IBM PCsand their clones. But the 386 stands out among them, as thefirst 32-bit processor, and the first Intel processor to providehardware support for the features needed for real operatingsystems (such as large memories, protection levels, paging).Subsequent processors in the family have provided enhancedperformance more often than enhanced capabilities.
Standards Institute (ANSI) D16.1 Standard, 2007 Manual on Classification of Motor Vehicle Traffic Accidents, 7th edition as the primary source for definitions, classifications of crashes and related diagrams. The assignment of crashes to a geographical location, such as a city or county. The instruction at 0x01764b69 referenced memory at 0x00000000. The memory could not be read. Click on OK to terminate the program.
But here we're concerned with the Intel 80386 architecture, usedin Minix. There's a variety of x86 assembly languages.We'll use the language of NASM, an open-sourceassembler that is widely available and is pretty close to whatMinix uses.
Registers
Figure 1. Registers in the Intel 80386.
The first thing to learning a processor is its registerstructure. See Figure 1.
This structure is pretty messy. In particular, the registers overlap.The eax register holds 32 bits,but you can refer to its lower 16 bits using ax, and withinax you can refer to its higher 8 bits with ah and thelower 8 bits with al.This weird structure is an outgrowth of the fact that the x86 designbegan with an 8-bit design, then grew into 16 bits, theninto 32 bits, each time maintaining backward compatibility.
The left-hand block, from eax to esp, represents thegeneral registers,where computation is meant to take place. Most have some specialproperties, in the sense that some instructions treat them in a specialway. For example, the push and pop instructionstreat esp (meant to be the stack pointer) specially,in that they access memory relative to the address stored in espand decrement and increment the value stored in esp. We'llsee how the other registers are special when we get to thoseparticular instructions.
The eip register is the instruction pointer (often calledthe program counter). The eflags register holds various flagsthat are altered or accessed by instructions.
The last block of six registers, cs through gs, are thesegment registers, used for memory addressing. You don'treally need to know much about them right now --- but we'll getto them eventually.
Memory
Like most other computers, the x86 architecture regularly accessesmemory off the chip in order to fetch instructions and manipulate datathat doesn't fit into the registers.
The x86 architecture uses the little endian technique for storingdata of multiple bytes. Say we store the 16-bit value 0x1234 intomemory at address 100. The value 0x12 goes into address 101 andthe value 0x34 goes into address 100. Storing the 32-bit value0x12345678 into address 100 would place 0x12 into address 103 and 0x78into address 100.
Many other architectures use the big endian technique, whichputs the highest-order bits into the lowest address. They both workwell. Which you like depends on whether you picture memory withaddress 0 at the bottom or with address 0 at the top. Both ways makesense, but the x86 designers had to choose one, and they clearlythought of address 0 being at the bottom.
Arithmetic instructions
addr/m, r/imm | subr/m, r/imm |
addr, m | subr, m |
negr/m | cmpr, r/m |
incr/m | cmpm, r |
decr/m | cmpr/m, imm |
Table 1. Arithmetic instructions
The first set of instructions we'll examine are the arithmeticinstructions, listed in Table 1. In these tables,we use r to represent a register, m to represent a memoryreference, and imm to represent a constant value (immediate).
The add and sub instructions are for performingadditions and subtractions. They have a variety of ways in whichthey can be used. The following illustrates them.
In each of these, the item changed is listed first, and the numberto be added is listed second. The instruction add eax, 1increments the value in eax. Andthe instruction add eax, eax adds the value of eax to eax,effectively doubling eax.Enclose memory references in brackets.So the instruction add eax, [data] says to load the data stored atthe memory address that data represents, and addthat value to eax.
(You'll findthat the Minix code reverses parentheses and brackets.Parentheses are used for memory references, and brackets areused for parenthesizing computations to be done at assemblytime. Another difference is that Minix uses exclamation points to mark comments, whileNASM usessemicolons.)
Both add and sub change flags in the eflags registerbased on the result, which is useful when more information isneeded later about the computation's result.
- The carry flag is set if the computation carried an extrabit beyond the capacity of the register, or if the computation borroweda bit beyond the capacity of the register.
- The zero flag is set if the result is zero.
- The sign flag is set if the result's high-order bit is 1(representing a negative number).
- The overflow flag is set if the computation, interpreted asa signed computation, goes beyond the capacity of the register. (Thisis analogous to the carry flag, but the carry flag represents thecomputation interpreted as an unsigned computation.)
The cmp instruction is for comparing two values.It's actually analogous to the sub instruction, butcmp does not change the value of the target register.Its only effect is to change the flags according to thedifference of the two values.
The neg instruction negates the target value (either aregister or memory location). (In the rare case that the valuedoesn't fit --- which only happens if the value is the smallestnumber possible for its bit size --- the value is unchanged and theoverflow flag is set.)
Finally, the inc and dec instructions increment or decrementthe given value. Why use inc eax instead of add eax, 1? A minorreason is that it is slightly more efficient, since the instruction isjust one byte long instead of three (leading to better cache use). (Onolder processors, it actually was faster.) Also, the inc anddec instruction do not affect the carry flag, which may beuseful in some situations. The most common reason is that it's just alittle easier to read.
Minix occasionally appends b to the end of aninstruction name, as in incb, decb, and cmpb.(This also applies to many instructions described later in thisdocument.)The appended b denotes that the instruction is abyte instruction (using registers like al or ch)instead of the regular 32-bit instructions normally used.You'll also see o16 in the Minix assembly code, used as a prefixfor many instructions. This is to indicate that the instruction shouldbe assembled into an instruction using 16-bit data.
This instruction is ambiguous: Does data hold an 8-bit value,a 16-bit value, or a 32-bit value? NASM will refuse to assemble it.We include the keyword dword to disambiguate.Notice that add eax, 4 isn't ambiguous, since the assembler knowsthat eax is a 32-bit location.Control instructions
nop | jmpr/m/label |
jzlabel | jnzlabel |
jalabel | jglabel |
jaelabel | jgelabel |
jblabel | jllabel |
jbelabel | jlelabel |
callr/m/label | ret |
looplabel |
Table 2. Control instructions
Of the control instructions (Table 2), thenop instruction is certainly the simplest: It does nothing.It just occupies a byte in memory. It's convenientoccasionally; for example, a compiler might put in a nop as aplaceholder ifit thinks it's possible that some code should go there, but it'snot sure at the time it generates the code.
The jmp instruction transfers execution to another place inthe program. Notice that you can jump to the address in aregister or a memory location; or you can jump to a label withinthe program. In the case of a label, the label is assembled intoan offset.
This program is an infinite loop with no real purpose. Butthe point is that the jmp instruction gets assembled asjmp -1, signifying that the CPU should subtract 1from the program counter (eip). This means the CPU backs up one byte tothe inc eax instruction to execute it again.The next 10 instructions give ways of doing jumpsconditionally. The usual assumption is that you've justcompleted a cmp instruction (or some other instructionthat sets the flags), and now you want to jump based onthe values in the flags.The jz instruction jumps if the zero flag is set. Inparticular, if the last instruction setting the flags was acmp instruction, the zero flag would be set if the twoarguments were equal.Similarly, jnz jumps if the zero flag is not set --- whichin the same sense corresponds to unequal arguments in apreceding cmp instruction.
The first instruction places the value 1 in register eax.(We haven't seen the mov instruction yet --- it's coming.)The next two instructions skip everything elseif cl is zero (and so we complete with eax holding20=1).The add instruction doubles the value ineax, and the dec instruction subtracts 1 from cl.Finally, the jnz instruction jumps back to again ifthe zero flag is not set --- that is, if the result of thedecrement is not zero. If the zeroflag is zero, then control continues to the done label.The ja instruction stands for ``jump if above'.It jumps to the specified location if both the zero bit and the carrybit are zero. (Consider a cmp instruction where we understand boththe arguments to be unsigned numbers. Then it sets the zero bit if thevalues are equal and the carry bit if the first number is below thesecond; if neither of these are one, then the first number is above thesecond. So this is when the ja instruction will jump.) The nextthree instructions --- jae (above or equal), jb(below), and jbe (below or equal) --- work analogously.
The last 4 conditional jumps are for signed computation,using the overflow flag, the sign flag, and the zero flag to determine whether tojump: jg (greater), jge(greater or equal), jl (less), and jle (less or equal).The intention of them is identical to the previous 4, exceptthat the previous four were for unsigned arithmetic.
The loop instruction is for running through a loop for a fixednumber of times, as in our earlier program to compute2n,where we knew we wanted to iterate exactly n times.The loop instruction subtracts 1 from ecx and jumps to thelabel if ecx is not zero.Notice that loopalways uses ecx for thispurpose. The ecx register has this special meaning to the loopinstruction. (The x86 designers thought of the c as standing forcount.)
Notice that we had to switch to using ecx in place of cl here,since loop works with ecx.Finally, the call and ret instructions provide supportfor subroutines (frequently called functions or procedures inprogramming languages). A call instruction has two effects:First, eip is pushed onto the stack. (This is itself atwo-step process: esp is decreased by 4, and thenthe value of eip is stored in the memory where espnow points.) Then control goes to the location specified in theinstruction. The ret instruction is for returning from thesubroutine, and here eip is popped from the stack. (That is,the value stored at the location to which esp points iscopied into eip, and then esp is increased by 4). Ineffect, this restores the previous value of eip, so that thenext instruction executed is the instruction following thecall that put us there.That's all a little complicated, but it's worth some time figuring itout. Luckily, in practice it's relatively simple.Here's a very simple subroutine for doubling the number ineax.
Notice that we use ret to return from the subroutine. Nowwe can use this as we like.In this case, the subroutine is contrived --- it's too simple tobe useful ---, butit illustrates how you can write a subroutine in x86 assemblylanguage.Data movement instructions
movr/m, r/imm | movr, m |
movr/seg, r/seg | movzxr, r/m |
lear, m | xchgr, r/m |
pushr/m/seg/imm | popr/m/seg |
pushad | popad |
pushf | popf |
outr/imm | inr/imm |
Table 3. Data movement instructions
Table 3 lists the x86 assembly instructions useful for moving data between different areas of the computer.The most fundamental of these is the mov instruction, whichmoves a value into another location. (The word move hereis slightly misleading: It actually copies, leaving theold value in its location.) Note that mov does not alter anyflags.
A mov instruction can move a number, a register value, or avalue in memory into either a register or a memory location,except that a single mov cannot copy directly from one memorylocation to another. Additionally, a mov can copy betweena segment registers and a general register --- this givesyou a way of accessing these segment registers when needed.(When we talk more about memory, we'll talk about why thesesegment registers are important. For now, you should still beignoring them.)
The movzx instruction is for moving a shorter value into alonger destination. For example, if you want to copy blinto eax, you can use the instruction movzx eax, bl.This copies bl into al, extending it with zeroes into thehigher bits of eax.
Actually, the lea (load effectiveaddress) instruction doesn't access memory at all.It stores the address of the given memory location intothe given register. This is intended for when you wantto compute an address that is more complex than the built-inx86 addressingcan handle. (For example, you may want lea to accessan element of an array stored within a structure located within anarray of structures.) For example, the instruction lea ecx,[1 + ecx] increments ecx --- it's functionally equivalentto inc ecx, except that no flags are altered.
The xchg instruction exchanges the data in two locations,of which one must be a register.
The push and pop instructions add and remove from thestack. If the computer encountered a push ax instruction, itwould decrease esp by 2 (choosing 2 because ax istwo bytes long) and then store ax at the memory pointed toby esp. The instruction pop cl would load one byte from thememory pointed to by esp into cl and then increaseesp by 1.
The pushad and popad instructions are for saving several thegeneral registers at once: pushad pushes eax, ecx, edx,ebx, esp, ebp, esi (in that order), and edi, and popad popsthem in reverse order. Similarly, the pushf and popfinstructions are for saving and restoring the value of the flagsregister.
The in and out instructions are a way ofcommunicating with many I/O devices.The idea is that an I/O device can be referenced by a numberedport, specified in the in or out instruction(either directly with a constant value or indirectly bya register value). An in instruction reads the value at thatport and copies it into the ax register (al in thecase of inb). An out instruction takes the value inax (al in the case of outb) and copies it out to theport given in the instruction.
Logical instructions
andr/m, r/imm | orr/m, r/imm |
testr/m, r/imm | xorr/m, r/imm |
shlr/m, r/imm | shrr/mr/imm |
rolr/m, r/imm | rorr/m, r/imm |
Table 4. Logical instructions
The and and or instructions perform the bitwiseAND or OR, placing the result in the destination register andsetting the flags. For example, say al held the value11110010; then the instruction and al, 0xA3 would take theAND of each corresponding pair of bits and put the result(10100010) back into al. It would also set the flags: inthis case, the top bit is 1, so the sign flag would be setto 1; the result isn't zero, so the result flag would be resetto 0; and the carry and overflow flags would be reset to 0, sincethere is no carry and no overflow (not even the possibility).The or instruction is identical.
The test instruction performs a bitwise AND and throwsthe result away. It's relationship to and is the same as therelationship of cmp to sub. It's useful in twosituations. First, it's useful for testing whether a particularbit is set in a register (test al, 0x80 followed by a jnzwill jump if the top bit of al is set). It's also usefulwhen you just want to see whether a register is zero: testeax, eax is more efficient (a shorter instruction) thancmp eax, 0.
The xor instruction works like and and or. Butit's rare you want to do the exclusive OR of two things. Yet itoccurs quite often in assembly code for quite another reason: Ifyou take the XOR of something with itself, you get 0. So theinstruction xor eax, eax actually puts 0 into eax.This instruction is shorter and hence more efficient thanmov eax, 0, so x86 programmers usethe xor idiom when they want to put 0 into a location.
The last four instructions are instructions for shiftingvalues. The first two, shl and shr, are the mostelementary: They shift the value in the destination location left(or right). Zeroes are shifted into the empty places, and thelast bit shifted out goes into the carry flag.
The next two, rol and ror, rotate the bits. Forexample, rol al, 1 will shift every bit in al left onespot, with the highest-order bit being rotated around into the lowestbit of the register. (Again, the carry flag also comes to holdthis last bit rotated off the end of the register.) Note thatrol al, 8 effectively does nothing to al, as all thebits are rotated back into their original places. Of course, thecarry flag changes to the last bit shifted off, in this case theuppermost bit. (But using the sign flag after test al, alis a more efficient way to accomplish the same effect.)
Interrupts
intimm | iret |
cli | sti |
Table 5. Interrupt instructions
At any rate, the int instruction initiates aninterrupt. The argument to the instruction names which of theinterrupts to use. The iret instruction is used withinthe interrupt handler to return from the interrupt. Returningfrom the interrupt pops the ip and cs values fromthe stack, effectively transferring control back to what wasexecuting before the interrupt occurred.
The cli and sti instructions affect theinterrupt flag in the flags register. When this flag is 0,no interrupts are accepted --- they are buffered (and ofcourse the chip's buffer is limited). When it is 1, interruptsare enabled, meaning that the hardware can interrupt the CPU atany time. The cli instruction clears the interrupt flag,cutting off the interrupts. The sti instruction sets theinterrupt flag, enabling future interrupts.
Segment registers
Sequence instructions
movs | stos |
outs | ins |
cld | std |
rep |
Table 6. Sequence instructions
The most useful among these is the movs instruction,for copying one memory fragment into another. The movsinstruction copies four bytes of memory, from[esi] to [edi]. (The esi and edi registersare designed for this purpose: The names stand for sourceindex and destination index, respectively.)The movs instruction also increments the esi and ediregisters by four, so that a subsequent movs will copy the next fourbytes of memory.
This gets really interesting when you use the repinstruction in conjunction with it. The rep instruction ispeculiar in that it is an optional prefix to several instructions.For example, if rep occurs before movs, then it willrepeatedly execute movs and decrement ecx,until ecx reacheszero. For example, we might write this.
Or we might write this.The first is much more efficient, and it's easier to type.Of course it isn't necessarily what we want, so some variation on thesecond is often more appropriate.Naturally, the movsb instruction works the same, except at thebyte level. With movsb, esi and edi areincremented by just 1 to do a proper string copy.The stos instruction is for saving several copies of the samebits into an array. It stores the value in eax into thememory pointed to by edi and then increments edi by 4.The rep prefix can be prepended to this to store many copies of thesame data very quickly.
The outs and ins instructions send and receive data froma port specified in the edx register. The outs instruction takesthe 4 bytes pointed to by esi, sends it to the port mentioned inedx, and increments edx by 4. The ins instruction reads 4bytes from the port mentioned in edx, stores it in the 4 bytespointed to by edi, and increments edx by 4.The rep prefix can be prepended to either of these instructions.
The final two sequence-manipulation instructions we'll look at arecld and std. These are for clearing and setting thedirection flag in the eflags register. In all of the aboveinstructions, when the direction flag is one, the values in esi andedi are decremented instead of incremented.This facilitates the case when an array is stored in reverse order.
Advanced features
lgdt | lldt |
lidt | ltr |
sgdt |
Table 7. Advanced feature instructions
The final set of instructions (Table 7) allow you toaccess these features.The lgdt, lidt, lldt, and ltrinstructions changethe global descriptor table,the local descriptor table,the interrupt descriptor table,and the task register, respectively.The sgdt gives a way of readingthe current global descriptor table.
Extended example
Figure 2. An x86 subroutine to perform selection sort.
Directives
global | section |
db | dw |
dd | equ |
times |
Table 8. Assembler directives
For example, the global directive tells the assembler about a labelthat should be made available outside the program. If youhad a large assembly program split across several files, you would useglobal to list the labels of thesubroutines that are accessed from other files. We'll useglobal to make the special label _start publicly available, as_start is the label that the operating system will call to start aprogram.
The section directive tells the assembler that we're starting anew section of the program. Normally, the program should be splitbetween the .data section (where data is stored) and the.text section (where instructions are stored).
The db, dw, and dd directives tell the assemblerto allocate some data into the output. (Respectively, they allocatebytes, words, and double words.) This typically occurs in the.data section, and there's usually a label before it.
In this example, we've told the assembler to place the three 16-bitvalues 23, 12, and 45 into the generated object file.The array label gives us a way of specifying the addressof the first of these words. Consider now the following instructionto access the third element of array.This instruction loads the value 45 into ax.(Or, if the memory has been changed, whatever has been written over the45.)We add 4 to array because we want to skip over the first twoelements of array, and each array element is two bytes long.The equ directive defines a constant. There must be a label forthis directive. The assembler will replace each occurrence of the labelwith the value defined for the label.
Finally, the times directive tells the assembler to do somethingseveral times.
This will accomplish the db 0 directive 1024 times,reserving 1024 bytes of memory initialized to 0. |
Figure 3. A complete program to sort.
Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign upHave a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
commented Feb 20, 2015
Hi, There is a problem with PUSHFW and POPFW instructions. For example PUSHFW is declared as: And the dictionnary which contains all instructions contains PUSHFW only here. It is the same Patch is provided (tested/working) I also fixed a string formatting problem during a log warning, which cause a crash instead of just the warning message. |
added some commits Feb 20, 2015
commented Feb 20, 2015
It seems you didn't fix the regression test for arch x86: Can you fix it in the same PR? and for PUSHF: |
commented Feb 20, 2015
Indeed :) done. |
commented Feb 20, 2015
Thank you! |