diff --git a/lsplt/src/main/jni/include/lsplt.hpp b/lsplt/src/main/jni/include/lsplt.hpp index ab0ab12..8ab4115 100644 --- a/lsplt/src/main/jni/include/lsplt.hpp +++ b/lsplt/src/main/jni/include/lsplt.hpp @@ -41,28 +41,63 @@ struct MapInfo { [[maybe_unused, gnu::visibility("default")]] static std::vector Scan(); }; -/// \brief Register a hook to a function. +/// \brief Register a hook to a function by inode. For so within an archive, you should use +/// #RegisterHook(ino_t, uintptr_t, size_t, std::string_view, void *, void **) instead. /// \param inode The inode of the library to hook. You can obtain the inode by #stat() or by finding -/// the library in the list returned by #MapInfo::Scan(). +/// the library in the list returned by #lsplt::v1::MapInfo::Scan(). /// \param symbol The function symbol to hook. /// \param callback The callback function pointer to call when the function is called. -/// \param backup The backup function pointer which can call the original function. This is optional. +/// \param backup The backup function pointer which can call the original function. This is +/// optional. /// \return Whether the hook is successfully registered. /// \note This function is thread-safe. /// \note \p backup will not be available until #CommitHook() is called. /// \note \p backup will be nullptr if the hook fails. /// \note You can unhook the function by calling this function with \p callback set to the backup /// set by previous call. -/// \note LSPlt will backup the hook memory region and restore it when the hook is restored -/// to its original function pointer so that there won't be dirty pages. -/// LSPlt will do hooks on a copied memory region so that the original memory region will not be -/// modified. You can invalidate this behaviour and hook the original memory region by calling +/// \note LSPlt will backup the hook memory region and restore it when the +/// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will +/// do hooks on a copied memory region so that the original memory region will not be modified. You +/// can invalidate this behaviour and hook the original memory region by calling /// #InvalidateBackup(). /// \see #CommitHook() /// \see #InvalidateBackup() [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback, void **backup); +/// \brief Register a hook to a function by inode with offset range. This is useful when hooking +/// a library that is directly loaded from an archive without extraction. +/// \param inode The inode of the library to hook. You can obtain the inode by #stat() or by finding +/// the library in the list returned by #lsplt::v1::MapInfo::Scan(). +/// \param offset The to the library in the file. +/// \param size The upper bound size to the library in the file. +/// \param symbol The function symbol to hook. +/// \param callback The callback function pointer to call when the function is called. +/// \param backup The backup function pointer which can call the original function. This is +/// optional. +/// \return Whether the hook is successfully registered. +/// \note This function is thread-safe. +/// \note \p backup will not be available until #CommitHook() is called. +/// \note \p backup will be nullptr if the hook fails. +/// \note You can unhook the function by calling this function with \p callback set to the backup +/// set by previous call. +/// \note LSPlt will backup the hook memory region and restore it when the +/// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will +/// do hooks on a copied memory region so that the original memory region will not be modified. You +/// can invalidate this behaviour and hook the original memory region by calling +/// #InvalidateBackup(). +/// \note You can get the offset range of the library by getting its entry offset and size in the +/// zip file. +/// \note According to the Android linker specification, the \p offset must be page aligned. +/// \note The \p offset must be accurate, otherwise the hook may fail because the ELF header +/// cannot be found. +/// \note The \p size can be inaccurate but should be larger or equal to the library size, +/// otherwise the hook may fail when the hook pointer is beyond the range. +/// \see #CommitHook() +/// \see #InvalidateBackup() +[[maybe_unused, gnu::visibility("default")]] bool RegisterHook(ino_t inode, uintptr_t offset, + size_t size, std::string_view symbol, + void *callback, void **backup); /// \brief Commit all registered hooks. /// \return Whether all hooks are successfully committed. If any of the hooks fail to commit, /// the result is false. diff --git a/lsplt/src/main/jni/lsplt.cc b/lsplt/src/main/jni/lsplt.cc index 8e9faea..505c671 100644 --- a/lsplt/src/main/jni/lsplt.cc +++ b/lsplt/src/main/jni/lsplt.cc @@ -24,6 +24,7 @@ inline auto PageEnd(uintptr_t addr) { struct RegisterInfo { ino_t inode; + std::pair offset_range; std::string symbol; void *callback; void **backup; @@ -34,7 +35,10 @@ struct HookInfo : public lsplt::MapInfo { uintptr_t backup; std::unique_ptr elf; bool self; - [[nodiscard]] bool Match(const RegisterInfo &info) const { return info.inode == inode; } + [[nodiscard]] bool Match(const RegisterInfo &info) const { + return info.inode == inode && offset >= info.offset_range.first && + offset < info.offset_range.second; + } }; class HookInfos : public std::map> { @@ -172,7 +176,7 @@ public: for (auto &[_, info] : *this) { for (auto iter = register_info.begin(); iter != register_info.end();) { const auto ® = *iter; - if (info.offset != 0 || !info.Match(reg)) { + if (info.offset != iter->offset_range.first || !info.Match(reg)) { ++iter; continue; } @@ -268,11 +272,33 @@ inline namespace v1 { [[maybe_unused]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback, void **backup) { if (inode == 0 || symbol.empty() || !callback) return false; - LOGV("RegisterHook %lu %s", inode, symbol.data()); std::unique_lock lock(hook_mutex); - register_info.emplace_back(RegisterInfo{inode, std::string{symbol}, callback, backup}); + static_assert(std::numeric_limits::min() == 0); + static_assert(std::numeric_limits::max() == -1); + const auto &info = register_info.emplace_back( + RegisterInfo{inode, + {std::numeric_limits::min(), std::numeric_limits::max()}, + std::string{symbol}, + callback, + backup}); + LOGV("RegisterHook %lu %s", info.inode, info.symbol.data()); + return true; +} + +[[maybe_unused]] bool RegisterHook(ino_t inode, uintptr_t offset, size_t size, + std::string_view symbol, void *callback, void **backup) { + if (inode == 0 || symbol.empty() || !callback) return false; + + std::unique_lock lock(hook_mutex); + static_assert(std::numeric_limits::min() == 0); + static_assert(std::numeric_limits::max() == -1); + const auto &info = register_info.emplace_back( + RegisterInfo{inode, {offset, offset + size}, std::string{symbol}, callback, backup}); + + LOGV("RegisterHook %lu %" PRIxPTR "-%" PRIxPTR " %s", info.inode, info.offset_range.first, + info.offset_range.second, info.symbol.data()); return true; }