能介绍一下StackMapTable属性的网站运作原理理吗

ProGuard 最全混淆规则说明原文链接:
说明:本文参考(翻译)自Android SDK根目录下的proguard目录下的说明文档,是其中的一篇。,文中除了翻译外加了一些作者的实际验证。文章对Android混淆规则做一个解释说明。作者才疏学浅,如有错误,请谅解!&@_@&Android混淆入门可参考
Input/Output Options 输入输出选项
-include filename递归引入目录的配置文件-basedirectory directoryname-injars class_path指定应用程序要处理的jars包(或者wars、ears、zip、或者目录结构),它们里面的class文件会被处理并被写入到输出jars里面。它们里面的任何非class文件会被直接复制过去但是不会处理。(需要注意过滤调一些IDE自动生成的文件);-outjars
指定输出jars(wars、ears、zip、目录结构)的名称;由-injars 指定的被处理的jars将被写入到指定的输出jars里。如果不指定outjars将不会有class文件被写入。-libraryjars class_path 不混淆指定的jar库(android
项目中一般不混淆引入的第三方类库)-skipnonpubliclibraryclasses 不混淆指定jars中的非public calsses-dontskipnonpubliclibraryclasses 不忽略指定jars中的非public calsses (默认选项)和上面的选手想对-dontskipnonpubliclibraryclassmembers不忽略指定类库的public类成员(变量和方法),默认情况下,ProGuard会忽略他们-keepdirectories
[directory_filter] 指定要保持的目录结构,默认情况下会删除所有目录以减小jar的大小。-target version指定java版本号。 版本号可以是1.0,1.1,1.2,1.3,1.4,1.5(或仅5),1.6(或仅6)或1.7(或仅7)中的一个。 默认情况下,类文件的版本号保持不变。 例如,您可能想要将类文件升级到Java 6,通过更改其版本号并对其进行预验证。-forceprocessing
强制处理输入(-injars)jars。即使输出jars是最新的。通过指定的输入,输出和配置文件或者目录的时间戳判断是否最新。
Keep Options 保留选项
-keep [,modifier,...] class_specification指定需要保留的类和类成员(作为公共类库,应该保留所有可公开访问的public方法)-keepclassmembers [,modifier,...] class_specification指定需要保留的类成员:变量或者方法-keepclasseswithmembers
[,modifier,...] class_specification指定保留的类和类成员,条件是所指定的类成员都存在(既在压缩阶段没有被删除的成员,效果和keep差不多)-keepnames class_specification [-keep allowshrinking class_specification 的简写]指定要保留名称的类和类成员,前提是在压缩阶段未被删除。仅用于模糊处理-keepclassmembernames
class_specification [-keepclassmembers allowshrinking class_specification 的简写]指定要保留名称的类成员,前提是在压缩阶段未被删除。仅用于模糊处理-keepclasseswithmembernames class_specification [-keepclasseswithmembers
allowshrinking class_specification 的简写]指定要保留名称的类成员,前提是在压缩阶段后所指定的类成员都存在。仅用于模糊处理-printseeds [filename]指定详尽列出由各种-keep选项匹配的类和类成员。 列表打印到标准输出或给定文件。 该列表可用于验证是否真的找到了预期的类成员,特别是如果您使用通配符。 例如,您可能想要列出所有应用程序或您保存的所有小程序。
Keep选项概述对比(Overview of Keep Options)
保持所指定类、成员
所指定类、成员在压缩阶段没有被删除,才能被保持
类和类成员
-keepnames
-keepclassmembers
-keepclassmembernames
类和类成员(前提是成员都存在)
-keepclasseswithmembers
-keepclasseswithmembernames
建议初学者,如果不确定用那个keep选项就尽量用-keep,这个比较简单且不易使混淆代码出错。
如果只指定类被保留,那么它的成员同样可能会被压缩、优化或者混淆。
如果指定类成员被保留,那么其他代码也有可能会被压缩、优化或者混淆
Keep命令修饰符说明 [,modifier,...] :
可用在keep、keepclassmembers、keepclasseswithmembers命令后面
allowshrinking 指定对象可能会被压缩,即使他们被keep选项保留。如果所指定的对象不是必需的,则他们可能会被删出(在压缩步骤)。反之如果他们是必须的,则他们可能不会被优化或者混淆。
如: 类ClassOneThree在代码中并没有被引用,是一个没有使用的类.-keep class com.dev.demo.one.ClassOneThree*{*;}-keep命令让上ClassOneThree保留下来,但是如果加了allowshrinking 修饰符结果就不一样了,下面这2条命令效果其实是一样的:ClassOneThree在压缩阶段就被删除了,没有被保留,因为它并没有被引用到。-keep,allowshrinking class com.dev.demo.one.ClassOneThree*{*;}
-keepnames class com.dev.demo.one.ClassOneThree*{*;}
allowoptimization 指定对象可能会被优化,即使他们被keep选项保留。所指定对象可能会被改变(优化步骤),但可能不会被混淆或者删除。该修饰符只对实现异常要求有用。
allowobfuscation 指定对象可能会被混淆,即使他们被keep保留。所指定对象可能会被重命名,但可能不会被删除或者优化。该修饰符只对实现异常要求有用。
Shrinking Options 压缩选项
压缩是默认开启的。压缩会删除没有使用的类以及类成员,除了由各种“-keep”选项列出的类和它们直接或间接依赖的类。 在每个优化步骤之后都会进行压缩步骤,因为一些优化后可能会出现更多可以删除的类及类成员-dontshrink关闭压缩-printusage [filename]列出被那些未使用的代码,并可输出到指定文件。仅用于收缩阶段-whyareyoukeeping
class_specification打印指定的类在压缩阶段为什么会保留其类、类成员的详细信息
Optimization Options 优化选项
优化是默认情况下启用的;。所有方法都在字节码级进行优化。-dontoptimize关闭优化-optimizations optimization_filter指定更精细级别的优化,仅用于优化阶段-optimizationpasses n指定优化次数,默认情况下1次。多次可以更进一步优化代码,如果在一次优化后没有改进则优化结束。仅用于优化阶段-assumenosideeffects
class_specification指示没有任何副作用的类方法。优化过程中如果确定这些方法没有被调用或者返回值没有被使用则删除它们。ProGuard会分析出库代码以外的程序代码。如指定System.currentTimeMillis()方法,任何对他的空闲调用将被删除。仅实用与优化。慎用!-allowaccessmodification指示容许修改类和类成员的访问修饰符,这可以改进优化结果。-mergeinterfacesaggressively指示容许合并接口,及时他们的实现类没有实现所有接口方法。这个可以减少类的总数来减小输出的大小。仅实用于优化
Obfuscation Options 混淆
混淆是默认开启的。混淆使类和类成员名称变成短的随机名,被各种“-keep”选项保护的类、类成员除外。对调试有用的内部属性(源文件名称,变量名称,行号)将被删除。-dontobfuscate关闭混淆-printmapping [filename]打印旧名称到重命名的类、类成员的新名称的映射关系,可输出到指定文件。仅实用于混淆处理-applymapping
[filename]指定文件为映射文件,混淆时映射文件中列出的类和类成员接收指定的名称,文件未提及的类和类成员接收新名称。-obfuscationdictionary [filename]指定一个文本文件,其中所有有效字词都用作混淆字段和方法名称。 默认情况下,诸如“a”,“b”等短名称用作混淆名称。 使用模糊字典,您可以指定保留关键字的列表,或具有外来字符的标识符,例如:
忽略空格,标点符号,重复字和#符号后的注释。 注意,模糊字典几乎不改善混淆。 有些编译器可以自动替换它们,并且通过使用更简单的名称再次混淆,可以很简单地撤消该效果。 最有用的是指定类文件中通常已经存在的字符串(例如'Code'),从而减少类文件的大小。 仅适用于混淆处理。-classobfuscationdictionary [filename]指定一个文本文件,其中所有有效词都用作混淆类名。
与-obfuscationdictionary类似。 仅适用于混淆处理。-packageobfuscationdictionary [filename]指定一个文本文件,其中所有有效词都用作混淆包名称。与-obfuscationdictionary类似。 仅适用于混淆处理。 -overloadaggressively深度重载混淆。这个选项可能会使参数和返回类型不同的方法和属性混淆后获得同样的名字,这个选项会使代码更小(且不易理解),仅实用于混淆处理
-useuniqueclassmembernames指定为具有相同名称的类成员分配相同的混淆名称,并为具有不同名称(对于每个给定类成员签名)的类成员分配不同的混淆名称。 没有该选项,更多的类成员可以映射到相同的短名称,如'a','b'等。因此选项稍微增加了生成的代码的大小,但它确保保存的混淆名称映射可以始终 在随后的增量混淆步骤中受到尊重。
-dontusemixedcaseclassnames混淆时不生成大小写混合的类名。 默认情况下,混淆的类名称可以包含大写字符和小写字符的混合。仅适用于混淆处理。
-keeppackagenames [package_filter]指定不混淆给定的包名称。package_filter过滤器是以逗号分隔的包名称列表。包名称可以包含 ?、 * 、
** 通配符。仅适用于混淆处理。
-flattenpackagehierarchy [package_name]将所有重命名后的包移动到给定的包中重新打包,如果没有参数或者空字符串,包将被移动到根包中,此选项进一步混淆包名称,可以使代码跟小更不易理解。仅适用于混淆处理。-repackageclasses [package_name]重新打包所有重命名的类文件,将它们移动到给定包中。
如果包中没有参数或一个空字符串,包被完全删除。 此选项将覆盖-flattenpackagehierarchy选项。 它可以使处理后的代码更小,更不容易理解。 它的已弃用名称是-defaultpackage。 仅适用于混淆处理。-keepattributes [attribute_filter]指定要保留的任何可选属性(注释...)。 可以使用一个或多个-keepattributes指令指定属性。
attribute_filter过滤器是以逗号分隔的属性名称列表。如果代码依赖于注释,则可能需要保留注释。 仅适用于混淆处理。-keepparameternames指定保留参数名称和保留的方法类型。 此选项实际上保留调试属性LocalVariableTable和LocalVariableTypeTable的修剪版本。 它在处理库时很有用。 一些IDE可以使用该信息来帮助使用库的开发人员,例如使用工具提示或自动完成。仅适用于混淆处理。-renamesourcefileattribute
[string]指定要放在类文件的SourceFile属性(和SourceDir属性)中的常量字符串。 请注意,该属性必须以开头存在,因此也必须使用-keepattributes指令明确保留。 例如,您可能希望使已处理的库和应用程序生成有用的混淆堆栈跟踪。仅适用于混淆处理。-adaptclassstrings [class_filter]混淆和类名称对应的字符串常量,如果没有filter则匹配与类名称相对应的所有字符串常量,使用filter则仅匹配与filter匹配的类中的字符串常量。
仅适用于混淆处理。-adaptresourcefilenames [file_filter]根据相应类文件(如果有)的混淆名称指定要重命名的资源文件。 如果没有过滤器,则重命名与类文件相对应的所有资源文件。 使用过滤器,仅重命名匹配的文件。仅适用于混淆处理。-adaptresourcefilecontents [file_filter]指定要更新其内容的资源文件。
根据相应类的混淆名称(如果有)重命名资源文件中提到的任何类名。 如果没有过滤器,所有资源文件的内容都会更新。 使用过滤器,仅更新匹配的文件。 资源文件使用平台的默认字符集进行解析和编写。 您可以通过设置环境变量LANG或Java系统属性file.encoding来更改此默认字符集。 仅适用于混淆处理。
Preverification Options 预验证
预验证默认情况下启动的。能提高虚拟机的加载效率。-dontpreverify关闭预验证-microedition指定已处理的类文件针对Java Micro Edition。 然后,preverifier将添加适当的StackMap属性,这些属性与Java Standard Edition的默认StackMapTable属性不同。 例如,如果您正在处理midlet,则需要此选项。
General Options 一般选项
-verbose混淆过程中打印详细信息,如果异常终止则打印整个堆栈信息-dontnote [class_filter]不打印配置类中可能的错误或遗漏的注释,如类名称中的拼写错误,或者可能有用的缺失选项。 可选class_filter是正则表达式; ProGuard不打印关于具有匹配名称的类的注释。-dontwarn
[class_filter]不对指定的类、包中的不完整的引用发出警告-ignorewarnings忽略警告继续处理-printconfiguration [filename]打印配置信息,到指定文件。包括文件和替换的变量-dump [filename]打印类文件内部结构到指定文件
Class Paths
class_paths 主要用以指示选项(-injars、-outjars、-libraryjars)的输入输出文件路径。ProGuard接受类路径的泛化。由一系列文件和分隔符组成(在Unix上为':',windows上为';');文件的顺序决定着优先级。输入项可包括:
class 文件或者 resource文件;
jar 文件, 可包含上述任意文件;
war文件,可包含上述任意文件;
ear文件, 可包含上述任意文件;
zip文件, 可包含上述任意文件;
包含上面任意文件的目录结构。
直接指定类文件或者资源文件的路径会被忽略,类文件可作为jar文件、war、ear、zip或者目录结构的一个部分。此外,类文件的路径在归档或目录中不应有任何其他目录前缀。
输出项可包括:
jar 文件, 包含了所处理的所有class文件或者资源文件;
war文件,包含上述任意文件;
ear文件, 包含上述任意文件;
zip文件, 包含上述任意文件;
包含上面任意文件的目录结构。
ProGuard 在输出时,会以合理的形式打包输出结果,根据需要重新构建输入文件。将所有类容写入输出目录是ProGuard最便捷的方式,输出目录包含了输入类容完整的重构。包可以是任意复杂的,可以是处理整个应用程序,zip文件中的包及其文档,将再次作为zip文件被处理。
ProGuard容许在文件名中称使用过滤器,它们能相对于完整的文件名过滤条一些文件或者其内容,过滤器位于括号()之间,他能支持5个类型以“;隔开”:
对所有的zip文件名称的过滤;
对所有的ear文件名称的过滤;
对所有的war文件名称的过滤;
对所有的jar文件名称的过滤;
对于所有的class文件名,资源文件名。
括号中的过滤器如果少于5个,则它们将被假设为最后一个过滤器,空的过滤器将被忽视, 格式如下:classpathentry([[[[]]]]filefilter)“[]” 表示其内容是可选的。 rt.jar(java/**.class,javax/**class)
input.jar(!**.gif,images/**)
input.war(lib/**.jar,support/**jar;**.class,**.gif)
一些例子: #所有输出类和资源将被合并到一个jar文件中。
-injars classes
-injars in1.jar
-injars in2.jar
-injars in3.jar
-outjars out.jar
# 将classes目录、in1.jar、in2.jar、in3.jar重构合并到out.jar文件中 #如果想保留输入的结构,则可以输出到目录结构(或者 war、ear、zip)中。
-injars in1.jar
-injars in2.jar
-injars in3.jar
-outjars out
# 输入文件能够保留原来文件名在目录out中构建
通过分组使用-injars和-outjars可以灵活的将jars(wars、ears、zips 或者 directories)合并到不同的jars(wars、ears、zips 或者 directories)中: -injars base_in1.jar
-injars base_in2.jar
-injars base_in3.jar
-outjars base_out.jar
extra_in.jar
-outjars extra_out.jar
# base_in*.jar文件会被合并到base_out.jar文件中,extra_in.jar被合并到extra_out.jar文件中。 #过滤掉了所有images下的所有文件
in.jar(!images/**)
-outjars out.jar
#忽略掉java运行时不相关的类; “&&”表示引用系统变量java home(JAVA_HOME)
-libraryjars &java.home&/lib/rt.jar(java/**,javax/**) -injars
-outjars code_out.jar(**.class)
-outjars resources_out.jar
# **.class文件将被输出到code_out.jar文件中,而所有其他文件将被输出到resources_out.jar文件中。
文件名称(File Names)
ProGuard 接受绝对路径和相对路径作为文件或者路径名。相对路径规则为:
首先相对与基础路径,如果没有设置则:
相对于指定的配置路径,如果没有则:
相对于工作目录。
名称中可包含java 系统配置,用“&&”括号包裹。系统配置将自动替代它们: "&java.home&/lib/rt.jar" 将被解释为 "/usr/local/java/jdk/jre/lib/rt.jar."
"&user.home&" 解释为用户的Home目录
"&user.dir&"解释为当前的工作目录
注意:具有空格和括号的特殊字符名称必须用单引号或者双引号引用,名称列表中的每个文件名必须单独引用。在命令行中引号本身可能需要转义,以避免shell误解
文件过滤符号说明(File Filters)
参考 -adaptresourcefilenames、-adaptresourcefilecontents
“file_filter” 和一般的Filters一样,是可以用逗号(",")分隔的一个过路调的文件列表,只读取匹配到的文件,支持一下通配符:? 匹配单个字符;* 匹配任意多个字符,不包括目录分隔符;** 匹配任意多个字符,不包括目录分隔符;! 表示否(取反)。
java/**.class,javax/**.class //匹配java和javax目录以及其子目录下的所有.class文件
-keep class org.codehaus.jackson.* //保持org.codehaus.jackson下面的类文件,不包括其子包里面类文件
-keep class org.codehaus.jackson.**//保持org.codehaus.jackson下面所有类文件,包括其子包里面类文件
!**.gif,images/** 匹配images目录下面所有文件,但不包括.gif文件
in.jar(!images/**) //指定输入jar包,但移除images目录下面的所有文件
ProGuard 容许配置的很多选项(文件、目录、类、包、属性、优化...等的名称)使用过滤器。可以使用逗号","分隔。 "foo,*bar"匹配foo文件,和所有以bar结尾的名称。
"!foobar,*bar 匹配所有bar结尾名称,foobar除外。
Class Specifications(类的书写规范模版)
类规范是一个类和类成员的书写模版。它主要用在 -keep、-assumenosideeffects选项后面。它使得相应的选项只适应与模版匹配的类和类成员。规范看起来类似Java语法,外加一些通配符。下面看一下类规范格式: [@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
[extends|implements [@annotationtype] classname]
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] &fields& |
(fieldtype fieldname);
[@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] &methods& |
&init&(argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
[@annotationtype] [[!]public|private|protected|static ... ] *;
"[]“方括号表示里面的内容是可选的;"..."省略号表示可以能很多选项;"|"竖线划分了不同的选择"()"普通非粗体括号表示属于同一组的属性
class关键字表示任何类或接口,interface 仅指示接口,enum关键字仅指示枚举;interface,enum前面加一个"!"表示非既是不是接口也不是枚举。
每个classname必须是全名,如:java.lang.String、com.example.ClassName;同时classname还可以是包含下面一些通配符的正则表达式:
? 问号匹配单个字符,但不能是包名分隔符;如:"mypackage.Test?",能匹配"mypackage.Test1"、"mypackage.Test2",但是不能匹配"mypackage.Test12";
* 单个星号能匹配任意多个字符,但除了包分隔符,如:"mypackage.*Test*",能匹配”mypackage.Test、mypackage.YourTestApplication“,但是不能匹配"mypackage.mysubpackage.MyTest";
"mypackage.*"匹配包mypackage下面的类,但不包括它的子包里面的类;** 双星号匹配任意多个字符,包括包名的分隔符;如:"**.Test",匹配了所有Test类,"mypackage.*"匹配了包mypackage下面的所有子类,包括它的子包的类。
extends 、implements 指示一个特定的类,既是表示继承或者是实现了指定的类(接口)的类才符合条件。
@ 指示被注释类型注释了的类或类成员。注释类型与类名一样被指定。
类成员、方法和java语法差不多,方法参数就想javadoc一样,不用包含参数名称。成员、方法也可以包含一些通配符:&init& 匹配了构造方法;&fields& 匹配了成员变量;&methods& 匹配了方法;*
匹配了任意成员、方法;上面这些没有任何返回类型,只有"&init&"有参数列表。成员变量、方法也可以是包含通配符的正则表达式。可以包含以下通配符:? 匹配方法中任何单个字符;* 匹配方法中任何多个字符;类型可以包含一下通配符%
匹配方法中的原始类型("boolean"、"int"...,不包括"void");? 匹配类名中的单个字符;* 匹配类名中的多个字符,不包括包分隔符;** 匹配类名中的多个字符,包括包分隔符;***
匹配原始类型,非原始类型,数组、非数组--- 匹配任意数量和类别的参数。注意:?、*、** 不会匹配原始类型,只有***可以;如:"** get*()"匹配“java.lang.Object getObject()”
不会匹配"float getFloat()" 也不匹配"java.lang.Object[] getObjects()";
构造函数可以用短名(不含包名),或者全名(含包名);java中构造函数可以有参数,但没有返回类型。
可以设置类,类成员访问修饰符(public,private...),它们通常帮助限制通配符类和成员。只能匹配具有指定修饰符的类或者成员。"!"表示非。容许设置多个标识("public static"),但如果它们是冲突的就只能设置一个(如public和private)
ProGuard支持编译器设置的一些修饰符synthetic、bridge、varargs;
下面是一些简单的列子: #保持了类ClassOneOne里面所有public 修饰的成员和方法
-keepclassmembernames class com.dev.demo.one.ClassOneOne {
#保持了ClassOne里面public 修饰的构造函数
-keep class com.dev.demo.ClassOne {
public &init&();
#保持了类ClassTwoTwo里面public修饰的参数为int的构造函数
-keep class com.dev.demo.two.ClassTwoTwo {
public &init&(int);
#保持了类ClassTwoThree 里面public修饰的方法和private修饰的成员变量
-keepclassmember class com.dev.demo.two.ClassTwoThree {
public &methods&;
private &fields&;
#保持了ClassTwoThree的子类以及里面的成员和方法
-keep class * extends com.dev.demo.two.ClassTwoThree {*;}
#保持了前缀为ClassOne的类以及里面的成员和方法
-keepnames class com.dev.demo.one.ClassOne*{*;}
#保持了ClassTwoTwo的内部类ClassTwoTwoInner里面的成员和方法
-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}
相关热门文章<div class="like-count align-center" data-v-2能介绍一下StackMapTable属性的运作原理吗? - 讨论 - 高级语言虚拟机 - ITeye群组
看虚拟机规范没看懂 RednaxelaFX能介绍一下么?
让我打捞起去年回复的一个站内信,看是否对你有帮助:
Re: 反编译后frame same和frame append是什么 RednaxelaFX
引用---------- Forwarded message ----------&
From: RednaxelaFX
Subject: Re: 反编译后frame same和frame append是什么
从Java 6开始,JVM规范有一个更新文档,,里面提到一种新的字节码校验算法,“类型检查”;在此之前是用“类型推导”的算法。为了支持新算法,Class文件从版本50开始添加了一个新的属性表,叫做StackMapTable,里面记录的是一个方法中操作数栈与局部变量区的类型在一些特定位置的状态。这个规范更新将会被整合到JVM规范第三版中,可以预见会在今年内与JDK7一同发布。
您提到的帖子里的frame append、frame same等指的是StackMapTable中的stack map frame。这个frame跟Java方法调用栈的栈帧(stack frame)不是同一个东西,是描述栈帧状态的一种数据结构,只用于Class文件加载时的校验。注意所谓的frame append、frame chop都是对同一个方法的栈帧而言的,并不是说“添加栈帧”“减少栈帧”或者“压入栈帧”“弹出栈帧”,而是指栈帧的内容(局部变量之类)个数上有变化。
把规范中的一小段引用一下:
JSR 202 写道4.8.4 The StackMapTable Attribute
The stack map attribute is a variable-length attribute in the attributes table of a Code attribute. The name of the attribute is StackMapTable. This attribute is used during the process of verification by typechecking (§4.11.1).
A stack map attribute consists of zero or more stack map frames. Each stack map frame specifies (either explicitly or implicitly) a bytecode offset, the verification types (§4.11.1) for the local variables, and the verification types for the operand stack.
The type checker deals with and manipulates the expected types of a method’s local variables and operand stack. Throughout this section, a location refers to either a single local variable or to a single operand stack entry.
We will use the terms stack map frame and type state interchangeably to describe a mapping from locations in the operand stack and local variables of a method to verification types. We will usually use the term stack map frame when such a mapping is provided in the class file, and the term type state when the mapping is inferred by the type checker.
Each stack_map_frame structure specifies the type state at a particular bytecode offset. Each frame type specifies (explicitly or implicitly) a value, offset_delta, that is used to calulate the actual bytecode offset at which it applies. The bytecode offset at which the frame applies is given by adding 1 + offset_delta to the offset of the previous frame, unless the previous frame is the initial frame of the method, in which case the bytecode offset is offset_delta.
每个map记录了一个字节码相对偏移量,一组局部变量的校验类型以及一组操作数栈的校验类型。它的定义如下:
union stack_map_frame {
same_locals_1_stack_item_
same_locals_1_stack_item_frame_
same_frame_
same_frame {
u1 frame_type = SAME;/* 0-63 */
same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
verification_type_info stack[1];
same_locals_1_stack_item_frame_extended {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
u2 offset_
verification_type_info stack[1];
chop_frame {
u1 frame_type=CHOP; /* 248-250 */
u2 offset_
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED;/* 251*/
u2 offset_
append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_
verification_type_info locals[frame_type -251];
full_frame {
u1 frame_type = FULL_FRAME; /* 255 */
u2 offset_
u2 number_of_
verification_type_info locals[number_of_locals];
u2 number_of_stack_
verification_type_info stack[number_of_stack_items];
}
留意到,所谓的“frame same”其实就是指一个stack map frame的第一个字节在0-63的范围内,意义是该frame与前一个frame的局部变量区、操作数栈上对应位置的类型都完全一样。frame append的意义是它对应的字节码偏移量位置上操作数栈为空,局部变量区的对应位置与前一个frame相同,但比前一个额外定义了k个局部变量,k = frame_type - 251;有append就有chop,也就是局部变量个数比前一个frame少的情况。其它frame_type对应的意义有空我写篇blog来说明吧 ^_^
前面提到“一些特定位置”,指的是每个“基本块”的开始位置。一个“基本块”(basic block)就是一个方法中的代码最长的直线型一段段代码序列。“直线型”也就是说代码序列中除了末尾之外不能有控制流(跳转)指令。
一个基本块的开头可以是方法的开头,也可以是某条跳转指令的跳转目标;
一个基本块的结尾可以是方法的末尾,也可以是某条跳转指令(Java中就是goto、if*系列等;invoke*系列的方法调用指令不算在跳转指令中)。如果一个方法代码如下:
public class Foo {
public void foo() {
// basic block 1 start
int i = 0;
int j = 0;
if (i & 0) { // basic block 1 end
// basic block 2 start
int k = 0;
// basic block 2 end
// basic block 3 start
int l = 0;
// basic block 3 end
}
那么可以看到就有3个基本块。不过在Java Class文件里StackMapTable关心的是类型检查,为了进一步压缩这个表的大小,使用的基本块定义比通常的定义要更宽松些:一个条件跳转的直落分支与条件跳转前的代码算在同一个基本块内。于是前面的例子就变成:
public class Foo {
public void foo() {
// basic block 1 start
int i = 0;
int j = 0;
if (i & 0) {
int k = 0;
// basic block 1 end
// basic block 2 start
int l = 0;
// basic block 2 end
这个方法就会有一个StackMapTable属性表,其中有一个stack frame map记录(本来应该是两个,但第一个是隐式的,不记录在属性表里)。
public void foo();
Stack=1, Locals=4, Args_size=1
/* basic block 1 start */
/* basic block 1 end */
/* basic block 2 start */
iconst_0 /* stack frame map 1 refers to here */
/* basic block 2 end */
LocalVariableTable:
StackMapTable: number_of_entries = 1
frame_type = 253 /* append */
offset_delta = 10
locals = [ int, int ]
隐式的第一个基本块的stack frame map是从方法签名计算出来的。这个例子foo是个实例方法,没有显示声明的参数,所以参数个数是1,也就是隐藏参数this。那么在字节码偏移量0的位置上,操作数栈为空,
局部变量区:[ Foo ]
下一个基本块从字节码偏移量10开始。此处变量k已经过了作用域,所以局部变量区的有效内容应该是:
局部变量区:[ Foo, int, int ]
这就比前一个基本块开头处的状态多了2个局部变量,类型分别是[ int, int ],所以就有了上面对应的StackMapTable项了,253 - 251 = 2。
不知道这个解释是否足够解答您的疑问呢?
有了StackMapTable之后,JVM在做Class文件的字节码校验时就可以比较方便的做类型检查。
beneo 写道&&&&
呃…应该先问你是不是合适发的 =_=
引用其它frame_type对应的意义有空我写篇blog来说明吧 ^_ 期待ing
引用chop,也就是局部变量个数比前一个frame少的情况FX不知道能否讲更明白或者举个例子- -减少的时候,按规范的叙述应该是减少多少个对吧?不过比较奇怪为什么就只支持三种(1 2 3)那么更多就只能够多次出现chopframe么??
lwwin 写道引用chop,也就是局部变量个数比前一个frame少的情况
FX不知道能否讲更明白或者举个例子- -减少的时候,按规范的叙述应该是减少多少个对吧?
不过比较奇怪为什么就只支持三种(1 2 3)那么更多就只能够多次出现chopframe么??
不用举例了吧…
chop frame是为常见情况而做的优化编码方式,如果减少的局部变量超过3个,那就用full frame就好了。
JVM层看到的字节码只是一个结果,至于字节码的生成过程是看不到的,所以有时候碰到不懂的地方,可以从javac这一层寻找答案。StackMapTable这个问题可以看看com.sun.tools.javac.jvm.ClassWriter的相关代码(搜一下StackMapTable就有了)
相关资源推荐

我要回帖

更多关于 发动机运作原理 的文章

 

随机推荐