summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/driver/compiler_driver.cc92
-rw-r--r--compiler/oat_test.cc23
-rw-r--r--compiler/oat_writer.cc4
-rw-r--r--runtime/dex_file.cc8
-rw-r--r--runtime/native/dalvik_system_ZygoteHooks.cc46
-rw-r--r--runtime/non_debuggable_classes.cc8
-rw-r--r--runtime/non_debuggable_classes.h7
-rw-r--r--runtime/oat_file_assistant.cc110
-rw-r--r--runtime/oat_file_assistant.h9
-rw-r--r--runtime/openjdkjvmti/ti_redefine.cc256
-rw-r--r--runtime/openjdkjvmti/ti_redefine.h13
-rwxr-xr-xtest/etc/run-test-jar5
-rwxr-xr-xtools/setup-buildbot-device.sh24
13 files changed, 430 insertions, 175 deletions
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 7e91453741..a5e4cb0877 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -2283,7 +2283,7 @@ class InitializeClassVisitor : public CompilationVisitor {
public:
explicit InitializeClassVisitor(const ParallelCompilationManager* manager) : manager_(manager) {}
- virtual void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE {
+ void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) OVERRIDE {
ATRACE_CALL();
jobject jclass_loader = manager_->GetClassLoader();
const DexFile& dex_file = *manager_->GetDexFile();
@@ -2343,23 +2343,32 @@ class InitializeClassVisitor : public CompilationVisitor {
// mode which prevents the GC from visiting objects modified during the transaction.
// Ensure GC is not run so don't access freed objects when aborting transaction.
- ScopedAssertNoThreadSuspension ants("Transaction end");
- runtime->ExitTransactionMode();
+ {
+ ScopedAssertNoThreadSuspension ants("Transaction end");
+ runtime->ExitTransactionMode();
+
+ if (!success) {
+ CHECK(soa.Self()->IsExceptionPending());
+ mirror::Throwable* exception = soa.Self()->GetException();
+ VLOG(compiler) << "Initialization of " << descriptor << " aborted because of "
+ << exception->Dump();
+ std::ostream* file_log = manager_->GetCompiler()->
+ GetCompilerOptions().GetInitFailureOutput();
+ if (file_log != nullptr) {
+ *file_log << descriptor << "\n";
+ *file_log << exception->Dump() << "\n";
+ }
+ soa.Self()->ClearException();
+ transaction.Rollback();
+ CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
+ }
+ }
if (!success) {
- CHECK(soa.Self()->IsExceptionPending());
- mirror::Throwable* exception = soa.Self()->GetException();
- VLOG(compiler) << "Initialization of " << descriptor << " aborted because of "
- << exception->Dump();
- std::ostream* file_log = manager_->GetCompiler()->
- GetCompilerOptions().GetInitFailureOutput();
- if (file_log != nullptr) {
- *file_log << descriptor << "\n";
- *file_log << exception->Dump() << "\n";
- }
- soa.Self()->ClearException();
- transaction.Rollback();
- CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
+ // On failure, still intern strings of static fields and seen in <clinit>, as these
+ // will be created in the zygote. This is separated from the transaction code just
+ // above as we will allocate strings, so must be allowed to suspend.
+ InternStrings(klass, class_loader);
}
}
}
@@ -2375,6 +2384,57 @@ class InitializeClassVisitor : public CompilationVisitor {
}
private:
+ void InternStrings(Handle<mirror::Class> klass, Handle<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(manager_->GetCompiler()->GetCompilerOptions().IsBootImage());
+ DCHECK(klass->IsVerified());
+ DCHECK(!klass->IsInitialized());
+
+ StackHandleScope<1> hs(Thread::Current());
+ Handle<mirror::DexCache> h_dex_cache = hs.NewHandle(klass->GetDexCache());
+ const DexFile* dex_file = manager_->GetDexFile();
+ const DexFile::ClassDef* class_def = klass->GetClassDef();
+ ClassLinker* class_linker = manager_->GetClassLinker();
+
+ // Check encoded final field values for strings and intern.
+ annotations::RuntimeEncodedStaticFieldValueIterator value_it(*dex_file,
+ &h_dex_cache,
+ &class_loader,
+ manager_->GetClassLinker(),
+ *class_def);
+ for ( ; value_it.HasNext(); value_it.Next()) {
+ if (value_it.GetValueType() == annotations::RuntimeEncodedStaticFieldValueIterator::kString) {
+ // Resolve the string. This will intern the string.
+ art::ObjPtr<mirror::String> resolved = class_linker->ResolveString(
+ *dex_file, dex::StringIndex(value_it.GetJavaValue().i), h_dex_cache);
+ CHECK(resolved != nullptr);
+ }
+ }
+
+ // Intern strings seen in <clinit>.
+ ArtMethod* clinit = klass->FindClassInitializer(class_linker->GetImagePointerSize());
+ if (clinit != nullptr) {
+ const DexFile::CodeItem* code_item = clinit->GetCodeItem();
+ DCHECK(code_item != nullptr);
+ const Instruction* inst = Instruction::At(code_item->insns_);
+
+ const uint32_t insns_size = code_item->insns_size_in_code_units_;
+ for (uint32_t dex_pc = 0; dex_pc < insns_size;) {
+ if (inst->Opcode() == Instruction::CONST_STRING) {
+ ObjPtr<mirror::String> s = class_linker->ResolveString(
+ *dex_file, dex::StringIndex(inst->VRegB_21c()), h_dex_cache);
+ CHECK(s != nullptr);
+ } else if (inst->Opcode() == Instruction::CONST_STRING_JUMBO) {
+ ObjPtr<mirror::String> s = class_linker->ResolveString(
+ *dex_file, dex::StringIndex(inst->VRegB_31c()), h_dex_cache);
+ CHECK(s != nullptr);
+ }
+ dex_pc += inst->SizeInCodeUnits();
+ inst = inst->Next();
+ }
+ }
+ }
+
const ParallelCompilationManager* const manager_;
};
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index 66111f6e23..e2233e4bbd 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -265,6 +265,7 @@ class OatTest : public CommonCompilerTest {
void TestDexFileInput(bool verify, bool low_4gb, bool use_profile);
void TestZipFileInput(bool verify);
+ void TestZipFileInputWithEmptyDex();
std::unique_ptr<const InstructionSetFeatures> insn_features_;
std::unique_ptr<QuickCompilerCallbacks> callbacks_;
@@ -821,6 +822,28 @@ TEST_F(OatTest, ZipFileInputCheckVerifier) {
TestZipFileInput(true);
}
+void OatTest::TestZipFileInputWithEmptyDex() {
+ ScratchFile zip_file;
+ ZipBuilder zip_builder(zip_file.GetFile());
+ bool success = zip_builder.AddFile("classes.dex", nullptr, 0);
+ ASSERT_TRUE(success);
+ success = zip_builder.Finish();
+ ASSERT_TRUE(success) << strerror(errno);
+
+ SafeMap<std::string, std::string> key_value_store;
+ key_value_store.Put(OatHeader::kImageLocationKey, "test.art");
+ std::vector<const char*> input_filenames { zip_file.GetFilename().c_str() }; // NOLINT [readability/braces] [4]
+ ScratchFile oat_file, vdex_file(oat_file, ".vdex");
+ std::unique_ptr<ProfileCompilationInfo> profile_compilation_info(new ProfileCompilationInfo());
+ success = WriteElf(vdex_file.GetFile(), oat_file.GetFile(), input_filenames,
+ key_value_store, /*verify*/false, profile_compilation_info.get());
+ ASSERT_FALSE(success);
+}
+
+TEST_F(OatTest, ZipFileInputWithEmptyDex) {
+ TestZipFileInputWithEmptyDex();
+}
+
TEST_F(OatTest, UpdateChecksum) {
InstructionSet insn_set = kX86;
std::string error_msg;
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index c5ec859d1f..8ab44d2c19 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -2260,6 +2260,10 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil
ZipEntry* zip_entry = oat_dex_file->source_.GetZipEntry();
std::unique_ptr<MemMap> mem_map(
zip_entry->ExtractToMemMap(location.c_str(), "classes.dex", &error_msg));
+ if (mem_map == nullptr) {
+ LOG(ERROR) << "Failed to extract dex file to mem map for layout: " << error_msg;
+ return false;
+ }
dex_file = DexFile::Open(location,
zip_entry->GetCrc32(),
std::move(mem_map),
diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc
index b6a2e09719..35e9d5db29 100644
--- a/runtime/dex_file.cc
+++ b/runtime/dex_file.cc
@@ -179,6 +179,14 @@ std::unique_ptr<const DexFile> DexFile::Open(const std::string& location,
std::string* error_msg) {
ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);
CHECK(map.get() != nullptr);
+
+ if (map->Size() < sizeof(DexFile::Header)) {
+ *error_msg = StringPrintf(
+ "DexFile: failed to open dex file '%s' that is too short to have a header",
+ location.c_str());
+ return nullptr;
+ }
+
std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index bb92ca7ae6..836ba81d8e 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -76,24 +76,29 @@ static void EnableDebugger() {
class ClassSet {
public:
- explicit ClassSet(Thread* const self) : hs_(self) {}
+ // The number of classes we reasonably expect to have to look at. Realistically the number is more
+ // ~10 but there is little harm in having some extra.
+ static constexpr int kClassSetCapacity = 100;
+
+ explicit ClassSet(Thread* const self) : self_(self) {
+ self_->GetJniEnv()->PushFrame(kClassSetCapacity);
+ }
+
+ ~ClassSet() {
+ self_->GetJniEnv()->PopFrame();
+ }
void AddClass(ObjPtr<mirror::Class> klass) REQUIRES(Locks::mutator_lock_) {
- for (Handle<mirror::Class> k : class_set_) {
- if (k.Get() == klass.Ptr()) {
- return;
- }
- }
- class_set_.push_back(hs_.NewHandle<mirror::Class>(klass));
+ class_set_.insert(self_->GetJniEnv()->AddLocalReference<jclass>(klass.Ptr()));
}
- const std::vector<Handle<mirror::Class>>& GetClasses() const {
+ const std::unordered_set<jclass>& GetClasses() const {
return class_set_;
}
private:
- VariableSizedHandleScope hs_;
- std::vector<Handle<mirror::Class>> class_set_;
+ Thread* const self_;
+ std::unordered_set<jclass> class_set_;
};
static void DoCollectNonDebuggableCallback(Thread* thread, void* data)
@@ -133,20 +138,15 @@ static void CollectNonDebuggableClasses() REQUIRES(!Locks::mutator_lock_) {
ScopedObjectAccess soa(self);
ClassSet classes(self);
{
- // Drop the mutator lock.
- self->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
- {
- // Get it back with a suspend all.
- ScopedSuspendAll suspend("Checking stacks for non-obsoletable methods!",
- /*long_suspend*/false);
- MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
- runtime->GetThreadList()->ForEach(DoCollectNonDebuggableCallback, &classes);
- }
- // Recover the shared lock before we leave this scope.
- self->TransitionFromSuspendedToRunnable();
+ // Drop the shared mutator lock.
+ ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+ // Get exclusive mutator lock with suspend all.
+ ScopedSuspendAll suspend("Checking stacks for non-obsoletable methods!", /*long_suspend*/false);
+ MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+ runtime->GetThreadList()->ForEach(DoCollectNonDebuggableCallback, &classes);
}
- for (Handle<mirror::Class> klass : classes.GetClasses()) {
- NonDebuggableClasses::AddNonDebuggableClass(klass.Get());
+ for (jclass klass : classes.GetClasses()) {
+ NonDebuggableClasses::AddNonDebuggableClass(klass);
}
}
diff --git a/runtime/non_debuggable_classes.cc b/runtime/non_debuggable_classes.cc
index db121a90e2..829ea65876 100644
--- a/runtime/non_debuggable_classes.cc
+++ b/runtime/non_debuggable_classes.cc
@@ -27,16 +27,16 @@ namespace art {
std::vector<jclass> NonDebuggableClasses::non_debuggable_classes;
-void NonDebuggableClasses::AddNonDebuggableClass(ObjPtr<mirror::Class> klass) {
+void NonDebuggableClasses::AddNonDebuggableClass(jclass klass) {
Thread* self = Thread::Current();
JNIEnvExt* env = self->GetJniEnv();
+ ObjPtr<mirror::Class> mirror_klass(self->DecodeJObject(klass)->AsClass());
for (jclass c : non_debuggable_classes) {
- if (self->DecodeJObject(c)->AsClass() == klass.Ptr()) {
+ if (self->DecodeJObject(c)->AsClass() == mirror_klass.Ptr()) {
return;
}
}
- ScopedLocalRef<jclass> lr(env, env->AddLocalReference<jclass>(klass));
- non_debuggable_classes.push_back(reinterpret_cast<jclass>(env->NewGlobalRef(lr.get())));
+ non_debuggable_classes.push_back(reinterpret_cast<jclass>(env->NewGlobalRef(klass)));
}
} // namespace art
diff --git a/runtime/non_debuggable_classes.h b/runtime/non_debuggable_classes.h
index 0c94dc03a7..e1b563339d 100644
--- a/runtime/non_debuggable_classes.h
+++ b/runtime/non_debuggable_classes.h
@@ -21,21 +21,16 @@
#include "base/mutex.h"
#include "jni.h"
-#include "obj_ptr.h"
namespace art {
-namespace mirror {
-class Class;
-} // namespace mirror
-
struct NonDebuggableClasses {
public:
static const std::vector<jclass>& GetNonDebuggableClasses() {
return non_debuggable_classes;
}
- static void AddNonDebuggableClass(ObjPtr<mirror::Class> klass)
+ static void AddNonDebuggableClass(jclass klass)
REQUIRES_SHARED(Locks::mutator_lock_);
private:
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 5ae2fc51b7..48bf1e72a4 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -430,8 +430,7 @@ OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile&
// starts up.
LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. "
<< "Allow oat file use. This is potentially dangerous.";
- } else if (file.GetOatHeader().GetImageFileLocationOatChecksum()
- != GetCombinedImageChecksum()) {
+ } else if (file.GetOatHeader().GetImageFileLocationOatChecksum() != image_info->oat_checksum) {
VLOG(oat) << "Oat image checksum does not match image checksum.";
return kOatBootImageOutOfDate;
}
@@ -726,68 +725,81 @@ const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() {
return required_dex_checksums_found_ ? &cached_required_dex_checksums_ : nullptr;
}
-const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() {
- if (!image_info_load_attempted_) {
- image_info_load_attempted_ = true;
-
- Runtime* runtime = Runtime::Current();
- std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces();
- if (!image_spaces.empty()) {
- cached_image_info_.location = image_spaces[0]->GetImageLocation();
-
- if (isa_ == kRuntimeISA) {
- const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
- cached_image_info_.oat_checksum = image_header.GetOatChecksum();
- cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
- image_header.GetOatDataBegin());
- cached_image_info_.patch_delta = image_header.GetPatchDelta();
- } else {
- std::string error_msg;
- std::unique_ptr<ImageHeader> image_header(
- gc::space::ImageSpace::ReadImageHeader(cached_image_info_.location.c_str(),
- isa_,
- &error_msg));
- CHECK(image_header != nullptr) << error_msg;
- cached_image_info_.oat_checksum = image_header->GetOatChecksum();
- cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(
- image_header->GetOatDataBegin());
- cached_image_info_.patch_delta = image_header->GetPatchDelta();
- }
- }
- image_info_load_succeeded_ = (!image_spaces.empty());
+// TODO: Use something better than xor for the combined image checksum.
+std::unique_ptr<OatFileAssistant::ImageInfo>
+OatFileAssistant::ImageInfo::GetRuntimeImageInfo(InstructionSet isa, std::string* error_msg) {
+ CHECK(error_msg != nullptr);
- combined_image_checksum_ = CalculateCombinedImageChecksum(isa_);
+ // Use the currently loaded image to determine the image locations for all
+ // the image spaces, regardless of the isa requested. Otherwise we would
+ // need to read from the boot image's oat file to determine the rest of the
+ // image locations in the case of multi-image.
+ Runtime* runtime = Runtime::Current();
+ std::vector<gc::space::ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces();
+ if (image_spaces.empty()) {
+ *error_msg = "There are no boot image spaces";
+ return nullptr;
}
- return image_info_load_succeeded_ ? &cached_image_info_ : nullptr;
-}
-// TODO: Use something better than xor.
-uint32_t OatFileAssistant::CalculateCombinedImageChecksum(InstructionSet isa) {
- uint32_t checksum = 0;
- std::vector<gc::space::ImageSpace*> image_spaces =
- Runtime::Current()->GetHeap()->GetBootImageSpaces();
+ std::unique_ptr<ImageInfo> info(new ImageInfo());
+ info->location = image_spaces[0]->GetImageLocation();
+
+ // TODO: Special casing on isa == kRuntimeISA is presumably motivated by
+ // performance: 'it's faster to use an already loaded image header than read
+ // the image header from disk'. But the loaded image is not necessarily the
+ // same as kRuntimeISA, so this behavior is suspect (b/35659889).
if (isa == kRuntimeISA) {
+ const ImageHeader& image_header = image_spaces[0]->GetImageHeader();
+ info->oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin());
+ info->patch_delta = image_header.GetPatchDelta();
+
+ info->oat_checksum = 0;
for (gc::space::ImageSpace* image_space : image_spaces) {
- checksum ^= image_space->GetImageHeader().GetOatChecksum();
+ info->oat_checksum ^= image_space->GetImageHeader().GetOatChecksum();
}
} else {
+ std::unique_ptr<ImageHeader> image_header(
+ gc::space::ImageSpace::ReadImageHeader(info->location.c_str(), isa, error_msg));
+ if (image_header == nullptr) {
+ return nullptr;
+ }
+ info->oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin());
+ info->patch_delta = image_header->GetPatchDelta();
+
+ info->oat_checksum = 0;
for (gc::space::ImageSpace* image_space : image_spaces) {
std::string location = image_space->GetImageLocation();
- std::string error_msg;
- std::unique_ptr<ImageHeader> image_header(
- gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, &error_msg));
- CHECK(image_header != nullptr) << error_msg;
- checksum ^= image_header->GetOatChecksum();
+ image_header.reset(
+ gc::space::ImageSpace::ReadImageHeader(location.c_str(), isa, error_msg));
+ if (image_header == nullptr) {
+ return nullptr;
+ }
+ info->oat_checksum ^= image_header->GetOatChecksum();
}
}
- return checksum;
+ return info;
}
-uint32_t OatFileAssistant::GetCombinedImageChecksum() {
+const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() {
if (!image_info_load_attempted_) {
- GetImageInfo();
+ image_info_load_attempted_ = true;
+ std::string error_msg;
+ cached_image_info_ = ImageInfo::GetRuntimeImageInfo(isa_, &error_msg);
+ if (cached_image_info_ == nullptr) {
+ LOG(WARNING) << "Unable to get runtime image info: " << error_msg;
+ }
+ }
+ return cached_image_info_.get();
+}
+
+uint32_t OatFileAssistant::CalculateCombinedImageChecksum(InstructionSet isa) {
+ std::string error_msg;
+ std::unique_ptr<ImageInfo> info = ImageInfo::GetRuntimeImageInfo(isa, &error_msg);
+ if (info == nullptr) {
+ LOG(WARNING) << "Unable to get runtime image info for checksum: " << error_msg;
+ return 0;
}
- return combined_image_checksum_;
+ return info->oat_checksum;
}
OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() {
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 3ede29f5e0..eec87f0768 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -284,6 +284,9 @@ class OatFileAssistant {
uintptr_t oat_data_begin = 0;
int32_t patch_delta = 0;
std::string location;
+
+ static std::unique_ptr<ImageInfo> GetRuntimeImageInfo(InstructionSet isa,
+ std::string* error_msg);
};
class OatFileInfo {
@@ -414,8 +417,6 @@ class OatFileAssistant {
// The caller shouldn't clean up or free the returned pointer.
const ImageInfo* GetImageInfo();
- uint32_t GetCombinedImageChecksum();
-
// To implement Lock(), we lock a dummy file where the oat file would go
// (adding ".flock" to the target file name) and retain the lock for the
// remaining lifetime of the OatFileAssistant object.
@@ -445,9 +446,7 @@ class OatFileAssistant {
// TODO: The image info should probably be moved out of the oat file
// assistant to an image file manager.
bool image_info_load_attempted_ = false;
- bool image_info_load_succeeded_ = false;
- ImageInfo cached_image_info_;
- uint32_t combined_image_checksum_ = 0;
+ std::unique_ptr<ImageInfo> cached_image_info_;
DISALLOW_COPY_AND_ASSIGN(OatFileAssistant);
};
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index c4d20c007e..7a69078391 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -779,6 +779,8 @@ bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
CheckSameMethods();
}
+class RedefinitionDataIter;
+
// A wrapper that lets us hold onto the arbitrary sized data needed for redefinitions in a
// reasonably sane way. This adds no fields to the normal ObjectArray. By doing this we can avoid
// having to deal with the fact that we need to hold an arbitrary number of references live.
@@ -802,13 +804,15 @@ class RedefinitionDataHolder {
RedefinitionDataHolder(art::StackHandleScope<1>* hs,
art::Runtime* runtime,
art::Thread* self,
- int32_t num_redefinitions) REQUIRES_SHARED(art::Locks::mutator_lock_) :
+ std::vector<Redefiner::ClassRedefinition>* redefinitions)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) :
arr_(
hs->NewHandle(
art::mirror::ObjectArray<art::mirror::Object>::Alloc(
self,
runtime->GetClassLinker()->GetClassRoot(art::ClassLinker::kObjectArrayClass),
- num_redefinitions * kNumSlots))) {}
+ redefinitions->size() * kNumSlots))),
+ redefinitions_(redefinitions) {}
bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
return arr_.IsNull();
@@ -870,8 +874,27 @@ class RedefinitionDataHolder {
return arr_->GetLength() / kNumSlots;
}
+ std::vector<Redefiner::ClassRedefinition>* GetRedefinitions()
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return redefinitions_;
+ }
+
+ bool operator==(const RedefinitionDataHolder& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return arr_.Get() == other.arr_.Get();
+ }
+
+ bool operator!=(const RedefinitionDataHolder& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return !(*this == other);
+ }
+
+ RedefinitionDataIter begin() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ RedefinitionDataIter end() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
private:
mutable art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_;
+ std::vector<Redefiner::ClassRedefinition>* redefinitions_;
art::mirror::Object* GetSlot(jint klass_index,
DataSlot slot) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -890,8 +913,115 @@ class RedefinitionDataHolder {
DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
};
-bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index,
- const RedefinitionDataHolder& holder) {
+class RedefinitionDataIter {
+ public:
+ RedefinitionDataIter(int32_t idx, RedefinitionDataHolder& holder) : idx_(idx), holder_(holder) {}
+
+ RedefinitionDataIter(const RedefinitionDataIter&) = default;
+ RedefinitionDataIter(RedefinitionDataIter&&) = default;
+ RedefinitionDataIter& operator=(const RedefinitionDataIter&) = default;
+ RedefinitionDataIter& operator=(RedefinitionDataIter&&) = default;
+
+ bool operator==(const RedefinitionDataIter& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return idx_ == other.idx_ && holder_ == other.holder_;
+ }
+
+ bool operator!=(const RedefinitionDataIter& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return !(*this == other);
+ }
+
+ RedefinitionDataIter operator++() { // Value after modification.
+ idx_++;
+ return *this;
+ }
+
+ RedefinitionDataIter operator++(int) {
+ RedefinitionDataIter temp = *this;
+ idx_++;
+ return temp;
+ }
+
+ RedefinitionDataIter operator+(ssize_t delta) const {
+ RedefinitionDataIter temp = *this;
+ temp += delta;
+ return temp;
+ }
+
+ RedefinitionDataIter& operator+=(ssize_t delta) {
+ idx_ += delta;
+ return *this;
+ }
+
+ Redefiner::ClassRedefinition& GetRedefinition() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return (*holder_.GetRedefinitions())[idx_];
+ }
+
+ RedefinitionDataHolder& GetHolder() {
+ return holder_;
+ }
+
+ art::mirror::ClassLoader* GetSourceClassLoader() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetSourceClassLoader(idx_);
+ }
+ art::mirror::Object* GetJavaDexFile() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetJavaDexFile(idx_);
+ }
+ art::mirror::LongArray* GetNewDexFileCookie() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewDexFileCookie(idx_);
+ }
+ art::mirror::DexCache* GetNewDexCache() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewDexCache(idx_);
+ }
+ art::mirror::Class* GetMirrorClass() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetMirrorClass(idx_);
+ }
+ art::mirror::ByteArray* GetOriginalDexFileBytes() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOriginalDexFileBytes(idx_);
+ }
+ int32_t GetIndex() const {
+ return idx_;
+ }
+
+ void SetSourceClassLoader(art::mirror::ClassLoader* loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetSourceClassLoader(idx_, loader);
+ }
+ void SetJavaDexFile(art::mirror::Object* dexfile) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetJavaDexFile(idx_, dexfile);
+ }
+ void SetNewDexFileCookie(art::mirror::LongArray* cookie)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewDexFileCookie(idx_, cookie);
+ }
+ void SetNewDexCache(art::mirror::DexCache* cache) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewDexCache(idx_, cache);
+ }
+ void SetMirrorClass(art::mirror::Class* klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetMirrorClass(idx_, klass);
+ }
+ void SetOriginalDexFileBytes(art::mirror::ByteArray* bytes)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOriginalDexFileBytes(idx_, bytes);
+ }
+
+ private:
+ int32_t idx_;
+ RedefinitionDataHolder& holder_;
+};
+
+RedefinitionDataIter RedefinitionDataHolder::begin() {
+ return RedefinitionDataIter(0, *this);
+}
+
+RedefinitionDataIter RedefinitionDataHolder::end() {
+ return RedefinitionDataIter(Length(), *this);
+}
+
+bool Redefiner::ClassRedefinition::CheckVerification(const RedefinitionDataIter& iter) {
DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
art::StackHandleScope<2> hs(driver_->self_);
std::string error;
@@ -899,7 +1029,7 @@ bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index,
art::verifier::MethodVerifier::FailureKind failure =
art::verifier::MethodVerifier::VerifyClass(driver_->self_,
dex_file_.get(),
- hs.NewHandle(holder.GetNewDexCache(klass_index)),
+ hs.NewHandle(iter.GetNewDexCache()),
hs.NewHandle(GetClassLoader()),
dex_file_->GetClassDef(0), /*class_def*/
nullptr, /*compiler_callbacks*/
@@ -918,21 +1048,20 @@ bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index,
// dexfile. This is so that even if multiple classes with the same classloader are redefined at
// once they are all added to the classloader.
bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
- int32_t klass_index,
art::Handle<art::mirror::ClassLoader> source_class_loader,
art::Handle<art::mirror::Object> dex_file_obj,
- /*out*/RedefinitionDataHolder* holder) {
+ /*out*/RedefinitionDataIter* cur_data) {
art::StackHandleScope<2> hs(driver_->self_);
art::MutableHandle<art::mirror::LongArray> old_cookie(
hs.NewHandle<art::mirror::LongArray>(nullptr));
bool has_older_cookie = false;
// See if we already have a cookie that a previous redefinition got from the same classloader.
- for (int32_t i = 0; i < klass_index; i++) {
- if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) {
+ for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
// Since every instance of this classloader should have the same cookie associated with it we
// can stop looking here.
has_older_cookie = true;
- old_cookie.Assign(holder->GetNewDexFileCookie(i));
+ old_cookie.Assign(old_data.GetNewDexFileCookie());
break;
}
}
@@ -953,14 +1082,14 @@ bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
}
// Save the cookie.
- holder->SetNewDexFileCookie(klass_index, new_cookie.Get());
+ cur_data->SetNewDexFileCookie(new_cookie.Get());
// If there are other copies of this same classloader we need to make sure that we all have the
// same cookie.
if (has_older_cookie) {
- for (int32_t i = 0; i < klass_index; i++) {
+ for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
// We will let the GC take care of the cookie we allocated for this one.
- if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) {
- holder->SetNewDexFileCookie(i, new_cookie.Get());
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+ old_data.SetNewDexFileCookie(new_cookie.Get());
}
}
}
@@ -969,32 +1098,32 @@ bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
}
bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
- int32_t klass_index, /*out*/RedefinitionDataHolder* holder) {
+ /*out*/RedefinitionDataIter* cur_data) {
art::ScopedObjectAccessUnchecked soa(driver_->self_);
art::StackHandleScope<2> hs(driver_->self_);
- holder->SetMirrorClass(klass_index, GetMirrorClass());
+ cur_data->SetMirrorClass(GetMirrorClass());
// This shouldn't allocate
art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
// The bootclasspath is handled specially so it doesn't have a j.l.DexFile.
if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
- holder->SetSourceClassLoader(klass_index, loader.Get());
+ cur_data->SetSourceClassLoader(loader.Get());
art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(
ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
- holder->SetJavaDexFile(klass_index, dex_file_obj.Get());
+ cur_data->SetJavaDexFile(dex_file_obj.Get());
if (dex_file_obj == nullptr) {
RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
return false;
}
// Allocate the new dex file cookie.
- if (!AllocateAndRememberNewDexFileCookie(klass_index, loader, dex_file_obj, holder)) {
+ if (!AllocateAndRememberNewDexFileCookie(loader, dex_file_obj, cur_data)) {
driver_->self_->AssertPendingOOMException();
driver_->self_->ClearException();
RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
return false;
}
}
- holder->SetNewDexCache(klass_index, CreateNewDexCache(loader));
- if (holder->GetNewDexCache(klass_index) == nullptr) {
+ cur_data->SetNewDexCache(CreateNewDexCache(loader));
+ if (cur_data->GetNewDexCache() == nullptr) {
driver_->self_->AssertPendingException();
driver_->self_->ClearException();
RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
@@ -1002,8 +1131,8 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
}
// We won't always need to set this field.
- holder->SetOriginalDexFileBytes(klass_index, AllocateOrGetOriginalDexFileBytes());
- if (holder->GetOriginalDexFileBytes(klass_index) == nullptr) {
+ cur_data->SetOriginalDexFileBytes(AllocateOrGetOriginalDexFileBytes());
+ if (cur_data->GetOriginalDexFileBytes() == nullptr) {
driver_->self_->AssertPendingOOMException();
driver_->self_->ClearException();
RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
@@ -1048,13 +1177,11 @@ bool Redefiner::EnsureAllClassAllocationsFinished() {
}
bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
- int32_t cnt = 0;
- for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
// Allocate the data this redefinition requires.
- if (!redef.FinishRemainingAllocations(cnt, &holder)) {
+ if (!data.GetRedefinition().FinishRemainingAllocations(&data)) {
return false;
}
- cnt++;
}
return true;
}
@@ -1069,22 +1196,39 @@ void Redefiner::ReleaseAllDexFiles() {
}
}
-bool Redefiner::CheckAllClassesAreVerified(const RedefinitionDataHolder& holder) {
- int32_t cnt = 0;
- for (Redefiner::ClassRedefinition& redef : redefinitions_) {
- if (!redef.CheckVerification(cnt, holder)) {
+bool Redefiner::CheckAllClassesAreVerified(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ if (!data.GetRedefinition().CheckVerification(data)) {
return false;
}
- cnt++;
}
return true;
}
+class ScopedDisableConcurrentAndMovingGc {
+ public:
+ ScopedDisableConcurrentAndMovingGc(art::gc::Heap* heap, art::Thread* self)
+ : heap_(heap), self_(self) {
+ if (heap_->IsGcConcurrentAndMoving()) {
+ heap_->IncrementDisableMovingGC(self_);
+ }
+ }
+
+ ~ScopedDisableConcurrentAndMovingGc() {
+ if (heap_->IsGcConcurrentAndMoving()) {
+ heap_->DecrementDisableMovingGC(self_);
+ }
+ }
+ private:
+ art::gc::Heap* heap_;
+ art::Thread* self_;
+};
+
jvmtiError Redefiner::Run() {
art::StackHandleScope<1> hs(self_);
// Allocate an array to hold onto all java temporary objects associated with this redefinition.
// We will let this be collected after the end of this function.
- RedefinitionDataHolder holder(&hs, runtime_, self_, redefinitions_.size());
+ RedefinitionDataHolder holder(&hs, runtime_, self_, &redefinitions_);
if (holder.IsNull()) {
self_->AssertPendingOOMException();
self_->ClearException();
@@ -1107,57 +1251,43 @@ jvmtiError Redefiner::Run() {
// cleaned up by the GC eventually.
return result_;
}
+
// At this point we can no longer fail without corrupting the runtime state.
- int32_t counter = 0;
- for (Redefiner::ClassRedefinition& redef : redefinitions_) {
- if (holder.GetSourceClassLoader(counter) == nullptr) {
- runtime_->GetClassLinker()->AppendToBootClassPath(self_, redef.GetDexFile());
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ if (data.GetSourceClassLoader() == nullptr) {
+ runtime_->GetClassLinker()->AppendToBootClassPath(self_, data.GetRedefinition().GetDexFile());
}
- counter++;
}
UnregisterAllBreakpoints();
+
// Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done
// allocating so no deadlocks.
- art::gc::Heap* heap = runtime_->GetHeap();
- if (heap->IsGcConcurrentAndMoving()) {
- // GC moving objects can cause deadlocks as we are deoptimizing the stack.
- heap->IncrementDisableMovingGC(self_);
- }
+ ScopedDisableConcurrentAndMovingGc sdcamgc(runtime_->GetHeap(), self_);
+
// Do transition to final suspension
// TODO We might want to give this its own suspended state!
// TODO This isn't right. We need to change state without any chance of suspend ideally!
- self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
- runtime_->GetThreadList()->SuspendAll(
- "Final installation of redefined Classes!", /*long_suspend*/true);
- counter = 0;
- for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+ art::ScopedSuspendAll ssa("Final installation of redefined Classes!", /*long_suspend*/true);
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
- if (holder.GetSourceClassLoader(counter) != nullptr) {
- ClassLoaderHelper::UpdateJavaDexFile(holder.GetJavaDexFile(counter),
- holder.GetNewDexFileCookie(counter));
+ ClassRedefinition& redef = data.GetRedefinition();
+ if (data.GetSourceClassLoader() != nullptr) {
+ ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie());
}
- art::mirror::Class* klass = holder.GetMirrorClass(counter);
+ art::mirror::Class* klass = data.GetMirrorClass();
// TODO Rewrite so we don't do a stack walk for each and every class.
redef.FindAndAllocateObsoleteMethods(klass);
- redef.UpdateClass(klass, holder.GetNewDexCache(counter),
- holder.GetOriginalDexFileBytes(counter));
- counter++;
+ redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFileBytes());
}
// TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
// are, force a full-world deoptimization before finishing redefinition. If we don't do this then
// methods that have been jitted prior to the current redefinition being applied might continue
// to use the old versions of the intrinsics!
// TODO Shrink the obsolete method maps if possible?
- // TODO Put this into a scoped thing.
- runtime_->GetThreadList()->ResumeAll();
- // Get back shared mutator lock as expected for return.
- self_->TransitionFromSuspendedToRunnable();
// TODO Do the dex_file release at a more reasonable place. This works but it muddles who really
// owns the DexFile and when ownership is transferred.
ReleaseAllDexFiles();
- if (heap->IsGcConcurrentAndMoving()) {
- heap->DecrementDisableMovingGC(self_);
- }
return OK;
}
@@ -1259,8 +1389,6 @@ bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() {
art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(driver_->self_)));
if (ext == nullptr) {
// No memory. Clear exception (it's not useful) and return error.
- // TODO This doesn't need to be fatal. We could just not support obsolete methods after hitting
- // this case.
driver_->self_->AssertPendingOOMException();
driver_->self_->ClearException();
RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 4e6d05f056..4313a9476e 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -66,6 +66,7 @@
namespace openjdkjvmti {
class RedefinitionDataHolder;
+class RedefinitionDataIter;
// Class that can redefine a single class's methods.
// TODO We should really make this be driven by an outside class so we can do multiple classes at
@@ -143,14 +144,13 @@ class Redefiner {
driver_->RecordFailure(e, class_sig_, err);
}
- bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder)
+ bool FinishRemainingAllocations(/*out*/RedefinitionDataIter* cur_data)
REQUIRES_SHARED(art::Locks::mutator_lock_);
bool AllocateAndRememberNewDexFileCookie(
- int32_t klass_index,
art::Handle<art::mirror::ClassLoader> source_class_loader,
art::Handle<art::mirror::Object> dex_file_obj,
- /*out*/RedefinitionDataHolder* holder)
+ /*out*/RedefinitionDataIter* cur_data)
REQUIRES_SHARED(art::Locks::mutator_lock_);
void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
@@ -161,8 +161,7 @@ class Redefiner {
bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
// Checks that the contained class can be successfully verified.
- bool CheckVerification(int32_t klass_index,
- const RedefinitionDataHolder& holder)
+ bool CheckVerification(const RedefinitionDataIter& holder)
REQUIRES_SHARED(art::Locks::mutator_lock_);
// Preallocates all needed allocations in klass so that we can pause execution safely.
@@ -241,7 +240,7 @@ class Redefiner {
jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
- bool CheckAllClassesAreVerified(const RedefinitionDataHolder& holder)
+ bool CheckAllClassesAreVerified(RedefinitionDataHolder& holder)
REQUIRES_SHARED(art::Locks::mutator_lock_);
bool EnsureAllClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
@@ -255,6 +254,8 @@ class Redefiner {
}
friend struct CallbackCtx;
+ friend class RedefinitionDataHolder;
+ friend class RedefinitionDataIter;
};
} // namespace openjdkjvmti
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 161aa2340d..0ac5481f6d 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -456,8 +456,9 @@ else
FLAGS="$FLAGS -Xnorelocate"
COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xnorelocate"
if [ "$HOST" = "y" ]; then
- # Increase ulimit to 64MB in case we are running hprof test.
- ulimit -S 64000 || exit 1
+ # Increase ulimit to 128MB in case we are running hprof test,
+ # or string append test with art-debug-gc.
+ ulimit -S 128000 || exit 1
fi
fi
diff --git a/tools/setup-buildbot-device.sh b/tools/setup-buildbot-device.sh
index 1e9c763534..7eaaaf9cbd 100755
--- a/tools/setup-buildbot-device.sh
+++ b/tools/setup-buildbot-device.sh
@@ -17,9 +17,33 @@
green='\033[0;32m'
nc='\033[0m'
+# Setup as root, as the next buildbot step (device cleanup) requires it.
+# This is also required to set the date, if needed.
+adb root
+adb wait-for-device
+
+echo -e "${green}Date on host${nc}"
+date
+
echo -e "${green}Date on device${nc}"
adb shell date
+host_seconds_since_epoch=$(date -u +%s)
+device_seconds_since_epoch=$(adb shell date -u +%s)
+
+abs_time_difference_in_seconds=$(expr $host_seconds_since_epoch - $device_seconds_since_epoch)
+if [ $abs_time_difference_in_seconds -lt 0 ]; then
+ abs_time_difference_in_seconds=$(expr 0 - $abs_time_difference_in_seconds)
+fi
+
+seconds_per_hour=3600
+
+# Update date on device if the difference with host is more than one hour.
+if [ $abs_time_difference_in_seconds -gt $seconds_per_hour ]; then
+ echo -e "${green}Update date on device${nc}"
+ adb shell date -u @$host_seconds_since_epoch
+fi
+
echo -e "${green}Turn off selinux${nc}"
adb shell setenforce 0
adb shell getenforce