Generating code for the 8086 CPU
Summary
A simple program
The x86_8086 package is based on the arch package. Every registers and more than 81 instructions are supported.
Instead of generating raw Assembly code, we can use the abstractions provided by this package. Here is an example:
program.arch.instructions.mov(program.arch.registers.AX, 0x28)
This is like
program.generate_line("mov ax, 0x28")
But you could find that the line above is too verbose. Fortunately, the Program class provides a shortcut named asm for arch.instructions and regs for arch.registers.
program.asm.mov(program.regs.AX, 0x28)
This line is smaller and more readable.
Let’s add a few instructions:
program.asm.mov(program.regs.AX, 0x28)
program.asm.mov(program.regs.BX, program.regs.AX)
program.asm.add(program.regs.AX, program.regs.BX)
If you generate the code, you will get the following result:
mov ax, 40
mov bx, ax
add ax, bx
Interaction with labels
The 8086 instruction wrappers work well with labels. For example, let’s define a label and jump to it.
@label()
def example():
pass
prog.asm.jmp("example")
Conditionnal jumps and comparisons are supported too. You can run this example and read the generated code:
from libpyosdev.program import Program
from libpyosdev.label import label
from libpyosdev.x86_8086.infos import info8086
class Labels8086(Program):
def __init__(self):
super().__init__("8086_labels", info8086)
@label()
def start(self):
self.asm.mov(self.regs.AX, 2)
self.asm.add(self.regs.AX, 4)
self.asm.jmp("loop")
@label()
def loop(self):
self.asm.inc(self.regs.AX)
self.asm.cmp(self.regs.AX, 10)
self.asm.jne("loop")
def _1_body(self):
self.start()
self.loop()
def run(self):
self.write()
prog = Labels8086()
prog.run()
You will get the following output:
start:
mov ax, 2
add ax, 4
jmp loop
loop:
inc ax
cmp ax, 10
jne loop
Calling the BIOS
The Intel x86 8086 is an architecture that supports the BIOS, so you can call its wrappers thanks to LibPyOSDev. In the example below, we call an interrupt to write a character to the screen and move the cursor. Without the BIOS class, you would write this:
prog.asm.mov(prog.regs.AH, 0x0E)
prog.asm.mov(prog.regs.AL, ord('A'))
prog.asm.mov(prog.regs.BH, 0) # page number, optionnal
prog.asm.mov(progs.regs.BL, 0x0F)
prog.asm.int_(0x10)
But with the BIOS class, you can do this in one line:
prog.arch.bios.interrupt(prog.arch.bios.TELETYPE_PUTCHAR, ord('A'), 0, 0x0F) # page number is required here
And you can make it even shorter:
prog.bios.interrupt(prog.bios.TELETYPE_PUTCHAR, ord('A'), 0, 0x0F)
The only thing you need to be careful about is passing values to registers. For TELETYPE_PUTCHAR, the right order is AL, BH, BL.