Fix oat_hook for recent Android versions (#387)

We used to hook functions `OatHeader::GetKeyValueStore` and `OatHeader::GetKeyValueStoreSize` to clean the `dex2oat` trace introduced by LSPosed. However, in recent versions of Android, these two functions are no longer exported. Moreover, the `OatHeader` structure has changed, which now sets certain entries to have fixed length by padding zeros.

To address these two changes, we hook `OatHeader::ComputeChecksum` as an entrypoint (fallback), and then employ the header file of `OatHeader` copied from AOSP to precisely locate its fields. Using this strategy, we modify `key_value_store` in memory, depending on whether the entry `dex2oat-cmdline` is padded.
This commit is contained in:
JingMatrix 2025-08-09 07:36:44 +02:00 committed by GitHub
parent e75b6007b4
commit 6703b45350
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 512 additions and 56 deletions

View File

@ -1,12 +1,14 @@
cmake_minimum_required(VERSION 3.10)
project(dex2oat)
add_executable(dex2oat dex2oat.c)
add_library(oat_hook SHARED oat_hook.cpp)
add_executable(dex2oat dex2oat.cpp)
add_library(oat_hook SHARED oat_hook.cpp oat.cpp)
OPTION(LSPLT_BUILD_SHARED OFF)
add_subdirectory(${EXTERNAL_ROOT}/lsplt/lsplt/src/main/jni external)
target_include_directories(oat_hook PUBLIC include)
target_include_directories(dex2oat PUBLIC include)
target_link_libraries(dex2oat log)
target_link_libraries(oat_hook log lsplt_static)

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stddef.h> // for size_t
#include <unistd.h> // for TEMP_FAILURE_RETRY
#include <utility>
// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't.
#ifndef TEMP_FAILURE_RETRY
#define TEMP_FAILURE_RETRY(exp) \
({ \
decltype(exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; \
})
#endif
// A macro to disallow the copy constructor and operator= functions
// This must be placed in the private: declarations for a class.
//
// For disallowing only assign or copy, delete the relevant operator or
// constructor, for example:
// void operator=(const TypeName&) = delete;
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
// semantically, one should either use disallow both or neither. Try to
// avoid these in new code.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName &) = delete; \
void operator=(const TypeName &) = delete
// A macro to disallow all the implicit constructors, namely the
// default constructor, copy constructor and operator= functions.
//
// This should be used in the private: declarations for a class
// that wants to prevent anyone from instantiating it. This is
// especially useful for classes containing only static methods.
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
DISALLOW_COPY_AND_ASSIGN(TypeName)
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
//
// One caveat is that arraysize() doesn't accept any array of an
// anonymous type or a type defined inside a function. In these rare
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
// due to a limitation in C++'s template system. The limitation might
// eventually be removed, but it hasn't happened yet.
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N]; // NOLINT(readability/casting)
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
#define SIZEOF_MEMBER(t, f) sizeof(std::declval<t>().f)
// Changing this definition will cause you a lot of pain. A majority of
// vendor code defines LIKELY and UNLIKELY this way, and includes
// this header through an indirect path.
#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
#define WARN_UNUSED __attribute__((warn_unused_result))
// A deprecated function to call to create a false use of the parameter, for
// example:
// int foo(int x) { UNUSED(x); return 10; }
// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED.
template <typename... T>
void UNUSED(const T &...) {}
// An attribute to place on a parameter to a function, for example:
// int foo(int x ATTRIBUTE_UNUSED) { return 10; }
// to avoid compiler warnings.
#define ATTRIBUTE_UNUSED __attribute__((__unused__))
// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through
// between switch labels:
// switch (x) {
// case 40:
// case 41:
// if (truth_is_out_there) {
// ++x;
// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in
// // comments.
// } else {
// return x;
// }
// case 42:
// ...
//
// As shown in the example above, the FALLTHROUGH_INTENDED macro should be
// followed by a semicolon. It is designed to mimic control-flow statements
// like 'break;', so it can be placed in most places where 'break;' can, but
// only if there are no statements on the execution path between it and the
// next switch label.
//
// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to
// [[clang::fallthrough]] attribute, which is analysed when performing switch
// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang
// documentation on language extensions for details:
// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough
//
// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no
// effect on diagnostics.
//
// In either case this macro has no effect on runtime behavior and performance
// of code.
#ifndef FALLTHROUGH_INTENDED
#define FALLTHROUGH_INTENDED [[fallthrough]] // NOLINT
#endif
// Current ABI string
#if defined(__arm__)
#define ABI_STRING "arm"
#elif defined(__aarch64__)
#define ABI_STRING "arm64"
#elif defined(__i386__)
#define ABI_STRING "x86"
#elif defined(__riscv)
#define ABI_STRING "riscv64"
#elif defined(__x86_64__)
#define ABI_STRING "x86_64"
#endif

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_LIBARTBASE_BASE_MACROS_H_
#define ART_LIBARTBASE_BASE_MACROS_H_
#include <stddef.h> // for size_t
#include <unistd.h> // for TEMP_FAILURE_RETRY
// Declare a friend relationship in a class with a test. Used rather that FRIEND_TEST to avoid
// globally importing gtest/gtest.h into the main ART header files.
#define ART_FRIEND_TEST(test_set_name, individual_test) \
friend class test_set_name##_##individual_test##_Test
// Declare a friend relationship in a class with a typed test.
#define ART_FRIEND_TYPED_TEST(test_set_name, individual_test) \
template <typename T> \
ART_FRIEND_TEST(test_set_name, individual_test)
// Shorthand for formatting with compile time checking of the format string
#define ART_FORMAT(str, ...) ::fmt::format(FMT_STRING(str), __VA_ARGS__)
// A macro to disallow new and delete operators for a class. It goes in the private: declarations.
// NOTE: Providing placement new (and matching delete) for constructing container elements.
#define DISALLOW_ALLOCATION() \
public: \
NO_RETURN ALWAYS_INLINE void operator delete(void*, size_t) { UNREACHABLE(); } \
ALWAYS_INLINE void* operator new(size_t, void* ptr) noexcept { return ptr; } \
ALWAYS_INLINE void operator delete(void*, void*) noexcept {} \
\
private: \
void* operator new(size_t) = delete // NOLINT
// offsetof is not defined by the spec on types with non-standard layout,
// however it is implemented by compilers in practice.
// (note that reinterpret_cast is not valid constexpr)
//
// Alternative approach would be something like:
// #define OFFSETOF_HELPER(t, f) \
// (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u))
// #define OFFSETOF_MEMBER(t, f) \
// (__builtin_constant_p(OFFSETOF_HELPER(t,f)) ? OFFSETOF_HELPER(t,f) : OFFSETOF_HELPER(t,f))
#define OFFSETOF_MEMBER(t, f) offsetof(t, f)
#define OFFSETOF_MEMBERPTR(t, f) \
(reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - \
static_cast<uintptr_t>(16)) // NOLINT
#define ALIGNED(x) __attribute__((__aligned__(x)))
#define PACKED(x) __attribute__((__aligned__(x), __packed__))
// Stringify the argument.
#define QUOTE(x) #x
#define STRINGIFY(x) QUOTE(x)
// Append tokens after evaluating.
#define APPEND_TOKENS_AFTER_EVAL_2(a, b) a##b
#define APPEND_TOKENS_AFTER_EVAL(a, b) APPEND_TOKENS_AFTER_EVAL_2(a, b)
#ifndef NDEBUG
#define ALWAYS_INLINE
#define FLATTEN
#else
#define ALWAYS_INLINE __attribute__((always_inline))
#define FLATTEN __attribute__((flatten))
#endif
#define NO_STACK_PROTECTOR __attribute__((no_stack_protector))
// clang doesn't like attributes on lambda functions. It would be nice to say:
// #define ALWAYS_INLINE_LAMBDA ALWAYS_INLINE
#define ALWAYS_INLINE_LAMBDA
#define NO_INLINE __attribute__((noinline))
#if defined(__APPLE__)
#define HOT_ATTR
#define COLD_ATTR
#else
#define HOT_ATTR __attribute__((hot))
#define COLD_ATTR __attribute__((cold))
#endif
#define PURE __attribute__((__pure__))
// Define that a position within code is unreachable, for example:
// int foo () { LOG(FATAL) << "Don't call me"; UNREACHABLE(); }
// without the UNREACHABLE a return statement would be necessary.
#define UNREACHABLE __builtin_unreachable
// Add the C++11 noreturn attribute.
#define NO_RETURN [[noreturn]] // NOLINT[whitespace/braces] [5]
// Annotalysis thread-safety analysis support. Things that are not in base.
#define LOCKABLE CAPABILITY("mutex")
#define SHARED_LOCKABLE SHARED_CAPABILITY("mutex")
// Some of the libs (e.g. libarttest(d)) require more public symbols when built
// in debug configuration.
// Using symbol visibility only for release builds allows to reduce the list of
// exported symbols and eliminates the need to check debug build configurations
// when changing the exported symbols.
#ifdef NDEBUG
#define HIDDEN __attribute__((visibility("hidden")))
#define PROTECTED __attribute__((visibility("protected")))
#define EXPORT __attribute__((visibility("default")))
#else
#define HIDDEN
#define PROTECTED
#define EXPORT
#endif
// Protected symbols must be declared with "protected" visibility attribute when
// building the library and "default" visibility when referred to from external
// libraries/binaries. Otherwise, the external code will expect the symbol to be
// defined locally and fail to link.
#ifdef BUILDING_LIBART
#define LIBART_PROTECTED PROTECTED
#else
#define LIBART_PROTECTED EXPORT
#endif
// Some global variables shouldn't be visible outside libraries declaring them.
// The attribute allows hiding them, so preventing direct access.
#define ALWAYS_HIDDEN __attribute__((visibility("hidden")))
#endif // ART_LIBARTBASE_BASE_MACROS_H_

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_RUNTIME_OAT_OAT_H_
#define ART_RUNTIME_OAT_OAT_H_
#include <array>
#include <cstddef>
#include <string_view>
#include <utility>
#include "base_macros.h"
#include "macros.h"
namespace art {
enum class InstructionSet;
class EXPORT PACKED(4) OatHeader {
public:
static constexpr std::array<uint8_t, 4> kOatMagic{{'o', 'a', 't', '\n'}};
// Last oat version changed reason: Ensure oat checksum determinism across hosts and devices.
static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '9', '\0'}};
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
static constexpr const char* kDebuggableKey = "debuggable";
static constexpr const char* kNativeDebuggableKey = "native-debuggable";
static constexpr const char* kCompilerFilter = "compiler-filter";
static constexpr const char* kClassPathKey = "classpath";
static constexpr const char* kBootClassPathKey = "bootclasspath";
static constexpr const char* kBootClassPathChecksumsKey = "bootclasspath-checksums";
static constexpr const char* kApexVersionsKey = "apex-versions";
static constexpr const char* kConcurrentCopying = "concurrent-copying";
static constexpr const char* kCompilationReasonKey = "compilation-reason";
static constexpr const char* kRequiresImage = "requires-image";
// Fields listed here are key value store fields that are deterministic across hosts and
// devices, meaning they should have exactly the same value when the oat file is generated on
// different hosts and devices for the same app / boot image and for the same device model with
// the same compiler options. If you are adding a new field that doesn't hold this property, put
// it in `kNonDeterministicFieldsAndLengths` and assign a length limit.
//
// When writing the oat header, the non-deterministic fields are padded to their length limits
// and excluded from the oat checksum computation. This makes the oat checksum deterministic
// across hosts and devices, which is important for Cloud Compilation, where we generate an oat
// file on a host and use it on a device.
static constexpr std::array<std::string_view, 9> kDeterministicFields{
kDebuggableKey, kNativeDebuggableKey, kCompilerFilter,
kClassPathKey, kBootClassPathKey, kBootClassPathChecksumsKey,
kConcurrentCopying, kCompilationReasonKey, kRequiresImage,
};
static constexpr std::array<std::pair<std::string_view, size_t>, 2>
kNonDeterministicFieldsAndLengths{
std::make_pair(kDex2OatCmdLineKey, 2048),
std::make_pair(kApexVersionsKey, 1024),
};
static constexpr const char kTrueValue[] = "true";
static constexpr const char kFalseValue[] = "false";
static constexpr size_t Get_key_value_store_size_Offset() {
return offsetof(OatHeader, key_value_store_size_);
}
static constexpr size_t Get_key_value_store_Offset() {
return offsetof(OatHeader, key_value_store_);
}
uint32_t GetKeyValueStoreSize() const;
const uint8_t* GetKeyValueStore() const;
void SetKeyValueStoreSize(uint32_t new_size);
void ComputeChecksum(/*inout*/ uint32_t* checksum) const;
private:
std::array<uint8_t, 4> magic_;
std::array<uint8_t, 4> version_;
uint32_t oat_checksum_;
InstructionSet instruction_set_;
uint32_t instruction_set_features_bitmap_;
uint32_t dex_file_count_;
uint32_t oat_dex_files_offset_;
uint32_t bcp_bss_info_offset_;
// Offset of the oat header (i.e. start of the oat data) in the ELF file.
// It is used to additional validation of the oat header as it is not
// page-aligned in the memory.
uint32_t base_oat_offset_;
uint32_t executable_offset_;
uint32_t jni_dlsym_lookup_trampoline_offset_;
uint32_t jni_dlsym_lookup_critical_trampoline_offset_;
uint32_t quick_generic_jni_trampoline_offset_;
uint32_t quick_imt_conflict_trampoline_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;
uint32_t nterp_trampoline_offset_;
uint32_t key_value_store_size_;
uint8_t key_value_store_[0]; // note variable width data at end
DISALLOW_COPY_AND_ASSIGN(OatHeader);
};
} // namespace art
#endif // ART_RUNTIME_OAT_OAT_H_

View File

@ -0,0 +1,17 @@
#include "oat.h"
namespace art {
uint32_t OatHeader::GetKeyValueStoreSize() const {
return *(uint32_t*)((uintptr_t)this + OatHeader::Get_key_value_store_size_Offset());
}
const uint8_t* OatHeader::GetKeyValueStore() const {
return (const uint8_t*)((uintptr_t)this + OatHeader::Get_key_value_store_Offset());
}
void OatHeader::SetKeyValueStoreSize(uint32_t new_size) {
*reinterpret_cast<uint32_t*>((uintptr_t)this + Get_key_value_store_size_Offset()) = new_size;
}
} // namespace art

View File

@ -1,43 +1,88 @@
#include <dlfcn.h>
#include <cinttypes>
#include <cstdint>
#include <lsplt.hpp>
#include <map>
#include <string>
#include <string_view>
#include <vector>
#include "logging.h"
#include "oat.h"
const std::string_view parameter_to_remove = " --inline-max-code-units=0";
const std::string_view param_to_remove = " --inline-max-code-units=0";
#define DCL_HOOK_FUNC(ret, func, ...) \
ret (*old_##func)(__VA_ARGS__); \
ret new_##func(__VA_ARGS__)
bool store_updated = false;
bool store_resized = false;
void UpdateKeyValueStore(std::map<std::string, std::string>* key_value, uint8_t* store) {
LOGD("updating KeyValueStore");
char* data_ptr = reinterpret_cast<char*>(store);
if (key_value != nullptr) {
auto it = key_value->begin();
auto end = key_value->end();
for (; it != end; ++it) {
strlcpy(data_ptr, it->first.c_str(), it->first.length() + 1);
data_ptr += it->first.length() + 1;
strlcpy(data_ptr, it->second.c_str(), it->second.length() + 1);
data_ptr += it->second.length() + 1;
bool ModifyStoreInPlace(uint8_t* store, uint32_t store_size) {
if (store == nullptr || store_size == 0) {
return false;
}
// Define the search space
uint8_t* const store_begin = store;
uint8_t* const store_end = store + store_size;
// 1. Search for the parameter in the memory buffer
auto it = std::search(store_begin, store_end, param_to_remove.begin(), param_to_remove.end());
// Check if the parameter was found
if (it == store_end) {
LOGD("Parameter '%.*s' not found.", (int)param_to_remove.size(), param_to_remove.data());
return false;
}
uint8_t* location_of_param = it;
LOGD("Parameter found at offset %td.", location_of_param - store_begin);
// 2. Check if there is padding immediately after the string
uint8_t* const byte_after_param = location_of_param + param_to_remove.size();
bool has_padding = false;
// Boundary check: ensure the byte after the parameter is within the buffer
if (byte_after_param + 1 < store_end) {
if (*(byte_after_param + 1) == '\0') {
has_padding = true;
}
}
LOGD("KeyValueStore updated");
store_updated = true;
// 3. Perform the conditional action
if (has_padding) {
// CASE A: Padding exists. Overwrite the parameter with zeros.
LOGD("Padding found. Overwriting parameter with zeros.");
memset(location_of_param, 0, param_to_remove.size());
return false; // Size did not change
} else {
// CASE B: No padding exists (or parameter is at the very end).
// Remove the parameter by shifting the rest of the memory forward.
LOGD("No padding found. Removing parameter and shifting memory.");
// Calculate what to move
uint8_t* source = byte_after_param;
uint8_t* destination = location_of_param;
size_t bytes_to_move = store_end - source;
// memmove is required because the source and destination buffers overlap
if (bytes_to_move > 0) {
memmove(destination, source, bytes_to_move);
}
// 4. Update the total size of the store
store_size -= param_to_remove.size();
LOGD("Store size changed. New size: %u", store_size);
return true; // Size changed
}
}
DCL_HOOK_FUNC(uint32_t, _ZNK3art9OatHeader20GetKeyValueStoreSizeEv, void* header) {
uint32_t size = old__ZNK3art9OatHeader20GetKeyValueStoreSizeEv(header);
if (store_updated) {
if (store_resized) {
LOGD("OatHeader::GetKeyValueStoreSize() called on object at %p\n", header);
size = size - parameter_to_remove.size();
size = size - param_to_remove.size();
}
return size;
}
@ -46,47 +91,32 @@ DCL_HOOK_FUNC(uint8_t*, _ZNK3art9OatHeader16GetKeyValueStoreEv, void* header) {
LOGD("OatHeader::GetKeyValueStore() called on object at %p\n", header);
uint8_t* key_value_store_ = old__ZNK3art9OatHeader16GetKeyValueStoreEv(header);
uint32_t key_value_store_size_ = old__ZNK3art9OatHeader20GetKeyValueStoreSizeEv(header);
const char* ptr = reinterpret_cast<const char*>(key_value_store_);
const char* end = ptr + key_value_store_size_;
std::map<std::string, std::string> new_store = {};
LOGD("scanning [%p-%p] for oat headers", ptr, end);
while (ptr < end) {
// Scan for a closing zero.
const char* str_end = reinterpret_cast<const char*>(memchr(ptr, 0, end - ptr));
if (str_end == nullptr) [[unlikely]] {
LOGE("failed to find str_end");
return key_value_store_;
}
std::string_view key = std::string_view(ptr, str_end - ptr);
const char* value_start = str_end + 1;
const char* value_end =
reinterpret_cast<const char*>(memchr(value_start, 0, end - value_start));
if (value_end == nullptr) [[unlikely]] {
LOGE("failed to find value_end");
return key_value_store_;
}
std::string_view value = std::string_view(value_start, value_end - value_start);
LOGV("header %s:%s", key.data(), value.data());
if (key == "dex2oat-cmdline") {
value = value.substr(0, value.size() - parameter_to_remove.size());
}
new_store.insert(std::make_pair(std::string(key), std::string(value)));
// Different from key. Advance over the value.
ptr = value_end + 1;
}
UpdateKeyValueStore(&new_store, key_value_store_);
LOGD("KeyValueStore via hook: [addr: %p, size: %u]", key_value_store_, key_value_store_size_);
store_resized = ModifyStoreInPlace(key_value_store_, key_value_store_size_);
return key_value_store_;
}
DCL_HOOK_FUNC(void, _ZNK3art9OatHeader15ComputeChecksumEPj, void* header, uint32_t* checksum) {
art::OatHeader* oat_header = reinterpret_cast<art::OatHeader*>(header);
const uint8_t* key_value_store_ = oat_header->GetKeyValueStore();
uint32_t key_value_store_size_ = oat_header->GetKeyValueStoreSize();
LOGD("KeyValueStore via offset: [addr: %p, size: %u]", key_value_store_, key_value_store_size_);
store_resized =
ModifyStoreInPlace(const_cast<uint8_t*>(key_value_store_), key_value_store_size_);
if (store_resized) {
oat_header->SetKeyValueStoreSize(key_value_store_size_ - param_to_remove.size());
}
old__ZNK3art9OatHeader15ComputeChecksumEPj(header, checksum);
LOGD("ComputeChecksum called: %" PRIu32, *checksum);
}
#undef DCL_HOOK_FUNC
void register_hook(dev_t dev, ino_t inode, const char* symbol, void* new_func, void** old_func) {
LOGD("RegisterHook: %s, %p, %p", symbol, new_func, old_func);
if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
LOGE("Failed to register plt_hook \"%s\"\n", symbol);
return;
}
}
@ -109,7 +139,8 @@ __attribute__((constructor)) static void initialize() {
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader20GetKeyValueStoreSizeEv);
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader16GetKeyValueStoreEv);
if (lsplt::CommitHook()) {
LOGD("lsplt hooks done");
};
if (!lsplt::CommitHook()) {
PLT_HOOK_REGISTER(dev, inode, _ZNK3art9OatHeader15ComputeChecksumEPj);
lsplt::CommitHook();
}
}

2
external/lsplt vendored

@ -1 +1 @@
Subproject commit 2efebed64ecce060530c6959b8654019aa796f52
Subproject commit e3db0003526b1e5be3fb9323ff5ff128a59f9ffb