mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-04 20:42:02 +08:00
Add doc
This commit is contained in:
parent
263565175a
commit
bc20e3cf11
@ -40,6 +40,10 @@ public:
|
||||
return GetAccessFlags() & kAccStatic;
|
||||
}
|
||||
|
||||
bool IsNative() {
|
||||
return GetAccessFlags() & kAccNative;
|
||||
}
|
||||
|
||||
void CopyFrom(const ArtMethod *other) {
|
||||
memcpy(this, other, art_method_size);
|
||||
}
|
||||
|
@ -3,35 +3,124 @@
|
||||
#include <jni.h>
|
||||
#include <string_view>
|
||||
|
||||
/// \namespace namespace of LSPlant
|
||||
namespace lsplant {
|
||||
|
||||
inline namespace v1 {
|
||||
/// \struct InitInfo
|
||||
struct InitInfo {
|
||||
/// \typedef Type of inline hook function.
|
||||
/// In \p std::function form so that user can use lambda expression with capture list.<br>
|
||||
/// \p target is the target function to be hooked.<br>
|
||||
/// \p hooker is the hooker function to replace the \p target function.<br>
|
||||
/// \p return is the backup function that points to the previous target function.
|
||||
/// it should return null if hook fails and nonnull if successes.
|
||||
using InlineHookFunType = std::function<void *(void *target, void *hooker)>;
|
||||
/// \typedef Type of inline unhook function.
|
||||
/// In \p std::function form so that user can use lambda expression with capture list.<br>
|
||||
/// \p func is the target function that is previously hooked.<br>
|
||||
/// \p return should indicate the status of unhooking.<br>
|
||||
using InlineUnhookFunType = std::function<bool(void *func)>;
|
||||
/// \typedef Type of symbol resolver to \p libart.so.
|
||||
/// In \p std::function form so that user can use lambda expression with capture list.<br>
|
||||
/// \p symbol_name is the symbol name that needs to retrieve.<br>
|
||||
/// \p return is the absolute address in the memory that points to the target symbol. It should
|
||||
/// be null if the symbol cannot be found. <br>
|
||||
/// \note It should be able to resolve symbols from both .dynsym and .symtab.
|
||||
using ArtSymbolResolver = std::function<void *(std::string_view symbol_name)>;
|
||||
|
||||
/// \brief The inline hooker function. Must not be null.
|
||||
InlineHookFunType inline_hooker;
|
||||
/// \brief The inline unhooker function. Must not be null.
|
||||
InlineUnhookFunType inline_unhooker;
|
||||
/// \brief The symbol resolver to \p libart.so. Must not be null.
|
||||
ArtSymbolResolver art_symbol_resolver;
|
||||
};
|
||||
|
||||
/// \brief Initialize LSPlant for procceding hook.
|
||||
/// It mainly prefetch needed symbols and hook some functions.
|
||||
/// \param[in] env The Java environment. Must not be null.
|
||||
/// \param[in] info The information for initialized. \ref InitInfo.
|
||||
/// Basically, the info provides the inline hooker and unhooker together with a symbol resolver of
|
||||
/// libart.so to hook and extract needed native functions of ART.
|
||||
/// \return Indicate whether initialization succeed. Behavior is undefined if calling other
|
||||
/// LSPlant interfaces before initialization or after a fail initialization.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
bool Init(JNIEnv *env, const InitInfo &info);
|
||||
|
||||
/// \brief Hook a Java method by providing the \p target_method together with the context object
|
||||
/// \p hooker_object and its callback \p callback_method.
|
||||
/// \param[in] env The Java environment. Must not be null.
|
||||
/// \param[in] target_method The method id to the method you want to hook. Must not be null.
|
||||
/// \param[in] hooker_object The hooker object to store the context of the hook.
|
||||
/// The most likely usage is to store the \b backup method into it so that when \b callback_method
|
||||
/// is invoked, it can call the original method. Another scenario is that, for example,
|
||||
/// in Xposed framework, multiple modules can hook the same Java method and the \b hooker_object
|
||||
/// can be used to store all the callbacks to allow multiple modules work simultaneously without
|
||||
/// conflict.
|
||||
/// \param[in] callback_method The callback method to the \p hooker_object is used to replace the
|
||||
/// \p target_method. Whenever the \p target_method is invoked, the \p callback_method will be
|
||||
/// inked instead of the original \p target_method. The signature of the \p callback_method must
|
||||
/// be:<br>
|
||||
/// \code{.java}
|
||||
/// Object callback_method(Object []args)
|
||||
/// \endcode<br>
|
||||
/// That is, the return type must be \p Object and the parameter type must be \b Object[]. Behavior
|
||||
/// is undefined if the signature does not match the requirement.
|
||||
/// Extra info can be provided by defining member variables of \p hooker_object.
|
||||
/// This method must be a method to \p hooker_object.
|
||||
/// \return The backup method. You can invoke it by reflection to invoke the original method. null
|
||||
/// if fails.
|
||||
/// \note This function thread safe (you can call it simultaneously from multiple thread)
|
||||
/// but it's not atomic to the same \b target_method. That means \p UnHook or \p IsUnhook does
|
||||
/// not guarantee to work properly on the same \p target_method before it returns. Also,
|
||||
/// simultaneously call on this function with the same \p target_method does not guarantee only one
|
||||
/// will success. If you call this with different \p hooker_object on the same target_method
|
||||
/// simultaneously, the behavior is undefined.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
jmethodID
|
||||
Hook(JNIEnv *env, jmethodID target_method, jobject hooker_object, jmethodID callback_method);
|
||||
|
||||
/// Unhook a Java function that is previously hooked.
|
||||
/// \param[in] env The Java environment.
|
||||
/// \param[in] target_method The target method that is previously hooked.
|
||||
/// \return Indicate whether the unhook succeed.
|
||||
/// \note please read Hook's note for more details.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
bool UnHook(JNIEnv *env, jmethodID target_method);
|
||||
|
||||
/// Check if a Java function is hooked by LSPlant or not
|
||||
/// \param[in] env The Java environment.
|
||||
/// \param[in] method The method to check if it was hooked or not.
|
||||
/// \return If \p method hooked, ture; otherwise, false.
|
||||
/// \note please read Hook's note for more details.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
bool IsHooked(JNIEnv *env, jmethodID method);
|
||||
|
||||
/// Deoptimize a method to avoid hooked callee not being called because of inline
|
||||
/// \param[in] env The Java environment.
|
||||
/// \param[in] method The method to deoptimize. By deoptimizing the method, the method will back all
|
||||
/// callee without inlining. For example, if you hooked a short method B that is invoked by method
|
||||
/// A, and you find that your callback to B is not invoked after hooking, then it may mean A has
|
||||
/// inlined B inside its method body. To force A to call your hooked B, you can deoptimize A and then
|
||||
/// your hook can take effect. Generally, you need to find all the callers of your hooked callee
|
||||
/// and that can be hardly achieve. Use this function if you are sure the deoptimized callers
|
||||
/// are all you need. Otherwise, it would be better to change the hook point or to deoptimize the
|
||||
/// whole app manually (by simple reinstall the app without uninstalled).
|
||||
/// \return Indicate whether the deoptimizing succeed or not.
|
||||
/// \note It is safe to call deoptimizing on a hooked method because the deoptimization will
|
||||
/// perform on the backup method instead.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
bool Deoptimize(JNIEnv *env, jmethodID method);
|
||||
|
||||
/// Get the registered native function pointer of a native function. It helps user to hook native
|
||||
/// methods directly by backing up the native function pointer this function returns and
|
||||
/// env->registerNatives another native function pointer.
|
||||
/// \param[in] env The Java environment.
|
||||
/// \param[in] method The native method to get the native function pointer.
|
||||
/// \return The native function pointer the \p method previously registered. If it has not been
|
||||
/// registered or it is not a native method, null is returned instead.
|
||||
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||
void *GetNativeFunction(JNIEnv *env, jmethodID method);
|
||||
|
||||
}
|
||||
} // namespace lsplant
|
||||
|
@ -418,6 +418,10 @@ void OnPending(art::ArtMethod *target, art::ArtMethod *hook, art::ArtMethod *bac
|
||||
}
|
||||
}
|
||||
|
||||
inline namespace v1 {
|
||||
|
||||
using ::lsplant::IsHooked;
|
||||
|
||||
[[maybe_unused]]
|
||||
bool Init(JNIEnv *env, const InitInfo &info) {
|
||||
bool static kInit = InitJNI(env) && InitNative(env, info);
|
||||
@ -489,14 +493,15 @@ Hook(JNIEnv *env, jmethodID target_method, jobject hooker_object, jmethodID call
|
||||
LOGE("Failed to decode target class");
|
||||
return nullptr;
|
||||
}
|
||||
auto class_def = miror_class->GetClassDef();
|
||||
const auto *class_def = miror_class->GetClassDef();
|
||||
if (!class_def) {
|
||||
LOGE("Failed to get target class def");
|
||||
return nullptr;
|
||||
}
|
||||
RecordPending(class_def, target, hook, backup);
|
||||
return backup_method;
|
||||
} else if (DoHook(target, hook, backup)) {
|
||||
}
|
||||
if (DoHook(target, hook, backup)) {
|
||||
RecordHooked(target, JNI_NewGlobalRef(env, reflected_backup));
|
||||
if (!is_proxy) [[likely]] RecordJitMovement(target, backup);
|
||||
return backup_method;
|
||||
@ -551,6 +556,17 @@ bool IsHooked(JNIEnv *env, jmethodID method) {
|
||||
bool Deoptimize(JNIEnv *env, jmethodID method) {
|
||||
auto reflected = JNI_ToReflectedMethod(env, jclass{ nullptr }, method, false);
|
||||
auto *art_method = ArtMethod::FromReflectedMethod(env, reflected);
|
||||
if (IsHooked(art_method)) {
|
||||
std::shared_lock lk(hooked_methods_lock_);
|
||||
auto it = hooked_methods_.find(art_method);
|
||||
if (it != hooked_methods_.end()) {
|
||||
auto *reflected_backup = it->second;
|
||||
art_method = ArtMethod::FromReflectedMethod(env, reflected_backup);
|
||||
}
|
||||
}
|
||||
if (!art_method) {
|
||||
return false;
|
||||
}
|
||||
return ClassLinker::SetEntryPointsToInterpreter(art_method);
|
||||
}
|
||||
|
||||
@ -558,9 +574,12 @@ bool Deoptimize(JNIEnv *env, jmethodID method) {
|
||||
void *GetNativeFunction(JNIEnv *env, jmethodID method) {
|
||||
auto reflected = JNI_ToReflectedMethod(env, jclass{ nullptr }, method, false);
|
||||
auto *art_method = ArtMethod::FromReflectedMethod(env, reflected);
|
||||
if (!art_method->IsNative()) return nullptr;
|
||||
return art_method->GetData();
|
||||
}
|
||||
|
||||
} // namespace v1
|
||||
|
||||
} // namespace lsplant
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
Loading…
x
Reference in New Issue
Block a user