Files
jvm_emulator/exec_code.c
T
2026-03-30 19:26:24 +00:00

662 lines
26 KiB
C

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include "classfile.h"
#include "helpers.h"
#include <stdlib.h>
#include <limits.h>
#ifndef PATH_MAX
#include <linux/limits.h>
#endif
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>
static int ends_with(const char *s, const char *suffix) {
size_t ls = strlen(s), lq = strlen(suffix);
if (lq > ls) return 0;
return strcmp(s + ls - lq, suffix) == 0;
}
static void trim_inplace(char *s) {
char *p = s;
while (*p && isspace((unsigned char)*p)) p++;
if (p != s) memmove(s, p, strlen(p) + 1);
size_t n = strlen(s);
while (n > 0 && isspace((unsigned char)s[n - 1])) s[--n] = '\0';
}
static void class_name_to_path(char *name) {
for (char *p = name; *p; p++) if (*p == '.') *p = '/';
if (!ends_with(name, ".class")) strcat(name, ".class");
}
static int shell_quote(const char *in, char *out, size_t out_sz) {
size_t k = 0;
if (out_sz < 3) return 0;
out[k++] = '\'';
for (const char *p = in; *p; p++) {
if (*p == '\'') {
if (k + 4 >= out_sz) return 0;
out[k++] = '\'';
out[k++] = '\\';
out[k++] = '\'';
out[k++] = '\'';
} else {
if (k + 1 >= out_sz) return 0;
out[k++] = *p;
}
}
if (k + 2 > out_sz) return 0;
out[k++] = '\'';
out[k] = '\0';
return 1;
}
static int find_first_class_file(const char *root, char *out_path, size_t out_sz) {
DIR *dir = opendir(root);
if (!dir) return 0;
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
char full[PATH_MAX];
snprintf(full, sizeof(full), "%s/%s", root, ent->d_name);
struct stat st;
if (stat(full, &st) != 0) continue;
if (S_ISDIR(st.st_mode)) {
if (find_first_class_file(full, out_path, out_sz)) {
closedir(dir);
return 1;
}
} else if (S_ISREG(st.st_mode) && ends_with(ent->d_name, ".class")) {
strncpy(out_path, full, out_sz - 1);
out_path[out_sz - 1] = '\0';
closedir(dir);
return 1;
}
}
closedir(dir);
return 0;
}
static int resolve_jar_entry_class(const char *extract_dir, char *out_class_file, size_t out_sz) {
char manifest[PATH_MAX];
snprintf(manifest, sizeof(manifest), "%s/META-INF/MANIFEST.MF", extract_dir);
FILE *fp = fopen(manifest, "r");
if (!fp) return find_first_class_file(extract_dir, out_class_file, out_sz);
char line[1024];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "MIDlet-1:", 9) == 0) {
char value[512];
strncpy(value, line + 9, sizeof(value) - 1);
value[sizeof(value) - 1] = '\0';
trim_inplace(value);
char *a = strrchr(value, ',');
if (a) {
a++;
trim_inplace(a);
class_name_to_path(a);
snprintf(out_class_file, out_sz, "%s/%s", extract_dir, a);
fclose(fp);
return access(out_class_file, R_OK) == 0;
}
}
if (strncmp(line, "Main-Class:", 11) == 0) {
char value[512];
strncpy(value, line + 11, sizeof(value) - 1);
value[sizeof(value) - 1] = '\0';
trim_inplace(value);
class_name_to_path(value);
snprintf(out_class_file, out_sz, "%s/%s", extract_dir, value);
fclose(fp);
return access(out_class_file, R_OK) == 0;
}
}
fclose(fp);
return find_first_class_file(extract_dir, out_class_file, out_sz);
}
typedef struct {
intptr_t locals[256];
intptr_t stack[256];
int sp;
uint8_t *code;
int pc;
} Frame;
static intptr_t g_static_fields[65536];
static const char *resolve_class_name(ClassFile *cf, CPEntry *methodref) {
uint16_t cls_idx = methodref->ref.class_index;
uint16_t utf_idx = cf->cp[cls_idx].cls_or_str.index;
return cp_utf8(cf, utf_idx);
}
static int resolve_field_info(ClassFile *cf, uint16_t fieldref_idx, const char **class_name, const char **field_name, const char **field_desc) {
if (!cf || fieldref_idx == 0 || fieldref_idx >= cf->cp_count) return 0;
CPEntry *ref = &cf->cp[fieldref_idx];
if (ref->tag != CP_FIELDREF) return 0;
uint16_t cls_idx = ref->ref.class_index;
uint16_t nat_idx = ref->ref.name_and_type_index;
if (cls_idx == 0 || cls_idx >= cf->cp_count) return 0;
if (nat_idx == 0 || nat_idx >= cf->cp_count) return 0;
if (cf->cp[cls_idx].tag != CP_CLASS) return 0;
if (cf->cp[nat_idx].tag != CP_NAME_AND_TYPE) return 0;
CPEntry *nat = &cf->cp[nat_idx];
if (class_name) *class_name = cp_utf8(cf, cf->cp[cls_idx].cls_or_str.index);
if (field_name) *field_name = cp_utf8(cf, nat->nat.name_index);
if (field_desc) *field_desc = cp_utf8(cf, nat->nat.descriptor_index);
return 1;
}
static const char *current_class_name(ClassFile *cf) {
if (!cf || cf->this_class == 0 || cf->this_class >= cf->cp_count) return "";
if (cf->cp[cf->this_class].tag != CP_CLASS) return "";
return cp_utf8(cf, cf->cp[cf->this_class].cls_or_str.index);
}
static int field_slot(ClassFile *cf, uint16_t fieldref_idx) {
CPEntry *ref = &cf->cp[fieldref_idx];
CPEntry *nat = &cf->cp[ref->ref.name_and_type_index];
const char *fname = cp_utf8(cf, nat->nat.name_index);
for (int i = 0; i < cf->fields_count; i++)
if (strcmp(cp_utf8(cf, cf->fields[i].name_index), fname) == 0)
return i;
fprintf(stderr, "field not found: %s\n", fname);
return -1;
}
int execute(ClassFile *cf, Method *m, intptr_t *args, int argc) {
Frame f = {0};
f.code = m->code.code;
/* copy arguments into locals */
for (int i = 0; i < argc; i++)
f.locals[i] = args[i];
while (1) {
uint8_t op = f.code[f.pc++];
switch (op) {
case OP_ALOAD_0: f.stack[f.sp++] = f.locals[0]; break;
case OP_ALOAD_1: f.stack[f.sp++] = f.locals[1]; break;
case OP_ALOAD_2: f.stack[f.sp++] = f.locals[2]; break;
case OP_ALOAD_3: f.stack[f.sp++] = f.locals[3]; break;
case OP_NEW: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
CPEntry *cls = &cf->cp[idx];
Object *obj = heap_alloc(cls, 0);
f.stack[f.sp++] = OBJ_TO_SLOT(obj);
break;
}
case OP_DUP: f.stack[f.sp] = f.stack[f.sp - 1]; f.sp++; break;
case OP_ICONST_0: f.stack[f.sp++] = 0; break;
case OP_ICONST_1: f.stack[f.sp++] = 1; break;
case OP_ICONST_2: f.stack[f.sp++] = 2; break;
case OP_ICONST_3: f.stack[f.sp++] = 3; break;
case OP_ICONST_4: f.stack[f.sp++] = 4; break;
case OP_ICONST_5: f.stack[f.sp++] = 5; break;
case OP_BIPUSH: f.stack[f.sp++] = (int8_t)f.code[f.pc++]; break;
case OP_SIPUSH: {
int16_t v = (int16_t)((f.code[f.pc] << 8) | f.code[f.pc + 1]);
f.pc += 2;
f.stack[f.sp++] = v;
break;
}
case OP_ISTORE_0: f.locals[0] = f.stack[--f.sp]; break;
case OP_ISTORE_1: f.locals[1] = f.stack[--f.sp]; break;
case OP_ISTORE_2: f.locals[2] = f.stack[--f.sp]; break;
case OP_ISTORE_3: f.locals[3] = f.stack[--f.sp]; break;
case OP_ILOAD_0: f.stack[f.sp++] = f.locals[0]; break;
case OP_ILOAD_1: f.stack[f.sp++] = f.locals[1]; break;
case OP_ILOAD_2: f.stack[f.sp++] = f.locals[2]; break;
case OP_ILOAD_3: f.stack[f.sp++] = f.locals[3]; break;
case OP_IADD: {
int b = f.stack[--f.sp];
int a = f.stack[--f.sp];
f.stack[f.sp++] = a + b;
break;
}
case OP_ISUB:{
int b = f.stack[--f.sp];
int a = f.stack[--f.sp];
f.stack[f.sp++] = a - b;
break;
}
case OP_IF_ICMPGT: {
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc+1];
f.pc += 2;
int b = f.stack[--f.sp];
int a = f.stack[--f.sp];
if (a > b) f.pc += offset - 3;
break;
}
case OP_IFNULL: {
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
intptr_t ref = f.stack[--f.sp];
if (ref == 0) f.pc += offset - 3;
break;
}
case OP_IFNONNULL: {
int16_t offset = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
intptr_t ref = f.stack[--f.sp];
if (ref != 0) f.pc += offset - 3;
break;
}
case OP_IRETURN: return f.stack[--f.sp];
case OP_RETURN: return 0;
case OP_GETSTATIC: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
const char *cls = NULL, *name = NULL, *desc = NULL;
if (resolve_field_info(cf, idx, &cls, &name, &desc) &&
strcmp(cls, "java/lang/System") == 0 &&
strcmp(name, "out") == 0 &&
strcmp(desc, "Ljava/io/PrintStream;") == 0) {
f.stack[f.sp++] = 0x57D0; /* fake PrintStream/static handle */
break;
}
f.stack[f.sp++] = g_static_fields[idx];
break;
}
case OP_PUTSTATIC: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
g_static_fields[idx] = f.stack[--f.sp];
break;
}
case OP_INVOKEVIRTUAL: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
f.pc += 2;
CPEntry *methodref = &cf->cp[idx];
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
const char *name = cp_utf8(cf, nat->nat.name_index);
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
const char *class_name = resolve_class_name(cf, methodref);
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
int nargs = 1; /* +1 for 'this' */
const char *d = desc + 1;
while (*d && *d != ')') {
switch (*d) {
case 'I': case 'Z': case 'B': case 'C': case 'S': case 'F':
nargs++; d++; break;
case 'J': case 'D': nargs += 2; d++; break;
case 'L': nargs++; while (*d && *d != ';') d++; d++; break;
case '[': nargs++;
while (*d == '[') d++;
if (*d == 'L') while (*d && *d != ';') d++;
d++; break;
default: d++; break;
}
}
ClassFile *target_cf = cf;
ClassFile *loaded_cf = NULL;
Method *target = classfile_find_method(cf, name, desc);
if (!target && class_name && *class_name) {
char class_path[PATH_MAX];
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
loaded_cf = classfile_load(class_path);
if (loaded_cf) {
target = classfile_find_method(loaded_cf, name, desc);
if (target) target_cf = loaded_cf;
}
}
if (!target) {
if (loaded_cf) classfile_free(loaded_cf);
fprintf(stderr, "method not found: %s.%s%s\n", class_name, name, desc);
return -1;
}
intptr_t vargs[32];
for (int i = nargs - 1; i >= 0; i--) vargs[i] = f.stack[--f.sp];
int result = execute(target_cf, target, vargs, nargs);
if (loaded_cf) classfile_free(loaded_cf);
const char *ret = strchr(desc, ')') + 1;
if (*ret != 'V') f.stack[f.sp++] = result;
break;
}
case OP_LDC: {
uint8_t idx = f.code[f.pc++];
CPEntry *e = &cf->cp[idx];
switch (e->tag) {
case CP_INTEGER: f.stack[f.sp++] = e->integer.value; break;
case CP_FLOAT: { int b; memcpy(&b, &e->flt.value, 4); f.stack[f.sp++] = b; break; }
case CP_STRING: f.stack[f.sp++] = (int)idx; break;
default: fprintf(stderr, "ldc: unhandled tag %d\n", e->tag); return -1;
}
break;
}
case OP_LDC_W:
case OP_LDC2_W: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
f.pc += 2;
CPEntry *e = &cf->cp[idx];
switch (e->tag) {
case CP_INTEGER: f.stack[f.sp++] = e->integer.value; break;
case CP_LONG:
f.stack[f.sp++] = (int)(e->lng.value & 0xFFFFFFFF);
f.stack[f.sp++] = (int)(e->lng.value >> 32);
break;
case CP_STRING: f.stack[f.sp++] = (int)idx; break;
default: fprintf(stderr, "ldc_w: unhandled tag %d\n", e->tag); return -1;
}
break;
}
case OP_INVOKESTATIC: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
f.pc += 2;
CPEntry *methodref = &cf->cp[idx];
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
const char *name = cp_utf8(cf, nat->nat.name_index);
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
const char *class_name = resolve_class_name(cf, methodref);
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
ClassFile *target_cf = cf;
ClassFile *loaded_cf = NULL;
Method *target = classfile_find_method(cf, name, desc);
if (!target && class_name && *class_name) {
char class_path[PATH_MAX];
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
loaded_cf = classfile_load(class_path);
if (loaded_cf) {
target = classfile_find_method(loaded_cf, name, desc);
if (target) target_cf = loaded_cf;
}
}
if (!target) {
if (loaded_cf) classfile_free(loaded_cf);
fprintf(stderr, "method not found: %s%s\n", name, desc);
return -1;
}
int nargs = 0;
const char *d = desc + 1; /* skip '(' */
while (*d && *d != ')') {
switch (*d) {
case 'I': case 'Z': case 'B':
case 'C': case 'S': case 'F':
nargs++; d++;
break;
case 'J': case 'D': /* long/double take 2 slots */
nargs += 2; d++;
break;
case 'L': /* object ref */
nargs++;
while (*d && *d != ';') d++;
d++; /* skip ';' */
break;
case '[': /* array */
nargs++;
while (*d == '[') d++; /* skip dimensions */
if (*d == 'L') while (*d && *d != ';') d++;
d++;
break;
}
}
intptr_t args[16];
for (int i = nargs - 1; i >= 0; i--) args[i] = f.stack[--f.sp];
int result = execute(target_cf, target, args, nargs);
if (loaded_cf) classfile_free(loaded_cf);
const char *ret = strchr(desc, ')') + 1;
if (*ret != 'V') f.stack[f.sp++] = result;
break;
}
case OP_ASTORE_0: f.locals[0] = f.stack[--f.sp]; break;
case OP_ASTORE_1: f.locals[1] = f.stack[--f.sp]; break;
case OP_ASTORE_2: f.locals[2] = f.stack[--f.sp]; break;
case OP_ASTORE_3: f.locals[3] = f.stack[--f.sp]; break;
case OP_INVOKESPECIAL: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc+1];
f.pc += 2;
CPEntry *methodref = &cf->cp[idx];
CPEntry *nat = &cf->cp[methodref->ref.name_and_type_index];
const char *name = cp_utf8(cf, nat->nat.name_index);
const char *desc = cp_utf8(cf, nat->nat.descriptor_index);
const char *class_name = resolve_class_name(cf, methodref);
const char *this_name = current_class_name(cf);
/* count args + implicit 'this' */
int nargs = 1;
const char *d = desc + 1;
while (*d && *d != ')') {
switch (*d) {
case 'I': case 'Z': case 'B': case 'C': case 'S': case 'F':
nargs++; d++; break;
case 'J': case 'D': nargs += 2; d++; break;
case 'L': nargs++; while (*d && *d != ';') d++; d++; break;
case '[': nargs++;
while (*d == '[') d++;
if (*d == 'L') while (*d && *d != ';') d++;
d++; break;
default: d++; break;
}
}
if (midp_native_call(cf, class_name, name, desc, f.stack, &f.sp, op)) break;
ClassFile *target_cf = cf;
ClassFile *loaded_cf = NULL;
Method *target = NULL;
if (class_name && *class_name && strcmp(class_name, this_name) != 0) {
char class_path[PATH_MAX];
snprintf(class_path, sizeof(class_path), "%s.class", class_name);
loaded_cf = classfile_load(class_path);
if (loaded_cf) {
target = classfile_find_method(loaded_cf, name, desc);
if (target) target_cf = loaded_cf;
}
} else {
target = classfile_find_method(cf, name, desc);
}
if (!target) {
/* super/<init> we don't have — just pop args+this */
f.sp -= nargs;
if (loaded_cf) classfile_free(loaded_cf);
break;
}
/* Guard against accidental self-resolution causing infinite recursion. */
if (target_cf == cf && target == m) {
fprintf(stderr, "recursive invokespecial guard: %s.%s%s\n", class_name, name, desc);
f.sp -= nargs;
if (loaded_cf) classfile_free(loaded_cf);
break;
}
intptr_t sargs[32];
for (int i = nargs - 1; i >= 0; i--) sargs[i] = f.stack[--f.sp];
execute(target_cf, target, sargs, nargs);
if (loaded_cf) classfile_free(loaded_cf);
break;
}
case OP_PUTFIELD: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
intptr_t val = f.stack[--f.sp];
Object *obj = SLOT_TO_OBJ(f.stack[--f.sp]);
if (!obj) { fprintf(stderr, "NPE: putfield at pc=%d\n", f.pc - 3); return -1; }
int slot = field_slot(cf, idx);
if (slot < 0) return -1;
if (slot >= obj->fields_count) {
obj->fields = realloc(obj->fields, (slot + 1) * sizeof(intptr_t));
for (int i = obj->fields_count; i <= slot; i++) obj->fields[i] = 0;
obj->fields_count = slot + 1;
}
obj->fields[slot] = val;
break;
}
case OP_GETFIELD: {
uint16_t idx = (f.code[f.pc] << 8) | f.code[f.pc + 1];
f.pc += 2;
Object *obj = SLOT_TO_OBJ(f.stack[--f.sp]);
if (!obj) { fprintf(stderr, "NPE: getfield at pc=%d\n", f.pc - 3); return -1; }
int slot = field_slot(cf, idx);
if (slot < 0) return -1;
if (slot >= obj->fields_count) {
obj->fields = realloc(obj->fields, (slot + 1) * sizeof(intptr_t));
for (int i = obj->fields_count; i <= slot; i++) obj->fields[i] = 0;
obj->fields_count = slot + 1;
}
f.stack[f.sp++] = obj->fields[slot];
break;
}
default:
printf("unknown opcode 0x%02X at pc=%d\n", op, f.pc-1);
return -1;
}
}
}
int main(int argc, char *argv[]) {
int exit_code = 0;
char input_path[PATH_MAX];
char original_cwd[PATH_MAX];
char jar_temp_dir[PATH_MAX] = {0};
int changed_dir = 0;
if (argc < 2) {
fprintf(stderr, "usage: %s <file.class|file.jar>\n", argv[0]);
return 1;
}
strncpy(input_path, argv[1], sizeof(input_path) - 1);
input_path[sizeof(input_path) - 1] = '\0';
if (ends_with(input_path, ".jar")) {
if (!getcwd(original_cwd, sizeof(original_cwd))) {
perror("getcwd");
return 1;
}
char tmpl[] = "/tmp/kolibrios-jar-XXXXXX";
char *tmp = mkdtemp(tmpl);
if (!tmp) {
perror("mkdtemp");
return 1;
}
strncpy(jar_temp_dir, tmp, sizeof(jar_temp_dir) - 1);
char qjar[PATH_MAX * 2], qtmp[PATH_MAX * 2], cmd[PATH_MAX * 5];
if (!shell_quote(input_path, qjar, sizeof(qjar)) ||
!shell_quote(jar_temp_dir, qtmp, sizeof(qtmp))) {
fprintf(stderr, "path too long for jar extraction\n");
return 1;
}
snprintf(cmd, sizeof(cmd), "unzip -oq %s -d %s >/dev/null", qjar, qtmp);
if (system(cmd) != 0) {
fprintf(stderr, "failed to extract jar: %s\n", input_path);
return 1;
}
if (!resolve_jar_entry_class(jar_temp_dir, input_path, sizeof(input_path))) {
fprintf(stderr, "could not resolve entry class from jar manifest: %s\n", argv[1]);
return 1;
}
if (chdir(jar_temp_dir) != 0) {
perror("chdir");
return 1;
}
changed_dir = 1;
}
midp_sdl_init(240, 320);
/* step 1 - load the class file */
ClassFile *cf = classfile_load(input_path);
if (!cf) {
midp_sdl_destroy();
if (changed_dir) chdir(original_cwd);
return 1;
}
/* step 2 - execute entry point */
Method *m = classfile_find_method(cf, "main", "([Ljava/lang/String;)V");
if (!m) {
Method *start = classfile_find_method(cf, "startApp", "()V");
if (!start) {
fprintf(stderr, "no main or startApp method found\n");
exit_code = 1;
goto cleanup;
}
Object *self = heap_alloc(&cf->cp[cf->this_class], cf->fields_count);
if (!self) {
fprintf(stderr, "failed to allocate MIDlet instance\n");
exit_code = 1;
goto cleanup;
}
Method *ctor = classfile_find_method(cf, "<init>", "()V");
if (ctor) {
intptr_t cargs[1] = { OBJ_TO_SLOT(self) };
if (execute(cf, ctor, cargs, 1) < 0) {
exit_code = 1;
goto cleanup;
}
}
intptr_t sargs[1] = { OBJ_TO_SLOT(self) };
if (execute(cf, start, sargs, 1) < 0) {
exit_code = 1;
goto cleanup;
}
} else {
if (execute(cf, m, NULL, 0) < 0) {
exit_code = 1;
goto cleanup;
}
}
/* Keep emulator window visible after startup work. */
midp_sdl_wait(5000);
cleanup:
classfile_free(cf);
midp_sdl_destroy();
if (changed_dir) {
char qtmp[PATH_MAX * 2], cmd[PATH_MAX * 3];
chdir(original_cwd);
if (shell_quote(jar_temp_dir, qtmp, sizeof(qtmp))) {
snprintf(cmd, sizeof(cmd), "rm -rf %s", qtmp);
system(cmd);
}
}
return exit_code;
}