Skip to content

Instantly share code, notes, and snippets.

@thaolt
Created June 10, 2025 00:38
Show Gist options
  • Save thaolt/a71cf5e133bb55c22f6f5a83abcfca97 to your computer and use it in GitHub Desktop.
Save thaolt/a71cf5e133bb55c22f6f5a83abcfca97 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
// VM Configuration
#define STACK_SIZE 1024
#define MAX_STRING_LENGTH 256
#define ENCRYPTION_KEY 0xAB
// Opcodes for the VM
typedef enum {
OP_NOP = 0x00, // No operation
OP_PUSH = 0x01, // Push value onto stack
OP_POP = 0x02, // Pop value from stack
OP_ADD = 0x03, // Add two values
OP_SUB = 0x04, // Subtract two values
OP_MUL = 0x05, // Multiply two values
OP_DIV = 0x06, // Divide two values
OP_MOD = 0x07, // Modulo operation
OP_PRINT_INT = 0x08, // Print integer from stack
OP_PRINT_STR = 0x09, // Print decrypted string
OP_HALT = 0xFF // Halt execution
} Opcode;
// VM State Structure
typedef struct {
int32_t stack[STACK_SIZE];
int32_t sp; // Stack pointer
uint8_t *bytecode; // Bytecode instructions
size_t pc; // Program counter
size_t bytecode_size; // Size of bytecode
bool running; // VM running state
} VM;
// Encrypted string structure
typedef struct {
uint8_t *data;
size_t length;
} EncryptedString;
// Function prototypes
VM* vm_create(void);
void vm_destroy(VM *vm);
bool vm_load_bytecode(VM *vm, uint8_t *bytecode, size_t size);
void vm_execute(VM *vm);
bool vm_push(VM *vm, int32_t value);
bool vm_pop(VM *vm, int32_t *value);
void vm_print_stack(VM *vm);
// String encryption/decryption functions
EncryptedString* encrypt_string(const char *plaintext);
char* decrypt_string(EncryptedString *encrypted);
void free_encrypted_string(EncryptedString *encrypted);
// Utility functions
uint32_t read_uint32(uint8_t *data, size_t offset);
void vm_error(const char *message);
// VM Creation and Destruction
VM* vm_create(void) {
VM *vm = malloc(sizeof(VM));
if (!vm) {
vm_error("Failed to allocate memory for VM");
return NULL;
}
memset(vm->stack, 0, sizeof(vm->stack));
vm->sp = -1;
vm->bytecode = NULL;
vm->pc = 0;
vm->bytecode_size = 0;
vm->running = false;
printf("VM created successfully\n");
return vm;
}
void vm_destroy(VM *vm) {
if (vm) {
if (vm->bytecode) {
free(vm->bytecode);
}
free(vm);
printf("VM destroyed\n");
}
}
// Bytecode loading
bool vm_load_bytecode(VM *vm, uint8_t *bytecode, size_t size) {
if (!vm || !bytecode || size == 0) {
return false;
}
vm->bytecode = malloc(size);
if (!vm->bytecode) {
vm_error("Failed to allocate memory for bytecode");
return false;
}
memcpy(vm->bytecode, bytecode, size);
vm->bytecode_size = size;
vm->pc = 0;
vm->running = false;
printf("Bytecode loaded: %zu bytes\n", size);
return true;
}
// Stack operations
bool vm_push(VM *vm, int32_t value) {
if (vm->sp >= STACK_SIZE - 1) {
vm_error("Stack overflow");
return false;
}
vm->stack[++vm->sp] = value;
return true;
}
bool vm_pop(VM *vm, int32_t *value) {
if (vm->sp < 0) {
vm_error("Stack underflow");
return false;
}
*value = vm->stack[vm->sp--];
return true;
}
// Main execution engine
void vm_execute(VM *vm) {
if (!vm || !vm->bytecode) {
vm_error("Invalid VM state for execution");
return;
}
vm->running = true;
vm->pc = 0;
printf("Starting VM execution...\n");
while (vm->running && vm->pc < vm->bytecode_size) {
uint8_t opcode = vm->bytecode[vm->pc++];
switch (opcode) {
case OP_NOP:
// No operation
break;
case OP_PUSH: {
if (vm->pc + 4 > vm->bytecode_size) {
vm_error("Incomplete PUSH instruction");
vm->running = false;
break;
}
uint32_t value = read_uint32(vm->bytecode, vm->pc);
vm->pc += 4;
if (!vm_push(vm, (int32_t)value)) {
vm->running = false;
}
break;
}
case OP_POP: {
int32_t value;
if (!vm_pop(vm, &value)) {
vm->running = false;
}
break;
}
case OP_ADD: {
int32_t b, a;
if (!vm_pop(vm, &b) || !vm_pop(vm, &a)) {
vm->running = false;
break;
}
if (!vm_push(vm, a + b)) {
vm->running = false;
}
break;
}
case OP_SUB: {
int32_t b, a;
if (!vm_pop(vm, &b) || !vm_pop(vm, &a)) {
vm->running = false;
break;
}
if (!vm_push(vm, a - b)) {
vm->running = false;
}
break;
}
case OP_MUL: {
int32_t b, a;
if (!vm_pop(vm, &b) || !vm_pop(vm, &a)) {
vm->running = false;
break;
}
if (!vm_push(vm, a * b)) {
vm->running = false;
}
break;
}
case OP_DIV: {
int32_t b, a;
if (!vm_pop(vm, &b) || !vm_pop(vm, &a)) {
vm->running = false;
break;
}
if (b == 0) {
vm_error("Division by zero");
vm->running = false;
break;
}
if (!vm_push(vm, a / b)) {
vm->running = false;
}
break;
}
case OP_MOD: {
int32_t b, a;
if (!vm_pop(vm, &b) || !vm_pop(vm, &a)) {
vm->running = false;
break;
}
if (b == 0) {
vm_error("Modulo by zero");
vm->running = false;
break;
}
if (!vm_push(vm, a % b)) {
vm->running = false;
}
break;
}
case OP_PRINT_INT: {
int32_t value;
if (!vm_pop(vm, &value)) {
vm->running = false;
break;
}
printf("Output: %d\n", value);
break;
}
case OP_PRINT_STR: {
// Read string length
if (vm->pc + 4 > vm->bytecode_size) {
vm_error("Incomplete PRINT_STR instruction");
vm->running = false;
break;
}
uint32_t str_length = read_uint32(vm->bytecode, vm->pc);
vm->pc += 4;
if (vm->pc + str_length > vm->bytecode_size) {
vm_error("String data exceeds bytecode bounds");
vm->running = false;
break;
}
// Create encrypted string structure
EncryptedString encrypted;
encrypted.data = &vm->bytecode[vm->pc];
encrypted.length = str_length;
// Decrypt and print
char *decrypted = decrypt_string(&encrypted);
if (decrypted) {
printf("Output: %s\n", decrypted);
free(decrypted);
}
vm->pc += str_length;
break;
}
case OP_HALT:
printf("VM halted normally\n");
vm->running = false;
break;
default:
printf("Unknown opcode: 0x%02X at PC: %zu\n", opcode, vm->pc - 1);
vm->running = false;
break;
}
}
if (vm->running) {
printf("VM reached end of bytecode\n");
}
}
// String encryption/decryption
EncryptedString* encrypt_string(const char *plaintext) {
if (!plaintext) return NULL;
size_t len = strlen(plaintext);
EncryptedString *encrypted = malloc(sizeof(EncryptedString));
if (!encrypted) return NULL;
encrypted->data = malloc(len);
if (!encrypted->data) {
free(encrypted);
return NULL;
}
encrypted->length = len;
// Simple XOR encryption
for (size_t i = 0; i < len; i++) {
encrypted->data[i] = plaintext[i] ^ ENCRYPTION_KEY;
}
return encrypted;
}
char* decrypt_string(EncryptedString *encrypted) {
if (!encrypted || !encrypted->data) return NULL;
char *plaintext = malloc(encrypted->length + 1);
if (!plaintext) return NULL;
// Simple XOR decryption
for (size_t i = 0; i < encrypted->length; i++) {
plaintext[i] = encrypted->data[i] ^ ENCRYPTION_KEY;
}
plaintext[encrypted->length] = '\0';
return plaintext;
}
void free_encrypted_string(EncryptedString *encrypted) {
if (encrypted) {
if (encrypted->data) {
free(encrypted->data);
}
free(encrypted);
}
}
// Utility functions
uint32_t read_uint32(uint8_t *data, size_t offset) {
return (uint32_t)data[offset] |
((uint32_t)data[offset + 1] << 8) |
((uint32_t)data[offset + 2] << 16) |
((uint32_t)data[offset + 3] << 24);
}
void vm_error(const char *message) {
fprintf(stderr, "VM Error: %s\n", message);
}
void vm_print_stack(VM *vm) {
printf("Stack state (SP=%d):\n", vm->sp);
for (int i = vm->sp; i >= 0; i--) {
printf(" [%d]: %d\n", i, vm->stack[i]);
}
}
// Helper function to create bytecode with embedded encrypted strings
size_t create_sample_bytecode(uint8_t **bytecode_out) {
// Sample program: Calculate (10 + 5) * 3 and print result, then print string
// Encrypt sample string
EncryptedString *encrypted = encrypt_string("Hello, VM World!");
if (!encrypted) {
*bytecode_out = NULL;
return 0;
}
// Calculate bytecode size
size_t base_size = 1 + 5 + 1 + 5 + 1 + 1 + 5 + 1 + 1 + 1; // Basic arithmetic operations
size_t string_size = 1 + 4 + encrypted->length + 1; // PRINT_STR + length + data + HALT
size_t total_size = base_size + string_size;
uint8_t *bytecode = malloc(total_size);
if (!bytecode) {
free_encrypted_string(encrypted);
*bytecode_out = NULL;
return 0;
}
size_t offset = 0;
// PUSH 10
bytecode[offset++] = OP_PUSH;
bytecode[offset++] = 10;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
// PUSH 5
bytecode[offset++] = OP_PUSH;
bytecode[offset++] = 5;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
// ADD
bytecode[offset++] = OP_ADD;
// PUSH 3
bytecode[offset++] = OP_PUSH;
bytecode[offset++] = 3;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
bytecode[offset++] = 0;
// MUL
bytecode[offset++] = OP_MUL;
// PRINT_INT
bytecode[offset++] = OP_PRINT_INT;
// PRINT_STR with encrypted string
bytecode[offset++] = OP_PRINT_STR;
// String length (little endian)
uint32_t str_len = encrypted->length;
bytecode[offset++] = str_len & 0xFF;
bytecode[offset++] = (str_len >> 8) & 0xFF;
bytecode[offset++] = (str_len >> 16) & 0xFF;
bytecode[offset++] = (str_len >> 24) & 0xFF;
// Encrypted string data
memcpy(&bytecode[offset], encrypted->data, encrypted->length);
offset += encrypted->length;
// HALT
bytecode[offset++] = OP_HALT;
free_encrypted_string(encrypted);
*bytecode_out = bytecode;
return offset;
}
// Main function with example usage
int main(void) {
printf("Stack-Based Bytecode VM Engine\n");
printf("==============================\n\n");
// Create VM instance
VM *vm = vm_create();
if (!vm) {
return 1;
}
// Create sample bytecode
uint8_t *bytecode;
size_t bytecode_size = create_sample_bytecode(&bytecode);
if (!bytecode) {
printf("Failed to create sample bytecode\n");
vm_destroy(vm);
return 1;
}
// Load and execute bytecode
if (vm_load_bytecode(vm, bytecode, bytecode_size)) {
printf("\nExecuting sample program...\n");
printf("Program: (10 + 5) * 3, then print string\n\n");
vm_execute(vm);
printf("\nFinal stack state:\n");
vm_print_stack(vm);
}
// Cleanup
free(bytecode);
vm_destroy(vm);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment