The bytecode verifier in the Dalvik VM attempts to provide the same sorts of checks and guarantees that other popular virtual machines do. We perform generally the same set of checks as are described in _The Java Virtual Machine Specification, Second Edition_, including the updates planned for the Third Edition.
Verification can be enabled for all classes, disabled for all, or enabled only for "remote" (non-bootstrap) classes. It should be performed for any class that will be processed with the DEX optimizer, and in fact the default VM behavior is to only optimize verified classes.
The verification process adds additional time to the build and to the installation of new applications. It's fairly quick for app-sized DEX files, but rather slow for the big "core" and "framework" files. Why do it all, when our system relies on UNIX processes for security?
There are a few checks that the Dalvik bytecode verifier does not perform, because they're not relevant. For example:
jsr
and ret
do not apply,
because Dalvik doesn't support subroutines.
new-instance
instruction.
This solves the same problem -- trickery potentially allowing
uninitialized objects to slip past the verifier -- without unduly
limiting branches.
move-exception
instruction can only appear as
the first instruction in an exception handler.
move-result*
instructions can only appear
immediately after an appropriate invoke-*
or filled-new-array
instruction.
The Dalvik verifier is more restrictive than other VMs in one area:
type safety on sub-32-bit integer widths. These additional restrictions
should make it impossible to, say, pass a value outside the range
[-128, 127] to a function that takes a byte
as an argument.
When the verifier rejects a class, it always throws a VerifyError. This is different in some cases from other implementations. For example, if a class attempts to perform an illegal access on a field, the expected behavior is to receive an IllegalAccessError at runtime the first time the field is actually accessed. The Dalvik verifier will reject the entire class immediately.
It's difficult to throw the error on first use in Dalvik. Possible ways to implement this behavior include:
Other implementations are possible, but they all involve allocating some amount of additional memory or spending additional cycles on non-DEX-optimized instructions. We don't want to throw an IllegalAccessError at verification time, since that would indicate that access to the class being verified was illegal.
One approach that might be worth pursuing: for situations like illegal accesses, the verifier makes an in-RAM private copy of the method, and alters the instructions there. The class object is altered to point at the new copy of the instructions. This requires minimal memory overhead and provides a better experience for developers.
The VerifyError is accompanied by detailed, if somewhat cryptic, information in the log file. From this it's possible to determine the exact instruction that failed, and the reason for the failure. We can also constructor the VerifyError with an IllegalAccessError passed in as the cause.
Copyright © 2008 The Android Open Source Project