ASM 系列详细教程-12-ASM metadata 元数据之调试 debug

本文深入探讨Java类文件中包含的调试信息,包括源文件名、源行号与字节码指令映射,以及本地变量名与字节码槽位的关联。介绍了如何使用ASM框架的ClassVisitor和MethodVisitor类访问这些信息,并提供了示例代码。同时,讲解了如何在不关心调试信息时,使用ClassReader类的SKIP_DEBUG选项忽略这些信息。

Debug

使用 javac -g 编译的类包含其源文件的名称,源行号和字节码指令之间的映射以及源代码中的本地变量名称和字节码中的本地变量槽之间的映射。

此可选信息在调试器中以及可用时在异常堆栈跟踪中使用。

结构体

类的源文件名存储在专用的类文件结构部分中(请参见图2.1)。

源行号和字节码指令之间的映射存储为方法的已编译代码部分中(行号,标签)对的列表。

例如,如果l1,l2和l3是按此顺序出现的三个标签,则以下对:

(n1, l1)
(n2, l2)
(n3, l3)

表示l1和l2之间的指令来自第n1行,l2和l3之间的指令来自第n2行,l3之后的指令来自第n3行。

请注意,给定的行号可以成对出现。

这是因为与出现在单个源代码行上的表达式相对应的指令在字节码中可能不是连续的。

