如何解析Java类文件常量池?

如何解析Java类文件常量池?,java,bytecode,pool,Java,Bytecode,Pool,根据-类文件的Java常量池从文件中的10个字节开始 到目前为止,我已经能够解析之前的所有内容(神奇地检查它是否是类文件、主/次版本、常量池大小),但我仍然不知道如何解析常量池。比如,是否有用于指定方法引用和其他内容的操作码 在文本以十六进制表示之前,我有没有办法引用每个十六进制值来找出下面的值 我是否应该通过按NOP(0x00)拆分每组条目,然后解析每个不是文本值的字节 例如,我如何准确地计算出这些值中的每一个值代表什么? 对于类文件,您需要的唯一相关文档是,尤其是,如果您要解析的不是常量池,

根据-类文件的Java常量池从文件中的10个字节开始

到目前为止,我已经能够解析之前的所有内容(神奇地检查它是否是类文件、主/次版本、常量池大小),但我仍然不知道如何解析常量池。比如,是否有用于指定方法引用和其他内容的操作码

在文本以十六进制表示之前,我有没有办法引用每个十六进制值来找出下面的值

我是否应该通过按NOP(0x00)拆分每组条目,然后解析每个不是文本值的字节

例如,我如何准确地计算出这些值中的每一个值代表什么?

对于类文件,您需要的唯一相关文档是,尤其是,如果您要解析的不是常量池,那么

常量池由可变长度项组成,其第一个字节决定其类型,而该类型又决定其大小。大多数项目由一个或两个指向其他项目的索引组成。不需要任何第三方库的简单解析代码可能如下所示:

public static final int HEAD=0xcafebabe;
// Constant pool types
public static final byte CONSTANT_Utf8               = 1;
public static final byte CONSTANT_Integer            = 3;
public static final byte CONSTANT_Float              = 4;
public static final byte CONSTANT_Long               = 5;
public static final byte CONSTANT_Double             = 6;
public static final byte CONSTANT_Class              = 7;
public static final byte CONSTANT_String             = 8;
public static final byte CONSTANT_FieldRef           = 9;
public static final byte CONSTANT_MethodRef          =10;
public static final byte CONSTANT_InterfaceMethodRef =11;
public static final byte CONSTANT_NameAndType        =12;
public static final byte CONSTANT_MethodHandle       =15;
public static final byte CONSTANT_MethodType         =16;
public static final byte CONSTANT_InvokeDynamic      =18;
public static final byte CONSTANT_Module             =19;
public static final byte CONSTANT_Package            =20;

static void parseRtClass(Class<?> clazz) throws IOException, URISyntaxException {
    URL url = clazz.getResource(clazz.getSimpleName()+".class");
    if(url==null) throw new IOException("can't access bytecode of "+clazz);
    Path p = Paths.get(url.toURI());
    if(!Files.exists(p))
        p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
    parse(ByteBuffer.wrap(Files.readAllBytes(p)));
}
static void parseClassFile(Path path) throws IOException {
    ByteBuffer bb;
    try(FileChannel ch=FileChannel.open(path, StandardOpenOption.READ)) {
        bb=ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
    }
    parse(bb);
}
static void parse(ByteBuffer buf) {
    if(buf.order(ByteOrder.BIG_ENDIAN).getInt()!=HEAD) {
        System.out.println("not a valid class file");
        return;
    }
    int minor=buf.getChar(), ver=buf.getChar();
    System.out.println("version "+ver+'.'+minor);
    for(int ix=1, num=buf.getChar(); ix<num; ix++) {
        String s; int index1=-1, index2=-1;
        byte tag = buf.get();
        switch(tag) {
            default:
                System.out.println("unknown pool item type "+buf.get(buf.position()-1));
                return;
            case CONSTANT_Utf8: decodeString(ix, buf); continue;
            case CONSTANT_Class: case CONSTANT_String: case CONSTANT_MethodType:
            case CONSTANT_Module: case CONSTANT_Package:
                s="%d:\t%s ref=%d%n"; index1=buf.getChar();
                break;
            case CONSTANT_FieldRef: case CONSTANT_MethodRef:
            case CONSTANT_InterfaceMethodRef: case CONSTANT_NameAndType:
                s="%d:\t%s ref1=%d, ref2=%d%n";
                index1=buf.getChar(); index2=buf.getChar();
                break;
            case CONSTANT_Integer: s="%d:\t%s value="+buf.getInt()+"%n"; break;
            case CONSTANT_Float: s="%d:\t%s value="+buf.getFloat()+"%n"; break;
            case CONSTANT_Double: s="%d:\t%s value="+buf.getDouble()+"%n"; ix++; break;
            case CONSTANT_Long: s="%d:\t%s value="+buf.getLong()+"%n"; ix++; break;
            case CONSTANT_MethodHandle:
                s="%d:\t%s kind=%d, ref=%d%n"; index1=buf.get(); index2=buf.getChar();
                break;
             case CONSTANT_InvokeDynamic:
                s="%d:\t%s bootstrap_method_attr_index=%d, ref=%d%n";
                index1=buf.getChar(); index2=buf.getChar();
                break;
        }
        System.out.printf(s, ix, FMT[tag], index1, index2);
    }
}
private static String[] FMT= {
    null, "Utf8", null, "Integer", "Float", "Long", "Double", "Class",
    "String", "Field", "Method", "Interface Method", "Name and Type",
    null, null, "MethodHandle", "MethodType", null, "InvokeDynamic",
    "Module", "Package"
};

