Wednesday, May 23, 2012

Passing Events to a Virtual Machine

The source code for this article may be found here.

Virtual machines and Software Frameworks are an initial part of our digital life. There are complex VM and simple Software Frameworks. These two articles (Simple Virtual Machine and Simple Runtime Framework by Example) show how easy it may be to implement one yourself. I did my best to describe the way VM code may interact with native code and the Operating System, however, the backwards interaction is still left unexplained. This article is going to fix this omission.

As usual - note for nerds:
The source code given in this article is for example purposes only. I know that this framework is far from being perfect, therefore, this article is not a howto or tutorial - just an explanation of principle. Error checks are omitted on purpose. You want to implement a real framework - do it yourself, including error checks.
By saying VM's code I do not refer to the implementation of the virtual machine, but to the pseudo code that runs inside it.

Architecture Overview
Needless to mention, that the ability to pass events/signals to a code executed by the virtual machine implies a more complex VM architecture. While all previous examples were based on a single function responsible for the execution, adding events means not only adding another function, but we will have to introduce threads to our implementation.

At least two threads are needed:
VM Architecture with Event Listener

  1. Actual VM - this thread is responsible for the execution of the VM's executable code and events queue dispatch (processor);
  2. Event Listener - this thread is responsible for collection of relevant events from the Operating Systems and adding them to the VM's event queue (listener).
You may see that the Core() function, in the attached source code, creates additional thread.

Event ListenerThis thread collects events from the Operating System (mouse move, key up/down, etc) and adds a new entry to the list of EVENT structures.

typedef struct _EVENT
   struct _EVENT* next_event; // Pointer to the next event in the queue
   int            code;       // Code of the event
   unsigned int   data;       // Either unsigned int data or the address of the buffer
                              // containing information to be passed to the handler

The code for the listener is quite simple:

while(WAIT_TIMEOUT == WaitForSingleObject(processor_thread, 1))
   // Check for events from the OS
      event = (EVENT*)malloc(sizeof(EVENT));
      event->code = whatever_code_is_needed;
      event->data = whatever_data_is_relevant;
      add_event(event_list, event);
      event->next_event = NULL;

The code is self explanatory enough.  First of all it checks for available events (this part is omitted and replaced by a comment). If there is a new event to pass to the VM, it adds it to the queue. While in this example, event collection is implemented as a loop, in real life, you may do it in a form of callbacks and use the loop above just to wait for the processor thread to exit.


Obviously, the "processor" thread is going to be a bit more complicated, then in the previous article (
Simple Runtime Framework by Example), as in addition to running the run_opcode(CPU**) function, it has to check for pending events and pass the control flow to the associated handler in the VM code.

typedef struct _EVENT_HANDLER
   struct _EVENT_HANDLER* next_handler; // Pointer to the next handler
   int                    event_code;   // Code of the event
   unsigned int           handler_base; // Address of the handler in the VM's code

DWORD WINAPI RunningThread(void* param)
   CPU*            cpu = (CPU*)param;
   EVENT*          event;
   EVENT_HANDLER*  handler;

      if(NULL != events)
         event = events;
         events = events->next_event;

         // Save current context by pushing VM registers to VM's stack
         cpu->regs[REG_A] = (unsigned int)event->code;
         cpu->regs[REG_B] = event->data;

         handler = handlers;
         while(NULL != handler && event->code != handler->event_code)
               handler = handler->next_handler;
         cpu->regs[REG_IP] = handler->handler_base;


   }while(0 != run_opcode(&cpu));
   return cpu->regs[REG_A];

We are almost done. Our framework already knows how to pass events to a correct handler in the VM's code. Two more things are yet uncovered - registering a handler and returning from a handler.

Returning from Handler

Due to the fact that Event Handler is not a regular routine, we cannot return from it using the regular
RET instruction, instead, let's introduce another instruction - IRET. As event actually interrupts the execution flow of the program, IRET - interrupt return is exactly what we need. The source code that handles this instruction is so simple, that there is no need to give it here in the text of the article. All it does is simply restoring the context of the VM's code by popping the registers previously pushed on stack.

Registering an Event Handler

The last thing left is to "teach" the program written in pseudo assembly to register a handler for a given event type. In order to do this, we need to add one simple system call -
SYS_ADD_LISTENER.  This system call accepts two parameters:
  1. Code of the event to handle;
  2. Address of the handler function.
loadi  A, 0             ;Code of the event
loadi  B, handler       ;Address of the handler subroutine
_int   sys_add_listener ;Register the handler

Example Code

The example code attached to this article is the implementation of all of the above. It does the following:
  1. Registers event handler;
  2. Enters an infinite loop printing out '.' every several milliseconds;
  3. The first thread waits a bit and generates an event;
  4. Event handler terminates the infinite loop and returns;
  5. The program prints out a message and exits.

I hope this post was helpful or, at least, interesting.

See you at the next.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.