Support hooking so in an archive

This commit is contained in:
LoveSy 2022-11-28 20:58:29 +08:00
parent b30d5f4529
commit d6a78a935f
No known key found for this signature in database
2 changed files with 72 additions and 11 deletions

View File

@ -41,28 +41,63 @@ struct MapInfo {
[[maybe_unused, gnu::visibility("default")]] static std::vector<MapInfo> Scan(); [[maybe_unused, gnu::visibility("default")]] static std::vector<MapInfo> 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 /// \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 symbol The function symbol to hook.
/// \param callback The callback function pointer to call when the function is called. /// \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. /// \return Whether the hook is successfully registered.
/// \note This function is thread-safe. /// \note This function is thread-safe.
/// \note \p backup will not be available until #CommitHook() is called. /// \note \p backup will not be available until #CommitHook() is called.
/// \note \p backup will be nullptr if the hook fails. /// \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 /// \note You can unhook the function by calling this function with \p callback set to the backup
/// set by previous call. /// set by previous call.
/// \note LSPlt will backup the hook memory region and restore it when the hook is restored /// \note LSPlt will backup the hook memory region and restore it when the
/// to its original function pointer so that there won't be dirty pages. /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will
/// LSPlt will do hooks on a copied memory region so that the original memory region will not be /// do hooks on a copied memory region so that the original memory region will not be modified. You
/// modified. You can invalidate this behaviour and hook the original memory region by calling /// can invalidate this behaviour and hook the original memory region by calling
/// #InvalidateBackup(). /// #InvalidateBackup().
/// \see #CommitHook() /// \see #CommitHook()
/// \see #InvalidateBackup() /// \see #InvalidateBackup()
[[maybe_unused, gnu::visibility("default")]] bool RegisterHook(ino_t inode, std::string_view symbol, [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(ino_t inode, std::string_view symbol,
void *callback, void **backup); 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. /// \brief Commit all registered hooks.
/// \return Whether all hooks are successfully committed. If any of the hooks fail to commit, /// \return Whether all hooks are successfully committed. If any of the hooks fail to commit,
/// the result is false. /// the result is false.

View File

@ -24,6 +24,7 @@ inline auto PageEnd(uintptr_t addr) {
struct RegisterInfo { struct RegisterInfo {
ino_t inode; ino_t inode;
std::pair<uintptr_t, uintptr_t> offset_range;
std::string symbol; std::string symbol;
void *callback; void *callback;
void **backup; void **backup;
@ -34,7 +35,10 @@ struct HookInfo : public lsplt::MapInfo {
uintptr_t backup; uintptr_t backup;
std::unique_ptr<Elf> elf; std::unique_ptr<Elf> elf;
bool self; 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<uintptr_t, HookInfo, std::greater<>> { class HookInfos : public std::map<uintptr_t, HookInfo, std::greater<>> {
@ -172,7 +176,7 @@ public:
for (auto &[_, info] : *this) { for (auto &[_, info] : *this) {
for (auto iter = register_info.begin(); iter != register_info.end();) { for (auto iter = register_info.begin(); iter != register_info.end();) {
const auto &reg = *iter; const auto &reg = *iter;
if (info.offset != 0 || !info.Match(reg)) { if (info.offset != iter->offset_range.first || !info.Match(reg)) {
++iter; ++iter;
continue; continue;
} }
@ -268,11 +272,33 @@ inline namespace v1 {
[[maybe_unused]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback, [[maybe_unused]] bool RegisterHook(ino_t inode, std::string_view symbol, void *callback,
void **backup) { void **backup) {
if (inode == 0 || symbol.empty() || !callback) return false; if (inode == 0 || symbol.empty() || !callback) return false;
LOGV("RegisterHook %lu %s", inode, symbol.data());
std::unique_lock lock(hook_mutex); std::unique_lock lock(hook_mutex);
register_info.emplace_back(RegisterInfo{inode, std::string{symbol}, callback, backup}); static_assert(std::numeric_limits<uintptr_t>::min() == 0);
static_assert(std::numeric_limits<uintptr_t>::max() == -1);
const auto &info = register_info.emplace_back(
RegisterInfo{inode,
{std::numeric_limits<uintptr_t>::min(), std::numeric_limits<uintptr_t>::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<uintptr_t>::min() == 0);
static_assert(std::numeric_limits<uintptr_t>::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; return true;
} }