private static void decodeString(int poolIndex, ByteBuffer buf) {
    int size=buf.getChar(), oldLimit=buf.limit();
    buf.limit(buf.position()+size);
    StringBuilder sb=new StringBuilder(size+(size>>1)+16)
        .append(poolIndex).append(":\tUtf8 ");
    while(buf.hasRemaining()) {
        byte b=buf.get();
        if(b>0) sb.append((char)b);
        else
        {
            int b2 = buf.get();
            if((b&0xf0)!=0xe0)
                sb.append((char)((b&0x1F)<<6 | b2&0x3F));
            else
            {
                int b3 = buf.get();
                sb.append((char)((b&0x0F)<<12 | (b2&0x3F)<<6 | b3&0x3F));
            }
        }
    }
    buf.limit(oldLimit);
    System.out.println(sb);
}
public static final int HEAD=0xcafebabe;
//固定池类型
公共静态最终字节常量\u Utf8=1;
公共静态最终字节常量_整数=3;
公共静态最终字节常量\u Float=4;
公共静态最终字节常量_Long=5;
公共静态最终字节常量_Double=6;
公共静态最终字节常量_Class=7;
公共静态最终字节常量\u字符串=8;
公共静态最终字节常量\u FieldRef=9;
公共静态最终字节常量_MethodRef=10;
公共静态最终字节常量\u InterfaceMethodRef=11;
公共静态最终字节常量\u name和type=12;
公共静态最终字节常量_MethodHandle=15;
公共静态最终字节常量_MethodType=16;
公共静态最终字节常量_InvokeDynamic=18;
公共静态最终字节常量_模块=19;
公共静态最终字节常量_Package=20;
静态void parseRtClass(类clazz)抛出IOException、urisyntaxeption{
URL URL=clazz.getResource(clazz.getSimpleName()+“.class”);
如果(url==null)抛出新IOException(“无法访问”+clazz的字节码);
Path p=Path.get(url.toURI());
如果(!Files.exists(p))
p=p.resolve(“/modules”).resolve(p.getRoot().relatize(p));
parse(ByteBuffer.wrap(Files.readAllBytes(p));
}
静态void parseClassFile(路径路径)引发IOException{
ByteBuffer bb;
try(FileChannel ch=FileChannel.open(路径,StandardOpenOption.READ)){
bb=ch.map(FileChannel.MapMode.READ_ONLY,0,ch.size());
}
解析(bb);
}
静态void解析(ByteBuffer buf){
if(buf.order(ByteOrder.BIG_ENDIAN.getInt()!=HEAD){
System.out.println(“不是有效的类文件”);
返回;
}
int minor=buf.getChar(),ver=buf.getChar();
System.out.println(“版本”+版本+”.+次要版本);
对于(intix=1,num=buf.getChar();ix>1)+16)
.append(poolidex).append(“:\tUtf8”);
while(buf.haslaining()){
字节b=buf.get();
如果(b>0)sb.append((char)b);
其他的
{
int b2=buf.get();
如果((b&0xf0)!=0xe0)

sb.追加((字符)((b&0x1F)所有这些都在JVM规范中,或者您可以查看
javap
的源代码,它已经完成了所有这一切,或者使用类似BCEL的东西,它也已经完成了所有这一切。例如,请参阅。JVM规范的相关部分:读取池非常简单。我有一个在Python中这样做的示例—您将想要习惯于读取JVM规范,因为理解它们是处理类文件的必要条件。@DaveNewton我通常会使用类似ASM或BCEL的东西,但这是一个重新创建轮子的项目,这样我就可以理解它在引擎盖下是如何工作的。到目前为止,我只是对如何确定每个项目中的常量感到困惑常量表是。我想我只需要确定标记,然后每个常量的结构就会不同。顺便问一下,字符串常量和utf-8常量之间有什么区别?字符串常量是否表示实际字符串值,utf-8常量是否表示方法名称、方法描述符或..?
utf-8
常量存储实际字符数据,而不考虑其用途。它们是保存实际字符数据的唯一常量,而所有其他常量将直接或间接引用
utf-8
常量。因此,是的,
string
常量表示实际
string
,但它包含对
utf的引用-8仅限常量。同样,类名、成员名和签名指的是
utf-8
常量。此外,类文件属性有一个名称索引,该索引指的是
utf-8
常量。