Getting Out of the VM
Sometimes virtual code needs to call functions that exist outside the VM. These functions are called "trap functions". Best of all, you can add your own! Adding a trap function is a bit of a pain, especially if you're doing it to botlib (if so you'll have to change the function export structure: just grep -i "Stupidly" and you'll find all the changes I made for one of our botlib traps: trap_BotMoveStupidlyInDirection). Adding traps like we did for reading/writing sockets is much easier, though: just follow the step-by-step instructions below and copy the style of the existing entries there. Typically our edits are at the bottom of the files, but if you search "quagents" (case-insensitive) you'll turn them up quickly.
Edit these files to insert a trap function accessible to the game module (where most of our modifications take place):
- game/g_public.h: add an element to the gameImport_t enum (order matters - try to maintain the values of all the other entries (just add at the bottom)).
- game/g_local.h: add trap function declarations (see bottom of file). These will be the function names inside the VM.
- game/g_syscalls.asm: add equ statements to the file reflecting the (negative) value of the enum entry you added. It should be off by one like all the others are.
- game/g_syscalls.c: add a patch function that calls syscall with the appropriate enum value and parameters, and returns what it returns unless the return is void.
- qcommon/q_shared.h: add trap function declarations (search "Quagents" and keep them together). These will be the function names outside the VM.
- qcommon/common.c: we've defined all our trap functions here, but you probably don't have to. Still, it's a convenient place to do it, so why don't we keep them together and define the function here.
- server/sv_game.c: edit SV_GameSystemCalls and add a case for your enum value to call your function with. If you're passing memory addresses out of the virtual machine, use VMA(x) to remap address in args[x] from VM-memory to Quake-memory. Keep in mind that there's no way to go the other way - you won't be able to see memory outside the VM from within it.
To insert trap functions into cgame, replace game/g_* with cgame/cg_*, and server/sv_game.c with client/cl_cgame. To insert trap functions into ui, ...? Something similar.
Getting Into the VM
Getting into the VM is very easy. Each module of VM code has a *_public.h file hosting a large *Export enum of function names. Add the function you want to be calling from outside the VM to that enum, go to *_main.c and add a case for that value and function into the switch in the vmMain(...) method. You can then call your function from outside the vm by using VM_Call(...). You should give VM_Call a legal vm (which can sometimes be a pain to get a hold of!) and the enum value you defined.
Debugging in the VM
Using gdb inside the VM is horrible because every so often you run into a bunch of inline assembly that gdb has trouble coping with. Try running the executable with "+set sv_pure 0 +set vm_game 0 +set vm_cgame 0 +set vm_ui 0" to force it to circumvent the VMs entirely and use dynamic library code. If you compile with debug flags ("make debug" will do that for you), it should be much easier to track your program. Additionally, you get nice familiar segfaults instead of strange VM_Free() errors.