Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

How to create your own virtual machine -- Part 2

4.68/5 (17 votes)
27 Feb 2010CPOL6 min read 1   2.2K  
This article takes you through a step-by-step process of creating your own virtual machine.

Introduction

A while back, I submitted Part 1 of a two part tutorial on how to create your own virtual machine. In that first part, we developed a functional assembler for our fictional B32 machine. This assembler would take "B32 Assembly Language" files and convert them to B32 byte code that would run on our B32 virtual machine. In this second part of the tutorial, I will show you how to create the actual virtual machine that will run B32 byte code programs produced with our assembler.

Before reading this section, make sure you read part 1 of my tutorial, which can be found here. Also, I have a PDF of this tutorial available for download which goes into much more details. Feel free to download that.

The B32 Virtual Machine

The first thing we need to create is a "virtual screen". Almost all machines running off any microprocessor or CPU has some kind of output. In order to keep with the KISS (Keep It Simple Stupid) principle, the virtual display we will create for our B32 machine will be a standard text color display capable of displaying 80 characters wide by 25 lines long. Although it could be easily expanded to do simple graphics, such as lines and circles, I decided to forgo graphics in this tutorial in order to keep it simple.

Recall from Part 1 that our virtual machine will have 64K of memory access, and it will be to use all 64K with the exception of the block of memory between $A000 and $AFA0. This 4K region of memory will be our video memory. For those of you who have programmed in the old DOS days, you will have no trouble understanding how our display works. Whenever a byte is stored in an even portion of that memory block, that will represent the character to be displayed. For example, if I store the value 65 in $A000, then it will display the letter 'A' in the upper left corner of the screen. The odd portions of that memory block will determine the foreground and background color. For example, if I store the value 7 in $A001, then that 'A' in the upper left corner of the screen will show up as grey text on a black background (just like a command window looks like). For more information on how the foreground and background colors work, either refer to my PDF or to this site.

Our B32 screen will be a Windows Forms User Control. The screen itself is stored in a variable called m_ScreenMemory, and it is a 4000 byte array. It has one property for getting or setting the starting address of our virtual screen (defaults to 0xA000). This property sets a variable called m_ScreenMemoryLocation. There are three public methods, called Poke, Peek, and Reset. Poke() will store a value in the virtual screen array. Peek() will retrieve a value stored at the given address. Finally, Reset() will reset the screen, basically clearing it. The code for these three methods are as follows:

C#
public void Reset()
{
    for (int i = 0; i < 4000; i += 2)
    {
        m_ScreenMemory[i] = 32;
        m_ScreenMemory[i + 1] = 7;
    }
    Refresh();
}

public void Poke(ushort Address, byte Value)
{
    ushort MemLoc;

    try
    {
        MemLoc = (ushort)(Address - m_ScreenMemoryLocation);
    }
    catch (Exception)
    {
        return;
    }

    if (MemLoc < 0 || MemLoc > 3999)
        return;

    m_ScreenMemory[MemLoc] = Value;
    Refresh();
}

public byte Peek(ushort Address)
{
    ushort MemLoc;

    try
    {
        MemLoc = (ushort)(Address - m_ScreenMemoryLocation);
    }
    catch (Exception)
    {
        return (byte)0;
    }

    if (MemLoc < 0 || MemLoc > 3999)
        return (byte)0;

    return m_ScreenMemory[MemLoc];
}

This code is pretty straightforward. In addition to these methods, I override the Paint method. The Paint method is what does all the work. Basically, it determines a foreground and background brush color to use based on my attribute byte, then it uses DrawString() to output one character at a time to an internal bitmap. Finally, this bitmap is instantly transferred to the screen, in order to avoid flicker.

Our main program consists of three sections. The top section is the menu bar, for loading B32 programs, pausing and restarting the programs, and exiting. The bottom section will serve as a status bar. It will show the value of all five registers, the compare flag, and our instruction pointer. Finally, the section in the middle is our B32 virtual screen.

The details of how our virtual machine was put together in detail can be found in the PDF. I will give you a brief summary though of how it works. Basically, a 64K array is created at runtime. This array represents the entire memory of our virtual machine. Whenever a B32 byte code program is opened, it stores this program in that array at the address specified by the origin (see Part 1 of the tutorial). A thread is then created that executes our program at the appropriate execution address.

This thread executes concurrently with the main B32 program. The thread is simply a loop that interprets the byte code and does the appropriate function. Whenever data is stored in our 64K array, it is also passed onto our Poke() function we created for the B32 virtual screen. This is done via a function called ThreadPoke(), which updates our B32 Screen in a thread-safe manner. If the data store falls outside our video memory range, then our B32 virtual screen control basically ignores the poke. This loop continues till the end of our program is reached.

In addition, I created functionality to suspend and resume a thread. This is done by using .NET's ManualResetEvent() which literally suspends the thread. Also, the speed of the execution can be controlled. The default is to run the program in real time with no delay. However, you can slow down the execution. This is done by adding a Thread.Sleep() command in between each opcode execution.

How Does B32 Differ from a REAL Virtual Machine?

There are at least two major differences between the virtual machine presented here and an actual virtual machine. Difference number one: timing is everything. If you are creating a virtual machine designed to execute code based on a real life machine, then timing is a big thing. Every CPU instruction takes a certain amount of time to execute. Usually, it's a very short amount of time; however, it must be measured accurately; otherwise, the programs executing on your virtual machine will run either too fast or too slow. I am using Thread.Sleep() to simulate delay in B32; however, in a real virtual machine, you'll want to use a better, more high resolution timer, such as the one provided with DirectX.

The second difference is, to emulate modern machines, programmers use a method called Dynamic Recompilation. In B32, I used server IF statements to interpret the opcodes in the B32 program. Most virtual machines will not use this method. Instead, they use dynamic recompilation, which takes the virtual byte code and converts it into machine code compatible with the host computer. For example, if B32 used dynamic compilation, then after opening the file, B32 would convert the program to Intel x86 machine code. B32 would then execute the machine code natively. Obviously, this is an advanced subject, well beyond the scope of this tutorial. However, if I ever create a part 3, then I will almost certainly touch on this topic more.

The End

I hope you all enjoyed this tutorial. Feel free to contact me at icemanind@yahoo.com if you need any help or have any feedback!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)