299 lines
11 KiB
C
299 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "classfile.h"
|
|
|
|
static uint8_t read_u1(FILE *f) {
|
|
return (uint8_t)fgetc(f);
|
|
}
|
|
static uint16_t read_u2(FILE *f) {
|
|
uint16_t a = read_u1(f), b = read_u1(f);
|
|
return (a << 8) | b;
|
|
}
|
|
static uint32_t read_u4(FILE *f) {
|
|
uint32_t a = read_u2(f), b = read_u2(f);
|
|
return (a << 16) | b;
|
|
}
|
|
static uint64_t read_u8(FILE *f) {
|
|
uint64_t a = read_u4(f), b = read_u4(f);
|
|
return (a << 32) | b;
|
|
}
|
|
|
|
static void skip_attr(FILE *f) {
|
|
uint32_t len = read_u4(f);
|
|
fseek(f, len, SEEK_CUR);
|
|
}
|
|
|
|
static void parse_code_attr(FILE *f, CodeAttr *code) {
|
|
read_u4(f); /* attribute_length (ignored) */
|
|
code->max_stack = read_u2(f);
|
|
code->max_locals = read_u2(f);
|
|
code->code_len = read_u4(f);
|
|
code->code = malloc(code->code_len);
|
|
fread(code->code, 1, code->code_len, f);
|
|
|
|
code->exception_table_len = read_u2(f);
|
|
code->exception_table = NULL;
|
|
if (code->exception_table_len > 0) {
|
|
code->exception_table = malloc(
|
|
code->exception_table_len * sizeof(ExceptionHandler));
|
|
for (int i = 0; i < code->exception_table_len; i++) {
|
|
code->exception_table[i].start_pc = read_u2(f);
|
|
code->exception_table[i].end_pc = read_u2(f);
|
|
code->exception_table[i].handler_pc = read_u2(f);
|
|
code->exception_table[i].catch_type = read_u2(f);
|
|
}
|
|
}
|
|
|
|
/* skip Code sub-attributes (LineNumberTable, LocalVariableTable …) */
|
|
uint16_t attr_count = read_u2(f);
|
|
for (int i = 0; i < attr_count; i++) {
|
|
read_u2(f); /* attribute_name_index */
|
|
skip_attr(f);
|
|
}
|
|
}
|
|
|
|
static int parse_constant_pool(FILE *f, ClassFile *cf) {
|
|
cf->cp = calloc(cf->cp_count, sizeof(CPEntry));
|
|
if (!cf->cp) return 0;
|
|
|
|
/* spec: index starts at 1; long/double consume two slots */
|
|
for (int i = 1; i < cf->cp_count; i++) {
|
|
CPEntry *e = &cf->cp[i];
|
|
e->tag = read_u1(f);
|
|
switch (e->tag) {
|
|
case CP_UTF8: {
|
|
e->utf8.len = read_u2(f);
|
|
e->utf8.bytes = malloc(e->utf8.len + 1);
|
|
fread(e->utf8.bytes, 1, e->utf8.len, f);
|
|
e->utf8.bytes[e->utf8.len] = '\0';
|
|
break;
|
|
}
|
|
case CP_INTEGER:
|
|
e->integer.value = (int32_t)read_u4(f);
|
|
break;
|
|
case CP_FLOAT: {
|
|
uint32_t raw = read_u4(f);
|
|
memcpy(&e->flt.value, &raw, 4);
|
|
break;
|
|
}
|
|
case CP_LONG:
|
|
e->lng.value = (int64_t)read_u8(f);
|
|
i++; /* long takes two CP slots */
|
|
break;
|
|
case CP_DOUBLE: {
|
|
uint64_t raw = read_u8(f);
|
|
memcpy(&e->dbl.value, &raw, 8);
|
|
i++; /* double takes two CP slots */
|
|
break;
|
|
}
|
|
case CP_CLASS:
|
|
case CP_STRING:
|
|
e->cls_or_str.index = read_u2(f);
|
|
break;
|
|
case CP_FIELDREF:
|
|
case CP_METHODREF:
|
|
case CP_IFACE_METHODREF:
|
|
e->ref.class_index = read_u2(f);
|
|
e->ref.name_and_type_index = read_u2(f);
|
|
break;
|
|
case CP_NAME_AND_TYPE:
|
|
e->nat.name_index = read_u2(f);
|
|
e->nat.descriptor_index = read_u2(f);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "classfile: unknown CP tag %d at index %d\n",
|
|
e->tag, i);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ── parse fields ───────────────────────────────────────────────────────── */
|
|
static int parse_fields(FILE *f, ClassFile *cf) {
|
|
cf->fields = calloc(cf->fields_count, sizeof(Field));
|
|
if (!cf->fields && cf->fields_count > 0) return 0;
|
|
|
|
for (int i = 0; i < cf->fields_count; i++) {
|
|
cf->fields[i].access_flags = read_u2(f);
|
|
cf->fields[i].name_index = read_u2(f);
|
|
cf->fields[i].descriptor_index = read_u2(f);
|
|
|
|
/* skip field attributes (ConstantValue, etc.) */
|
|
uint16_t ac = read_u2(f);
|
|
for (int j = 0; j < ac; j++) {
|
|
read_u2(f);
|
|
skip_attr(f);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ── parse methods ──────────────────────────────────────────────────────── */
|
|
static int parse_methods(FILE *f, ClassFile *cf) {
|
|
cf->methods = calloc(cf->methods_count, sizeof(Method));
|
|
if (!cf->methods && cf->methods_count > 0) return 0;
|
|
|
|
for (int i = 0; i < cf->methods_count; i++) {
|
|
Method *m = &cf->methods[i];
|
|
m->access_flags = read_u2(f);
|
|
m->name_index = read_u2(f);
|
|
m->descriptor_index = read_u2(f);
|
|
m->has_code = 0;
|
|
|
|
uint16_t ac = read_u2(f);
|
|
for (int j = 0; j < ac; j++) {
|
|
uint16_t attr_name_idx = read_u2(f);
|
|
const char *attr_name = cp_utf8(cf, attr_name_idx);
|
|
|
|
if (strcmp(attr_name, "Code") == 0) {
|
|
parse_code_attr(f, &m->code);
|
|
m->has_code = 1;
|
|
} else {
|
|
skip_attr(f);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ── public: load a .class file ─────────────────────────────────────────── */
|
|
ClassFile *classfile_load(const char *path) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) {
|
|
perror(path);
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t magic = read_u4(f);
|
|
if (magic != 0xCAFEBABE) {
|
|
fprintf(stderr, "%s: not a class file (magic=0x%08X)\n", path, magic);
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
ClassFile *cf = calloc(1, sizeof(ClassFile));
|
|
cf->minor_version = read_u2(f);
|
|
cf->major_version = read_u2(f);
|
|
|
|
/* constant pool */
|
|
cf->cp_count = read_u2(f);
|
|
if (!parse_constant_pool(f, cf)) goto fail;
|
|
|
|
cf->access_flags = read_u2(f);
|
|
cf->this_class = read_u2(f);
|
|
cf->super_class = read_u2(f);
|
|
|
|
/* interfaces */
|
|
cf->interfaces_count = read_u2(f);
|
|
cf->interfaces = malloc(cf->interfaces_count * sizeof(uint16_t));
|
|
for (int i = 0; i < cf->interfaces_count; i++)
|
|
cf->interfaces[i] = read_u2(f);
|
|
|
|
/* fields */
|
|
cf->fields_count = read_u2(f);
|
|
if (!parse_fields(f, cf)) goto fail;
|
|
|
|
/* methods */
|
|
cf->methods_count = read_u2(f);
|
|
if (!parse_methods(f, cf)) goto fail;
|
|
|
|
/* skip class-level attributes (SourceFile, etc.) */
|
|
uint16_t ac = read_u2(f);
|
|
for (int i = 0; i < ac; i++) {
|
|
read_u2(f);
|
|
skip_attr(f);
|
|
}
|
|
|
|
fclose(f);
|
|
return cf;
|
|
|
|
fail:
|
|
fclose(f);
|
|
classfile_free(cf);
|
|
return NULL;
|
|
}
|
|
|
|
/* ── public: find a method by name + descriptor ─────────────────────────── */
|
|
Method *classfile_find_method(ClassFile *cf, const char *name, const char *descriptor) {
|
|
for (int i = 0; i < cf->methods_count; i++) {
|
|
const char *n = cp_utf8(cf, cf->methods[i].name_index);
|
|
const char *d = cp_utf8(cf, cf->methods[i].descriptor_index);
|
|
if (strcmp(n, name) == 0 && strcmp(d, descriptor) == 0)
|
|
return &cf->methods[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ── public: free everything ────────────────────────────────────────────── */
|
|
void classfile_free(ClassFile *cf) {
|
|
if (!cf) return;
|
|
if (cf->cp) {
|
|
for (int i = 1; i < cf->cp_count; i++)
|
|
if (cf->cp[i].tag == CP_UTF8)
|
|
free(cf->cp[i].utf8.bytes);
|
|
free(cf->cp);
|
|
}
|
|
if (cf->interfaces) free(cf->interfaces);
|
|
if (cf->fields) free(cf->fields);
|
|
if (cf->methods) {
|
|
for (int i = 0; i < cf->methods_count; i++) {
|
|
if (cf->methods[i].has_code) {
|
|
free(cf->methods[i].code.code);
|
|
free(cf->methods[i].code.exception_table);
|
|
}
|
|
}
|
|
free(cf->methods);
|
|
}
|
|
free(cf);
|
|
}
|
|
|
|
/* ── public: debug dump ─────────────────────────────────────────────────── */
|
|
void classfile_dump(const ClassFile *cf) {
|
|
printf("Class file version: %d.%d\n",
|
|
cf->major_version, cf->minor_version);
|
|
printf("This class : %s\n",
|
|
cp_utf8(cf, cf->cp[cf->this_class].cls_or_str.index));
|
|
printf("Super class: %s\n",
|
|
cf->super_class
|
|
? cp_utf8(cf, cf->cp[cf->super_class].cls_or_str.index)
|
|
: "(none)");
|
|
|
|
printf("\nConstant pool (%d entries):\n", cf->cp_count - 1);
|
|
for (int i = 1; i < cf->cp_count; i++) {
|
|
CPEntry *e = &cf->cp[i];
|
|
printf(" #%-3d ", i);
|
|
switch (e->tag) {
|
|
case CP_UTF8: printf("Utf8 \"%s\"\n", e->utf8.bytes); break;
|
|
case CP_INTEGER: printf("Integer %d\n", e->integer.value); break;
|
|
case CP_FLOAT: printf("Float %f\n", e->flt.value); break;
|
|
case CP_LONG: printf("Long %lld\n", (long long)e->lng.value); break;
|
|
case CP_DOUBLE: printf("Double %f\n", e->dbl.value); break;
|
|
case CP_CLASS: printf("Class #%d\n", e->cls_or_str.index); break;
|
|
case CP_STRING: printf("String #%d\n", e->cls_or_str.index); break;
|
|
case CP_FIELDREF:
|
|
printf("Fieldref #%d.#%d\n",
|
|
e->ref.class_index, e->ref.name_and_type_index); break;
|
|
case CP_METHODREF:
|
|
printf("Methodref #%d.#%d\n",
|
|
e->ref.class_index, e->ref.name_and_type_index); break;
|
|
case CP_NAME_AND_TYPE:
|
|
printf("NameAndType #%d:#%d\n",
|
|
e->nat.name_index, e->nat.descriptor_index); break;
|
|
default: printf("(tag %d)\n", e->tag);
|
|
}
|
|
}
|
|
|
|
printf("\nMethods (%d):\n", cf->methods_count);
|
|
for (int i = 0; i < cf->methods_count; i++) {
|
|
Method *m = &cf->methods[i];
|
|
printf(" %s %s [code=%s, max_stack=%d, max_locals=%d, len=%d]\n",
|
|
cp_utf8(cf, m->name_index),
|
|
cp_utf8(cf, m->descriptor_index),
|
|
m->has_code ? "yes" : "no",
|
|
m->has_code ? m->code.max_stack : 0,
|
|
m->has_code ? m->code.max_locals : 0,
|
|
m->has_code ? (int)m->code.code_len : 0);
|
|
}
|
|
} |