例如对于 (init; cond; incr 语句; 通常按以下顺序编译:init语句incr cond。

源代码中的本地变量名称与字节码中的本地变量插槽之间的映射存储为方法的已编译代码部分中的(名称,类型描述符,类型签名,开始,结束,索引)元组的列表。

这样的元组意味着,在两个标签的开始和结束之间,插槽索引中的局部变量对应于其源代码中名称和类型由前三个元组元素给出的局部变量。

注意,编译器可以使用相同的局部变量槽来存储具有不同范围的不同源局部变量。

相反,可以将唯一的源局部变量编译为具有不连续范围的局部变量槽。 例如,可能会出现以下情况:

l1:
... // here slot 1 contains local variable i
l2:
... // here slot 1 contains local variable j
l3:
... // here slot 1 contains local variable i again
end:

相应的元组是:

("i", "I", null, l1, l2, 1)
("j", "I", null, l2, l3, 1)
("i", "I", null, l3, end, 1)

接口和组件

使用ClassVisitor和MethodVisitor类的三种方法访问调试信息:

  1. 使用的visitSource方法访问源文件名ClassVisitor类;

  2. 源行号和字节码指令之间的映射通过MethodVisitor类的visitLineNumber方法访问,一次一对。

  3. 使用MethodVisitor类的visitLocalVariable方法访问源代码中的本地变量名称和字节码中的本地变量插槽之间的映射,一次访问一个元组。

在访问作为参数传递的标签之后,必须调用visitLineNumber方法。

实际上,在该标签之后调用它,这使得在方法访问者中很容易知道当前指令的源代码行:

public class MyAdapter extends MethodVisitor {
    int currentLine;

    public MyAdapter(MethodVisitor mv) {
        super(ASM4, mv);
    }

    @Override
    public void visitLineNumber(int line, Label start) {
        mv.visitLineNumber(line, start);
        currentLine = line;
    }
    ...
}

同样,在访问作为参数传递的标签之后,必须调用visitLocalVariable方法。

这是与上一节中介绍的对和元组相对应的示例方法调用:

visitLineNumber(n1, l1);
visitLineNumber(n2, l2);
visitLineNumber(n3, l3);
visitLocalVariable("i", "I", null, l1, l2, 1);
visitLocalVariable("j", "I", null, l2, l3, 1);
visitLocalVariable("i", "I", null, l3, end, 1);

忽略调试信息

为了访问行号和局部变量名,ClassReader类可能需要引入“人工” Label对象,从某种意义上说,跳转指令不需要它们,而只是代表调试信息。

这可能会在诸如3.2.5节中解释的情况下引入误报,在这种情况下,指令序列中间的Label被视为跳转目标,因此阻止了该序列的删除。

为了避免这些误报,可以在ClassReader.accept方法中使用 SKIP_DEBUG 选项。

使用此选项,类读取器不会访问调试信息,也不会为其创建人工标签。

当然,调试信息将从类中删除,因此仅当这对您的应用程序来说不是问题时才可以使用此选项。

注意:ClassReader类提供了其他选项,例如SKIP_CODE跳过对已编译代码的访问(如果只需要类结构,这将很有用),SKIP_FRAMES跳过堆栈映射框架,以及EXPAND_FRAMES取消压缩这些框架。

工具类

与泛型类型和注释一样,您可以使用TraceClassVisitor,CheckClassAdapter和ASMifier类来查找如何使用调试信息。

拓展阅读

更多技术文章,生活趣事,尽在【老马啸西风】。

老马啸西风

参考文档

https://asm.ow2.io/asm4-guide.pdf

Resolving esp32s3 dependencies... Already up-to-date. Updating metadata for the vscode IDE... UserSideException: Processing esp32s3 (platform: espressif32 @ ~6.9.0; board: esp32-s3-devkitc-1; framework: espidf) -------------------------------------------------------------------------------- Verbose mode can be enabled via `-v, --verbose` option CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html PLATFORM: Espressif 32 (6.9.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM) HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa) PACKAGES: - framework-espidf @ 3.50301.0 (5.3.1) - tool-cmake @ 3.16.4 - tool-esptoolpy @ 1.40501.0 (4.5.1) - tool-idf @ 1.0.1 - tool-mconf @ 1.4060000.20190628 (406.0.0) - tool-ninja @ 1.9.0 - tool-riscv32-esp-elf-gdb @ 11.2.0+20220823 - tool-xtensa-esp-elf-gdb @ 11.2.0+20230208 - toolchain-esp32ulp @ 1.23800.240113 (2.38.0) - toolchain-riscv32-esp @ 13.2.0+20240530 - toolchain-xtensa-esp-elf @ 13.2.0+20240530 Reading CMake configuration... -- Found Git: C:/Program Files/Git/cmd/git.exe (found version "2.51.0.windows.1") -- git rev-parse returned 'fatal: not a git repository (or any of the parent directories): .git' -- The C compiler identification is GNU 13.2.0 -- The CXX compiler identification is GNU 13.2.0 -- The ASM compiler identification is GNU -- Found assembler: C:/Users/Administrator/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc.exe -- Check for working C compiler: C:/Users/Administrator/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc.exe -- Check for working C compiler: C:/Users/Administrator/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc.exe -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: C:/Users/Administrator/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-g++.exe -- Check for working CXX compiler: C:/Users/Administrator/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-g++.exe -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring incomplete, errors occurred! See also "E:/esp32/esp32_spi_slave(more_slave)1/.pio/build/esp32s3/CMakeFiles/CMakeOutput.log". fatal: not a git repository (or any of the parent directories): .git CMake Error at C:/Users/Administrator/.platformio/packages/framework-espidf/tools/cmake/project.cmake:677 (if): if given arguments: "(" "STREQUAL" "VERSION" mismatched parenthesis in condition Call Stack (most recent call first): CMakeLists.txt:3 (project) ========================= [FAILED] Took 11.48 seconds =========================
11-11
<think> 我们使用ASM框架来检查类上是否有@Component注解。具体步骤包括: 1. 使用ClassReader读取类的字节码。 2. 定义一个ClassVisitor,重写visitAnnotation方法,在该方法中检查注解描述符是否匹配@Component。 3. 在访问类时,记录是否存在@Component注解。 注意:@Component注解的全限定名通常为"org.springframework.stereotype.Component"。 在字节码中,注解用描述符表示,即"Lorg/springframework/stereotype/Component;"。 实现代码示例: </think> 使用 Java ASM 框架检测类上是否存在 `@Component` 注解的实现步骤如下(基于 Spring 框架的注解规范): ### 实现原理 1. **注解描述符** `@Component` 注解在字节码中的描述符为:`"Lorg/springframework/stereotype/Component;"` 2. **检测流程** 通过 `ClassVisitor` 的 `visitAnnotation` 方法匹配注解描述符 ### 完整代码实现 ```java import org.objectweb.asm.*; import java.io.IOException; public class ComponentAnnotationChecker { // 注解描述符 (Spring官方路径) private static final String COMPONENT_DESC = "Lorg/springframework/stereotype/Component;"; public static boolean hasComponentAnnotation(byte[] classBytes) { ClassReader cr = new ClassReader(classBytes); ComponentDetector detector = new ComponentDetector(); cr.accept(detector, ClassReader.SKIP_DEBUG); return detector.hasComponent; } private static class ComponentDetector extends ClassVisitor { boolean hasComponent = false; public ComponentDetector() { super(Opcodes.ASM9); } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { // 匹配@Component注解 if (COMPONENT_DESC.equals(descriptor)) { hasComponent = true; } return null; } } // 使用示例 public static void main(String[] args) throws IOException { // 从类路径读取字节码 byte[] bytes = ComponentAnnotationChecker.class .getResourceAsStream("/com/example/YourClass.class").readAllBytes(); boolean result = hasComponentAnnotation(bytes); System.out.println("包含@Component注解: " + result); } } ``` ### 关键步骤说明 1. **注解描述符匹配** `visitAnnotation` 方法中检查描述符是否等于 `Lorg/springframework/stereotype/Component;` 2. **字节码获取方式** - 类加载器获取:`ClassLoader.getResourceAsStream()` - 文件系统获取:`Files.readAllBytes(Paths.get("YourClass.class"))` - Spring 集成:通过 `MetadataReaderFactory` 获取(参考引用[1]) 3. **性能优化** `ClassReader.SKIP_DEBUG` 参数跳过调试信息可提升解析速度 ### Spring 集成方案(参考引用[1]) ```java // 使用Spring的MetadataReader抽象 import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; public boolean isSpringComponent(Class<?> clazz) throws IOException { MetadataReaderFactory factory = new SimpleMetadataReaderFactory(); MetadataReader reader = factory.getMetadataReader(clazz.getName()); AnnotationMetadata meta = reader.getAnnotationMetadata(); return meta.hasAnnotation("org.springframework.stereotype.Component"); } ``` ### 注意事项 1. **注解继承** 如需检测元注解(如 `@Service` 继承 `@Component`),需递归检查: ```java @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (isComponentOrDerived(desc)) { hasComponent = true; } return null; } boolean isComponentOrDerived(String desc) { return COMPONENT_DESC.equals(desc) || derivedAnnotations.contains(desc); } ``` 2. **多模块支持** 在 OSGi 或模块化环境中,需确保 `org.springframework.stereotype` 包可访问 3. **性能对比** | 方法 | 速度 | 内存开销 | 依赖 | |---|---|----|----| | ASM 原生 | ★★★ | 低 | 无 | | Spring MetadataReader | ★★ | 中 | Spring-core | | 反射 API | ★ | 高 | JDK | > **提示**:对于纯注解检测需求,ASM 原生实现比反射快 10-100 倍[^1],且无需加载类到 JVM。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值