ART: add hook for framework to set SELinux context
Adds a new zygote hook for system server, nativePostForkSystemServer,
so the process can transition between the system_server_startup and
system_server SELinux domains.
Memory resources for the JIT are allocated in the hook as setting the
SELinux domain with setcon() requires that the process is still single
threaded.
Bug: 66095511
Test: device boots
Test: art/test.py --host --64
Change-Id: Ic840634c5c59906b8d344c2edffafeb9b13a409f
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index ef893ee..e876a1b 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -168,34 +168,26 @@
cumulative_timings_.AddLogger(logger);
}
-Jit::Jit(JitOptions* options) : options_(options),
- cumulative_timings_("JIT timings"),
- memory_use_("Memory used for compilation", 16),
- lock_("JIT memory use lock") {}
+Jit::Jit(JitCodeCache* code_cache, JitOptions* options)
+ : code_cache_(code_cache),
+ options_(options),
+ cumulative_timings_("JIT timings"),
+ memory_use_("Memory used for compilation", 16),
+ lock_("JIT memory use lock") {}
-Jit* Jit::Create(JitOptions* options, std::string* error_msg) {
- DCHECK(options->UseJitCompilation() || options->GetProfileSaverOptions().IsEnabled());
- std::unique_ptr<Jit> jit(new Jit(options));
- if (jit_compiler_handle_ == nullptr && !LoadCompiler(error_msg)) {
+Jit* Jit::Create(JitCodeCache* code_cache, JitOptions* options) {
+ CHECK(jit_compiler_handle_ != nullptr) << "Jit::LoadLibrary() needs to be called first";
+ std::unique_ptr<Jit> jit(new Jit(code_cache, options));
+ if (jit_compiler_handle_ == nullptr) {
return nullptr;
}
- bool code_cache_only_for_profile_data = !options->UseJitCompilation();
- jit->code_cache_.reset(JitCodeCache::Create(
- options->GetCodeCacheInitialCapacity(),
- options->GetCodeCacheMaxCapacity(),
- jit->generate_debug_info_,
- code_cache_only_for_profile_data,
- error_msg));
- if (jit->GetCodeCache() == nullptr) {
- return nullptr;
- }
+
VLOG(jit) << "JIT created with initial_capacity="
<< PrettySize(options->GetCodeCacheInitialCapacity())
<< ", max_capacity=" << PrettySize(options->GetCodeCacheMaxCapacity())
<< ", compile_threshold=" << options->GetCompileThreshold()
<< ", profile_saver_options=" << options->GetProfileSaverOptions();
-
jit->CreateThreadPool();
// Notify native debugger about the classes already loaded before the creation of the jit.
@@ -203,7 +195,7 @@
return jit.release();
}
-bool Jit::LoadCompilerLibrary(std::string* error_msg) {
+bool Jit::BindCompilerMethods(std::string* error_msg) {
jit_library_handle_ = dlopen(
kIsDebugBuild ? "libartd-compiler.so" : "libart-compiler.so", RTLD_NOW);
if (jit_library_handle_ == nullptr) {
@@ -243,7 +235,7 @@
}
bool Jit::LoadCompiler(std::string* error_msg) {
- if (jit_library_handle_ == nullptr && !LoadCompilerLibrary(error_msg)) {
+ if (jit_library_handle_ == nullptr && !BindCompilerMethods(error_msg)) {
return false;
}
bool will_generate_debug_symbols = false;
@@ -308,6 +300,11 @@
return success;
}
+bool Jit::ShouldGenerateDebugInfo() {
+ CHECK(CompilerIsLoaded());
+ return generate_debug_info_;
+}
+
void Jit::CreateThreadPool() {
// There is a DCHECK in the 'AddSamples' method to ensure the tread pool
// is not null when we instrument.
@@ -347,10 +344,7 @@
void Jit::StartProfileSaver(const std::string& filename,
const std::vector<std::string>& code_paths) {
if (options_->GetSaveProfilingInfo()) {
- ProfileSaver::Start(options_->GetProfileSaverOptions(),
- filename,
- code_cache_.get(),
- code_paths);
+ ProfileSaver::Start(options_->GetProfileSaverOptions(), filename, code_cache_, code_paths);
}
}
@@ -391,7 +385,7 @@
return;
}
jit::Jit* jit = Runtime::Current()->GetJit();
- if (jit->generate_debug_info_) {
+ if (generate_debug_info_) {
DCHECK(jit->jit_types_loaded_ != nullptr);
jit->jit_types_loaded_(jit->jit_compiler_handle_, &type, 1);
}
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index edaf348..b0ea19b 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -100,6 +100,10 @@
return use_jit_compilation_;
}
+ bool RWXMemoryAllowed() const {
+ return rwx_memory_allowed_;
+ }
+
void SetUseJitCompilation(bool b) {
use_jit_compilation_ = b;
}
@@ -121,6 +125,10 @@
compile_threshold_ = 0;
}
+ void SetRWXMemoryAllowed(bool rwx_allowed) {
+ rwx_memory_allowed_ = rwx_allowed;
+ }
+
private:
bool use_jit_compilation_;
size_t code_cache_initial_capacity_;
@@ -132,6 +140,7 @@
uint16_t invoke_transition_weight_;
bool dump_info_on_shutdown_;
int thread_pool_pthread_priority_;
+ bool rwx_memory_allowed_;
ProfileSaverOptions profile_saver_options_;
JitOptions()
@@ -144,7 +153,8 @@
priority_thread_weight_(0),
invoke_transition_weight_(0),
dump_info_on_shutdown_(false),
- thread_pool_pthread_priority_(kJitPoolThreadPthreadDefaultPriority) {}
+ thread_pool_pthread_priority_(kJitPoolThreadPthreadDefaultPriority),
+ rwx_memory_allowed_(true) {}
DISALLOW_COPY_AND_ASSIGN(JitOptions);
};
@@ -157,20 +167,24 @@
static constexpr int16_t kJitRecheckOSRThreshold = 100;
virtual ~Jit();
- static Jit* Create(JitOptions* options, std::string* error_msg);
+
+ // Create JIT itself.
+ static Jit* Create(JitCodeCache* code_cache, JitOptions* options);
+
bool CompileMethod(ArtMethod* method, Thread* self, bool osr)
REQUIRES_SHARED(Locks::mutator_lock_);
- void CreateThreadPool();
const JitCodeCache* GetCodeCache() const {
- return code_cache_.get();
+ return code_cache_;
}
JitCodeCache* GetCodeCache() {
- return code_cache_.get();
+ return code_cache_;
}
+ void CreateThreadPool();
void DeleteThreadPool();
+
// Dump interesting info: #methods compiled, code vs data size, compile / verify cumulative
// loggers.
void DumpInfo(std::ostream& os) REQUIRES(!lock_);
@@ -268,7 +282,13 @@
JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_);
- static bool LoadCompilerLibrary(std::string* error_msg);
+ // Load and initialize compiler.
+ static bool LoadCompiler(std::string* error_msg);
+
+ static bool CompilerIsLoaded() { return jit_compiler_handle_ != nullptr; }
+
+ // Return whether debug info should be generated. Requires LoadCompiler() to have been called.
+ static bool ShouldGenerateDebugInfo();
ThreadPool* GetThreadPool() const {
return thread_pool_.get();
@@ -281,9 +301,9 @@
void Start();
private:
- explicit Jit(JitOptions* options);
+ Jit(JitCodeCache* code_cache, JitOptions* options);
- static bool LoadCompiler(std::string* error_msg);
+ static bool BindCompilerMethods(std::string* error_msg);
// JIT compiler
static void* jit_library_handle_;
@@ -296,9 +316,10 @@
// We make this static to simplify the interaction with libart-compiler.so.
static bool generate_debug_info_;
+ // JIT resources owned by runtime.
+ jit::JitCodeCache* const code_cache_;
const JitOptions* const options_;
- std::unique_ptr<jit::JitCodeCache> code_cache_;
std::unique_ptr<ThreadPool> thread_pool_;
// Performance monitoring.
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 082b311..a15a9be 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -185,18 +185,12 @@
JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
size_t max_capacity,
- bool generate_debug_info,
bool used_only_for_profile_data,
+ bool rwx_memory_allowed,
std::string* error_msg) {
ScopedTrace trace(__PRETTY_FUNCTION__);
CHECK_GE(max_capacity, initial_capacity);
- // With 'perf', we want a 1-1 mapping between an address and a method.
- // We aren't able to keep method pointers live during the instrumentation method entry trampoline
- // so we will just disable jit-gc if we are doing that.
- bool garbage_collect_code = !generate_debug_info &&
- !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled();
-
// We need to have 32 bit offsets from method headers in code cache which point to things
// in the data cache. If the maps are more than 4G apart, having multiple maps wouldn't work.
// Ensure we're below 1 GB to be safe.
@@ -224,8 +218,15 @@
// Bionic supports memfd_create, but the call may fail on older kernels.
mem_fd = unique_fd(art::memfd_create("/jit-cache", /* flags= */ 0));
if (mem_fd.get() < 0) {
- VLOG(jit) << "Failed to initialize dual view JIT. memfd_create() error: "
- << strerror(errno);
+ std::ostringstream oss;
+ oss << "Failed to initialize dual view JIT. memfd_create() error: " << strerror(errno);
+ if (!rwx_memory_allowed) {
+ // Without using RWX page permissions, the JIT can not fallback to single mapping as it
+ // requires tranitioning the code pages to RWX for updates.
+ *error_msg = oss.str();
+ return nullptr;
+ }
+ VLOG(jit) << oss.str();
}
if (mem_fd.get() >= 0 && ftruncate(mem_fd, max_capacity) != 0) {
@@ -350,8 +351,14 @@
"jit-code-cache-rw",
&error_str);
if (!non_exec_pages.IsValid()) {
- // Log and continue as single view JIT.
- VLOG(jit) << "Failed to map non-executable view of JIT code cache";
+ static const char* kFailedNxView = "Failed to map non-executable view of JIT code cache";
+ if (rwx_memory_allowed) {
+ // Log and continue as single view JIT (requires RWX memory).
+ VLOG(jit) << kFailedNxView;
+ } else {
+ *error_msg = kFailedNxView;
+ return nullptr;
+ }
}
}
} else {
@@ -369,8 +376,7 @@
std::move(non_exec_pages),
initial_data_capacity,
initial_exec_capacity,
- max_capacity,
- garbage_collect_code);
+ max_capacity);
}
JitCodeCache::JitCodeCache(MemMap&& data_pages,
@@ -378,8 +384,7 @@
MemMap&& non_exec_pages,
size_t initial_data_capacity,
size_t initial_exec_capacity,
- size_t max_capacity,
- bool garbage_collect_code)
+ size_t max_capacity)
: lock_("Jit code cache", kJitCodeCacheLock),
lock_cond_("Jit code cache condition variable", lock_),
collection_in_progress_(false),
@@ -391,7 +396,7 @@
data_end_(initial_data_capacity),
exec_end_(initial_exec_capacity),
last_collection_increased_code_cache_(false),
- garbage_collect_code_(garbage_collect_code),
+ garbage_collect_code_(true),
used_memory_for_data_(0),
used_memory_for_code_(0),
number_of_compilations_(0),
@@ -431,6 +436,12 @@
SetFootprintLimit(current_capacity_);
}
+ // With 'perf', we want a 1-1 mapping between an address and a method.
+ // We aren't able to keep method pointers live during the instrumentation method entry trampoline
+ // so we will just disable jit-gc if we are doing that.
+ garbage_collect_code_ = !Jit::ShouldGenerateDebugInfo() &&
+ !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled();
+
VLOG(jit) << "Created jit code cache: initial data size="
<< PrettySize(initial_data_capacity)
<< ", initial code size="
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 76ad8db..126fd44 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -92,8 +92,8 @@
// in the out arg error_msg.
static JitCodeCache* Create(size_t initial_capacity,
size_t max_capacity,
- bool generate_debug_info,
bool used_only_for_profile_data,
+ bool rwx_memory_allowed,
std::string* error_msg);
~JitCodeCache();
@@ -261,8 +261,8 @@
void MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method)
REQUIRES(!lock_) REQUIRES(Locks::mutator_lock_);
- // Dynamically change whether we want to garbage collect code. Should only be used
- // by tests.
+ // Dynamically change whether we want to garbage collect code. Should only be used during JIT
+ // initialization or by tests.
void SetGarbageCollectCode(bool value) {
garbage_collect_code_ = value;
}
@@ -284,8 +284,7 @@
MemMap&& non_exec_pages,
size_t initial_data_capacity,
size_t initial_exec_capacity,
- size_t max_capacity,
- bool garbage_collect_code);
+ size_t max_capacity);
// Internal version of 'CommitCode' that will not retry if the
// allocation fails. Return null if the allocation fails.
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index f5c0704..4d3ad62 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -282,6 +282,15 @@
return reinterpret_cast<jlong>(ThreadForEnv(env));
}
+static void ZygoteHooks_nativePostForkSystemServer(JNIEnv* env ATTRIBUTE_UNUSED,
+ jclass klass ATTRIBUTE_UNUSED) {
+ // This JIT code cache for system server is created whilst the runtime is still single threaded.
+ // System server has a window where it can create executable pages for this purpose, but this is
+ // turned off after this hook. Consequently, the only JIT mode supported is the dual-view JIT
+ // where one mapping is R->RW and the other is RX. Single view requires RX->RWX->RX.
+ Runtime::Current()->CreateJitCodeCache(/*rwx_memory_allowed=*/false);
+}
+
static void ZygoteHooks_nativePostForkChild(JNIEnv* env,
jclass,
jlong token,
@@ -419,6 +428,7 @@
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(ZygoteHooks, nativePreFork, "()J"),
+ NATIVE_METHOD(ZygoteHooks, nativePostForkSystemServer, "()V"),
NATIVE_METHOD(ZygoteHooks, nativePostForkChild, "(JIZZLjava/lang/String;)V"),
NATIVE_METHOD(ZygoteHooks, startZygoteNoThreadCreation, "()V"),
NATIVE_METHOD(ZygoteHooks, stopZygoteNoThreadCreation, "()V"),
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index c312126..34b84f5 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -407,6 +407,7 @@
if (jit_ != nullptr) {
VLOG(jit) << "Deleting jit";
jit_.reset(nullptr);
+ jit_code_cache_.reset(nullptr);
}
// Shutdown the fault manager if it was initialized.
@@ -785,17 +786,15 @@
// TODO(calin): We use the JIT class as a proxy for JIT compilation and for
// recoding profiles. Maybe we should consider changing the name to be more clear it's
// not only about compiling. b/28295073.
- if (jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo()) {
+ if (!safe_mode_ && (jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo())) {
+ // Try to load compiler pre zygote to reduce PSS. b/27744947
std::string error_msg;
- if (!IsZygote()) {
- // If we are the zygote then we need to wait until after forking to create the code cache
- // due to SELinux restrictions on r/w/x memory regions.
- CreateJit();
- } else if (jit_options_->UseJitCompilation()) {
- if (!jit::Jit::LoadCompilerLibrary(&error_msg)) {
- // Try to load compiler pre zygote to reduce PSS. b/27744947
- LOG(WARNING) << "Failed to load JIT compiler with error " << error_msg;
- }
+ if (!jit::Jit::LoadCompiler(&error_msg)) {
+ LOG(WARNING) << "Failed to load JIT compiler with error " << error_msg;
+ } else if (!IsZygote()) {
+ // If we are the zygote then we need to wait until after forking to create the code cache
+ // due to SELinux restrictions on r/w/x memory regions.
+ CreateJitCodeCache(/*rwx_memory_allowed=*/true);
}
}
@@ -892,29 +891,26 @@
}
}
- // Create the thread pools.
- heap_->CreateThreadPool();
- // Reset the gc performance data at zygote fork so that the GCs
- // before fork aren't attributed to an app.
- heap_->ResetGcPerformanceInfo();
-
- // We may want to collect profiling samples for system server, but we never want to JIT there.
if (is_system_server) {
- jit_options_->SetUseJitCompilation(false);
jit_options_->SetSaveProfilingInfo(profile_system_server);
if (profile_system_server) {
jit_options_->SetWaitForJitNotificationsToSaveProfile(false);
VLOG(profiler) << "Enabling system server profiles";
}
}
- if (!safe_mode_ &&
- (jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo()) &&
- jit_ == nullptr) {
+
+ if (jit_ == nullptr) {
// Note that when running ART standalone (not zygote, nor zygote fork),
// the jit may have already been created.
CreateJit();
}
+ // Create the thread pools.
+ heap_->CreateThreadPool();
+ // Reset the gc performance data at zygote fork so that the GCs
+ // before fork aren't attributed to an app.
+ heap_->ResetGcPerformanceInfo();
+
StartSignalCatcher();
// Start the JDWP thread. If the command-line debugger flags specified "suspend=y",
@@ -2484,18 +2480,43 @@
argv->push_back(feature_string);
}
-void Runtime::CreateJit() {
- CHECK(!IsAotCompiler());
+void Runtime::CreateJitCodeCache(bool rwx_memory_allowed) {
if (kIsDebugBuild && GetInstrumentation()->IsForcedInterpretOnly()) {
DCHECK(!jit_options_->UseJitCompilation());
}
- std::string error_msg;
- jit::Jit* jit = jit::Jit::Create(jit_options_.get(), &error_msg);
- DoAndMaybeSwitchInterpreter([=](){ jit_.reset(jit); });
- if (jit_.get() == nullptr) {
- LOG(WARNING) << "Failed to create JIT " << error_msg;
+
+ if (safe_mode_ || (!jit_options_->UseJitCompilation() && !jit_options_->GetSaveProfilingInfo())) {
return;
}
+
+ // SystemServer has execmem blocked by SELinux so can not use RWX page permissions after the
+ // cache initialized.
+ jit_options_->SetRWXMemoryAllowed(rwx_memory_allowed);
+
+ std::string error_msg;
+ bool profiling_only = !jit_options_->UseJitCompilation();
+ jit_code_cache_.reset(jit::JitCodeCache::Create(jit_options_->GetCodeCacheInitialCapacity(),
+ jit_options_->GetCodeCacheMaxCapacity(),
+ profiling_only,
+ jit_options_->RWXMemoryAllowed(),
+ &error_msg));
+ if (jit_code_cache_.get() == nullptr) {
+ LOG(WARNING) << "Failed to create JIT Code Cache: " << error_msg;
+ }
+}
+
+void Runtime::CreateJit() {
+ if (jit_code_cache_.get() == nullptr) {
+ return;
+ }
+
+ jit::Jit* jit = jit::Jit::Create(jit_code_cache_.get(), jit_options_.get());
+ DoAndMaybeSwitchInterpreter([=](){ jit_.reset(jit); });
+ if (jit == nullptr) {
+ LOG(WARNING) << "Failed to allocate JIT";
+ // Release JIT code cache resources (several MB of memory).
+ jit_code_cache_.reset(nullptr);
+ }
}
bool Runtime::CanRelocate() const {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index e27c87d..4fb0d2e 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -56,6 +56,7 @@
namespace jit {
class Jit;
+class JitCodeCache;
class JitOptions;
} // namespace jit
@@ -614,6 +615,8 @@
return (experimental_flags_ & flags) != ExperimentalFlags::kNone;
}
+ void CreateJitCodeCache(bool rwx_memory_allowed);
+
// Create the JIT and instrumentation and code cache.
void CreateJit();
@@ -906,6 +909,7 @@
std::unique_ptr<JavaVMExt> java_vm_;
std::unique_ptr<jit::Jit> jit_;
+ std::unique_ptr<jit::JitCodeCache> jit_code_cache_;
std::unique_ptr<jit::JitOptions> jit_options_;
// Fault message, printed when we get a SIGSEGV.