Showing preview only (274K chars total). Download the full file or copy to clipboard to get everything.
Repository: iqiyi/xHook
Branch: master
Commit: 391d0c410362
Files: 83
Total size: 253.2 KB
Directory structure:
gitextract_ohqx2nmx/
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE-docs
├── README.md
├── README.zh-CN.md
├── build_libs.sh
├── clean_libs.sh
├── docs/
│ ├── overview/
│ │ ├── android_plt_hook_overview.zh-CN.md
│ │ └── code/
│ │ ├── .gitignore
│ │ ├── build.sh
│ │ ├── clean.sh
│ │ ├── libtest/
│ │ │ └── jni/
│ │ │ ├── Android.mk
│ │ │ ├── Application.mk
│ │ │ ├── test.c
│ │ │ └── test.h
│ │ ├── main/
│ │ │ └── jni/
│ │ │ ├── Android.mk
│ │ │ ├── Application.mk
│ │ │ └── main.c
│ │ └── run.sh
│ └── xhooklogo.sketch
├── install_libs.sh
├── libbiz/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ └── biz.c
├── libtest/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ └── test.c
├── libxhook/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ ├── queue.h
│ ├── tree.h
│ ├── xh_core.c
│ ├── xh_core.h
│ ├── xh_elf.c
│ ├── xh_elf.h
│ ├── xh_errno.h
│ ├── xh_jni.c
│ ├── xh_log.c
│ ├── xh_log.h
│ ├── xh_util.c
│ ├── xh_util.h
│ ├── xh_version.c
│ ├── xh_version.h
│ ├── xhook.c
│ └── xhook.h
└── xhookwrapper/
├── .gitignore
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── qiyi/
│ │ ├── test/
│ │ │ ├── NativeHandler.java
│ │ │ └── Test.java
│ │ └── xhookwrapper/
│ │ └── MainActivity.java
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ └── activity_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── biz/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── qiyi/
│ │ └── biz/
│ │ ├── Biz.java
│ │ └── NativeHandler.java
│ └── res/
│ └── values/
│ └── strings.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── xhook/
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── qiyi/
│ └── xhook/
│ ├── NativeHandler.java
│ └── XHook.java
└── res/
└── values/
└── strings.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
libxhook/libs/
libxhook/obj/
libbiz/libs/
libbiz/obj/
libtest/libs/
libtest/obj/
#for xhookwrapper
build/
.gradle/
.idea/
local.properties
*.iml
*.log
*.so
xhookwrapper/xhook/libs/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to xHook
Welcome to the xHook project. Read on to learn more about our development process and how to propose bug fixes and improvements.
## Issues
We use GitHub issues to track public bugs and feature requests. Before creating an issue, please note the following:
1. Please search existing issues before creating a new one.
2. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. The more information the better.
## Branch Management
There are 2 main branches:
1. `master` branch
* It's the latest (pre-)release branch. We use `master` for tags.
* **Please do NOT submit any PR on `master` branch.**
2. `dev` branch
* It's our stable developing branch.
* Once `dev` has passed iQIYI's internal tests, it will be merged to `master` branch for the next release.
* **Please always submit PR on `dev` branch.**
## Pull Requests
Please make sure the following is done when submitting a pull request:
1. Fork the repo and create your branch from `master`.
2. Add the copyright notice to the top of any new files you've added.
3. Try your best to test your code.
4. Squash all of your commits into one meaningful commit.
## Code Style Guide
1. 4 spaces for indentation rather than tabs.
2. Follow the C code style already in place.
## License
By contributing to xHook, you agree that your contributions will be licensed under its [MIT LICENSE](LICENSE).
================================================
FILE: LICENSE
================================================
Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
Most source code in xhook are MIT licensed. Some other source code
have BSD-style licenses.
A copy of the MIT License is included in this file.
Source code Licensed under the BSD 2-Clause License
===================================================
tree.h
Copyright 2002 Niels Provos <provos@citi.umich.edu>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Source code Licensed under the BSD 3-Clause License
===================================================
queue.h
Copyright (c) 1991, 1993 The Regents of the University of California.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Terms of the MIT License
========================
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: LICENSE-docs
================================================
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More_considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public licenses.
Notwithstanding, Creative Commons may elect to apply one of its public
licenses to material it publishes and in those instances will be
considered the "Licensor." Except for the limited purpose of indicating
that material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the public
licenses.
Creative Commons may be contacted at creativecommons.org.
================================================
FILE: README.md
================================================
<p align="center"><img src="https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true" alt="xhook" width="50%"></p>
# xHook





[README 中文版](README.zh-CN.md)
[Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md)
xHook is a PLT (Procedure Linkage Table) hook library for Android native ELF (executable and shared libraries).
xHook has been keeping optimized for stability and compatibility.
## Features
* Support Android 4.0 - 10 (API level 14 - 29).
* Support armeabi, armeabi-v7a, arm64-v8a, x86 and x86_64.
* Support **ELF HASH** and **GNU HASH** indexed symbols.
* Support **SLEB128** encoded relocation info.
* Support setting hook info via regular expressions.
* Do not require root permission or any system permissions.
* Do not depends on any third-party shared libraries.
## Build
* Download [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html), set environment PATH. (support for armeabi has been removed since r17)
* Build and install the native libraries.
```
./build_libs.sh
./install_libs.sh
```
## Demo
```
cd ./xhookwrapper/
./gradlew assembleDebug
adb install ./app/build/outputs/apk/debug/app-debug.apk
```
## API
External API header file: `libxhook/jni/xhook.h`
### 1. Register hook info
```c
int xhook_register(const char *pathname_regex_str,
const char *symbol,
void *new_func,
void **old_func);
```
In current process's memory space, in every loaded ELF which pathname matches regular expression `pathname_regex_str`, every PLT entries to `symbol` will be **replaced with** `new_func`. The original one will be saved in `old_func`.
The `new_func` **must** have the same function declaration as the original one.
Return zero if successful, non-zero otherwise.
The regular expression for `pathname_regex_str` only support **POSIX BRE (Basic Regular Expression)**.
### 2. Ignore some hook info
```c
int xhook_ignore(const char *pathname_regex_str,
const char *symbol);
```
Ignore some hook info according to `pathname_regex_str` and `symbol`, from registered hooks by `xhook_register`. If `symbol` is `NULL`, xhook will ignore all symbols from ELF which pathname matches `pathname_regex_str`.
Return zero if successful, non-zero otherwise.
The regular expression for `pathname_regex_str` only support **POSIX BRE**.
### 3. Do hook
```c
int xhook_refresh(int async);
```
Do the real hook operations according to the registered hook info.
Pass `1` to `async` for asynchronous hook. Pass `0` to `async` for synchronous hook.
Return zero if successful, non-zero otherwise.
xhook will keep a global cache for saving the last ELF loading info from `/proc/self/maps`. This cache will also be updated in `xhook_refresh`. With this cache, `xhook_refresh` can determine which ELF is newly loaded. We only need to do hook in these newly loaded ELF.
### 4. Clear cache
```c
void xhook_clear();
```
Clear all cache owned by xhook, reset all global flags to default value.
If you confirm that all PLT entries you want have been hooked, you could call this function to save some memory.
### 5. Enable/Disable debug info
```c
void xhook_enable_debug(int flag);
```
Pass `1` to `flag` for enable debug info. Pass `0` to `flag` for disable. (**disabled** by default)
Debug info will be sent to logcat with tag `xhook`.
### 6. Enable/Disable SFP (segmentation fault protection)
```c
void xhook_enable_sigsegv_protection(int flag);
```
Pass `1` to `flag` for enable SFP. Pass `0` to `flag` for disable. (**enabled** by default)
xhook is NOT a compliant business layer library. We have to calculate the value of some pointers directly. Reading or writing the memory pointed to by these pointers will cause a segmentation fault in some unusual situations and environment. The APP crash rate increased which caused by xhook is about one ten-millionth (0.0000001) according to our test. (The increased crash rate is also related to the ELFs and symbols you need to hook). Finally, we have to use some trick to prevent this harmless crashing. We called it SFP (segmentation fault protection) which consists of: `sigaction()`, `SIGSEGV`, `siglongjmp()` and `sigsetjmp()`.
**You should always enable SFP for release-APP, this will prevent your app from crashing. On the other hand, you should always disable SFP for debug-APP, so you can't miss any common coding mistakes that should be fixed.**
## Examples
```c
//detect memory leaks
xhook_register(".*\\.so$", "malloc", my_malloc, NULL);
xhook_register(".*\\.so$", "calloc", my_calloc, NULL);
xhook_register(".*\\.so$", "realloc", my_realloc, NULL);
xhook_register(".*\\.so$", "free", my_free, NULL);
//inspect sockets lifecycle
xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL);
xhook_register(".*\\.so$", "socket", my_socket, NULL);
xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL);
xhook_register(".*\\.so$", "bind", my_bind, NULL);
xhook_register(".*\\.so$", "listen", my_listen, NULL);
xhook_register(".*\\.so$", "connect", my_connect, NULL);
xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL);
xhook_register(".*\\.so$", "close", my_close, NULL);
//filter off and save some android log to local file
xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL);
xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL);
xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL);
xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL);
//tracking (ignore linker and linker64)
xhook_register("^/system/.*$", "mmap", my_mmap, NULL);
xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL);
xhook_ignore (".*/linker$", "mmap");
xhook_ignore (".*/linker$", "munmap");
xhook_ignore (".*/linker64$", "mmap");
xhook_ignore (".*/linker64$", "munmap");
//defense to some injection attacks
xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL);
xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL);
//fix some system bug
xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL);
//ignore all hooks in libwebviewchromium.so
xhook_ignore(".*/libwebviewchromium.so$", NULL);
//hook now!
xhook_refresh(1);
```
## Support
* [GitHub Issues](https://github.com/iqiyi/xHook/issues)
* [GitHub Discussions](https://github.com/iqiyi/xHook/discussions)
## Contributing
See [xHook Contributing Guide](CONTRIBUTING.md).
## License
xHook is MIT licensed, as found in the [LICENSE](LICENSE) file.
xHook documentation is Creative Commons licensed, as found in the [LICENSE-docs](LICENSE-docs) file.
================================================
FILE: README.zh-CN.md
================================================
<p align="center"><img src="https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true" alt="xhook" width="50%"></p>
# xHook





[README English Version](README.md)
[Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md)
xHook 是一个针对 Android 平台 ELF (可执行文件和动态库) 的 PLT (Procedure Linkage Table) hook 库。
xHook 一直在稳定性和兼容性方面做着持续的优化。
## 特征
* 支持 Android 4.0 - 10(API level 14 - 29)。
* 支持 armeabi,armeabi-v7a,arm64-v8a,x86 和 x86_64。
* 支持 **ELF HASH** 和 **GNU HASH** 索引的符号。
* 支持 **SLEB128** 编码的重定位信息。
* 支持通过正则表达式批量设置 hook 信息。
* 不需要 root 权限或任何系统权限。
* 不依赖于任何的第三方动态库。
## 编译
* 下载 [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html),设置 PATH 环境变量。(对 armeabi 的支持,从 r17 版本开始被移除了)
* 编译和安装 native 库。
```
./build_libs.sh
./install_libs.sh
```
## Demo
```
cd ./xhookwrapper/
./gradlew assembleDebug
adb install ./app/build/outputs/apk/debug/app-debug.apk
```
## API
外部 API 头文件: `libxhook/jni/xhook.h`
### 1. 注册 hook 信息
```c
int xhook_register(const char *pathname_regex_str,
const char *symbol,
void *new_func,
void **old_func);
```
在当前进程的内存空间中,在每一个符合正则表达式 `pathname_regex_str` 的已加载ELF中,每一个调用 `symbol` 的 PLT 入口点的地址值都将给替换成 `new_func`。之前的 PLT 入口点的地址值将被保存在 `old_func` 中。
`new_func` 必须具有和原函数同样的函数声明。
成功返回 0,失败返回 非0。
`pathname_regex_str` 只支持 **POSIX BRE (Basic Regular Expression)** 定义的正则表达式语法。
### 2. 忽略部分 hook 信息
```c
int xhook_ignore(const char *pathname_regex_str,
const char *symbol);
```
根据 `pathname_regex_str` 和 `symbol`,从已经通过 `xhook_register` 注册的 hook 信息中,忽略一部分 hook 信息。如果 `symbol` 为 `NULL`,xhook 将忽略所有路径名符合正则表达式 `pathname_regex_str` 的 ELF。
成功返回 0,失败返回 非0。
`pathname_regex_str` 只支持 **POSIX BRE** 定义的正则表达式语法。
### 3. 执行 hook
```c
int xhook_refresh(int async);
```
根据前面注册的 hook 信息,执行真正的 hook 操作。
给 `async` 参数传 `1` 表示执行异步的 hook 操作,传 `0` 表示执行同步的 hook 操作。
成功返回 0,失败返回 非0。
xhook 在内部维护了一个全局的缓存,用于保存最后一次从 `/proc/self/maps` 读取到的 ELF 加载信息。每次一调用 `xhook_refresh` 函数,这个缓存都将被更新。xhook 使用这个缓存来判断哪些 ELF 是这次新被加载到内存中的。我们每次只需要针对这些新加载的 ELF 做 hook 就可以了。
### 4. 清除缓存
```c
void xhook_clear();
```
清除 xhook 的缓存,重置所有的全局标示。
如果你确定你需要的所有 PLT 入口点都已经被替换了,你可以调用这个函数来释放和节省一些内存空间。
### 5. 启用/禁用 调试信息
```c
void xhook_enable_debug(int flag);
```
给 `flag` 参数传 `1` 表示启用调试信息,传 `0` 表示禁用调试信息。 (默认为:**禁用**)
调试信息将被输出到 logcat,对应的 TAG 为:`xhook`。
### 6. 启用/禁用 SFP (段错误保护)
```c
void xhook_enable_sigsegv_protection(int flag);
```
给 `flag` 参数传 `1` 表示启用 SFP,传 `0` 表示禁用 SFP。 (默认为:**启用**)
xhook 并不是一个常规的业务层的动态库。在 xhook 中,我们不得不直接计算一些内存指针的值。在一些极端的情况和环境下,读或者写这些指针指向的内存会发生段错误。根据我们的测试,xhook 的行为将导致 APP 崩溃率增加 “一千万分之一” (0.0000001)。(具体崩溃率可能会增加多少,也和你想要 hook 的库和符号有关)。最终,我们不得不使用某些方法来防止这些无害的崩溃。我们叫它SFP (段错误保护),它是由这些调用和值组成的:`sigaction()`, `SIGSEGV`, `siglongjmp()` 和 `sigsetjmp()`。
**在 release 版本的 APP 中,你应该始终启用 SFP,这能防止你的 APP 因为 xhook 而崩溃。在 debug 版本的 APP 中,你应该始终禁用 SFP,这样你就不会丢失那些一般性的编码失误导致的段错误,这些段错误是应该被修复的。**
## 例子
```c
//监测内存泄露
xhook_register(".*\\.so$", "malloc", my_malloc, NULL);
xhook_register(".*\\.so$", "calloc", my_calloc, NULL);
xhook_register(".*\\.so$", "realloc", my_realloc, NULL);
xhook_register(".*\\.so$", "free", my_free, NULL);
//监控 sockets 生命周期
xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL);
xhook_register(".*\\.so$", "socket", my_socket, NULL);
xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL);
xhook_register(".*\\.so$", "bind", my_bind, NULL);
xhook_register(".*\\.so$", "listen", my_listen, NULL);
xhook_register(".*\\.so$", "connect", my_connect, NULL);
xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL);
xhook_register(".*\\.so$", "close", my_close, NULL);
//过滤出和保存部分安卓 log 到本地文件
xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL);
xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL);
xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL);
xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL);
//追踪某些调用 (忽略 linker 和 linker64)
xhook_register("^/system/.*$", "mmap", my_mmap, NULL);
xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL);
xhook_ignore (".*/linker$", "mmap");
xhook_ignore (".*/linker$", "munmap");
xhook_ignore (".*/linker64$", "mmap");
xhook_ignore (".*/linker64$", "munmap");
//防御某些注入攻击
xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL);
xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL);
//修复某些系统 bug
xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL);
//忽略 libwebviewchromium.so 的所有 hook 信息
xhook_ignore(".*/libwebviewchromium.so$", NULL);
//现在执行 hook!
xhook_refresh(1);
```
## 技术支持
* [GitHub Issues](https://github.com/iqiyi/xHook/issues)
* [GitHub Discussions](https://github.com/iqiyi/xHook/discussions)
## 贡献
请阅读 [xHook Contributing Guide](CONTRIBUTING.md)。
## 许可证
xHook 使用 [MIT 许可证](LICENSE)。
xHook 的文档使用 [Creative Commons 许可证](LICENSE-docs)。
================================================
FILE: build_libs.sh
================================================
#!/bin/bash
ndk-build -C ./libxhook/jni
ndk-build -C ./libbiz/jni
ndk-build -C ./libtest/jni
================================================
FILE: clean_libs.sh
================================================
#!/bin/bash
ndk-build -C ./libbiz/jni clean
ndk-build -C ./libxhook/jni clean
ndk-build -C ./libtest/jni clean
================================================
FILE: docs/overview/android_plt_hook_overview.zh-CN.md
================================================
# Android PLT hook 概述
## 获取代码和资源
你始终可以从 [这里](https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md) 访问本文的最新版本。
文中使用的示例代码可以从 [这里](https://github.com/iqiyi/xHook/tree/master/docs/overview/code) 获取。文中提到的 xhook 开源项目可以从 [这里](https://github.com/iqiyi/xHook) 获取。
## 开始
### 新的动态库
我们有一个新的动态库:libtest.so。
> 头文件 test.h
```c
#ifndef TEST_H
#define TEST_H 1
#ifdef __cplusplus
extern "C" {
#endif
void say_hello();
#ifdef __cplusplus
}
#endif
#endif
```
> 源文件 test.c
```c
#include <stdlib.h>
#include <stdio.h>
void say_hello()
{
char *buf = malloc(1024);
if(NULL != buf)
{
snprintf(buf, 1024, "%s", "hello\n");
printf("%s", buf);
}
}
```
`say_hello` 的功能是在终端打印出 `hello\n` 这6个字符(包括结尾的 `\n`)。
我们需要一个测试程序:main。
> 源文件 main.c
```c
#include <test.h>
int main()
{
say_hello();
return 0;
}
```
编译它们分别生成 libtest.so 和 main。运行一下:
```
caikelun@debian:~$ adb push ./libtest.so ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
hello
caikelun@debian:~$
```
太棒了!libtest.so 的代码虽然看上去有些愚蠢,但是它居然可以正确的工作,那还有什么可抱怨的呢?赶紧在新版 APP 中开始使用它吧!
遗憾的是,正如你可能已经发现的,libtest.so 存在严重的内存泄露问题,每调用一次 `say_hello` 函数,就会泄露 1024 字节的内存。新版 APP 上线后崩溃率开始上升,各种诡异的崩溃信息和报障信息跌撞而至。
### 面临的问题
幸运的是,我们修复了 libtest.so 的问题。可是以后怎么办呢?我们面临 2 个问题:
1. 当测试覆盖不足时,如何及时发现和准确定位线上 APP 的此类问题?
2. 如果 libtest.so 是某些机型的系统库,或者第三方的闭源库,我们如何修复它?如果监控它的行为?
### 怎么做?
如果我们能对动态库中的函数调用做 hook(替换,拦截,窃听,或者你觉得任何正确的描述方式),那就能够做到很多我们想做的事情。比如 hook `malloc`,`calloc`,`realloc` 和 `free`,我们就能统计出各个动态库分配了多少内存,哪些内存一直被占用没有释放。
这真的能做到吗?答案是:hook 我们自己的进程是完全可以的。hook 其他进程需要 root 权限(对于其他进程,没有 root 权限就没法修改它的内存空间,也没法注入代码)。幸运的是,我们只要 hook 自己就够了。
## ELF
### 概述
ELF(Executable and Linkable Format)是一种行业标准的二进制数据封装格式,主要用于封装可执行文件、动态库、object 文件和 core dumps 文件。
使用 google NDK 对源代码进行编译和链接,生成的动态库或可执行文件都是 ELF 格式的。用 readelf 可以查看 ELF 文件的基本信息,用 objdump 可以查看 ELF 文件的反汇编输出。
ELF 格式的概述可以参考 [这里](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format),完整定义可以参考 [这里](http://refspecs.linuxbase.org/elf/elf.pdf)。其中最重要的部分是:ELF 文件头、SHT(section header table)、PHT(program header table)。
### ELF 文件头
ELF 文件的起始处,有一个固定格式的定长的文件头(32 位架构为 52 字节,64 位架构为 64 字节)。ELF 文件头以 magic number `0x7F 0x45 0x4C 0x46` 开始(其中后 3 个字节分别对应可见字符 `E` `L` `F`)。
libtest.so 的 ELF 文件头信息:
```
caikelun@debian:~$ arm-linux-androideabi-readelf -h ./libtest.so
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: ARM
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 12744 (bytes into file)
Flags: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 25
Section header string table index: 24
```
ELF 文件头中包含了 SHT 和 PHT 在当前 ELF 文件中的起始位置和长度。例如,libtest.so 的 SHT 起始位置为 12744,长度 40 字节;PHT 起始位置为 52,长度 32字节。

### SHT(section header table)
ELF 以 section 为单位来组织和管理各种信息。ELF 使用 SHT 来记录所有 section 的基本信息。主要包括:section 的类型、在文件中的偏移量、大小、加载到内存后的虚拟内存相对地址、内存中字节的对齐方式等。
libtest.so 的 SHT:
```
caikelun@debian:~$ arm-linux-androideabi-readelf -S ./libtest.so
There are 25 section headers, starting at offset 0x31c8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.android.ide NOTE 00000134 000134 000098 00 A 0 0 4
[ 2] .note.gnu.build-i NOTE 000001cc 0001cc 000024 00 A 0 0 4
[ 3] .dynsym DYNSYM 000001f0 0001f0 0003a0 10 A 4 1 4
[ 4] .dynstr STRTAB 00000590 000590 0004b1 00 A 0 0 1
[ 5] .hash HASH 00000a44 000a44 000184 04 A 3 0 4
[ 6] .gnu.version VERSYM 00000bc8 000bc8 000074 02 A 3 0 2
[ 7] .gnu.version_d VERDEF 00000c3c 000c3c 00001c 00 A 4 1 4
[ 8] .gnu.version_r VERNEED 00000c58 000c58 000020 00 A 4 1 4
[ 9] .rel.dyn REL 00000c78 000c78 000040 08 A 3 0 4
[10] .rel.plt REL 00000cb8 000cb8 0000f0 08 AI 3 18 4
[11] .plt PROGBITS 00000da8 000da8 00017c 00 AX 0 0 4
[12] .text PROGBITS 00000f24 000f24 0015a4 00 AX 0 0 4
[13] .ARM.extab PROGBITS 000024c8 0024c8 00003c 00 A 0 0 4
[14] .ARM.exidx ARM_EXIDX 00002504 002504 000100 08 AL 12 0 4
[15] .fini_array FINI_ARRAY 00003e3c 002e3c 000008 04 WA 0 0 4
[16] .init_array INIT_ARRAY 00003e44 002e44 000004 04 WA 0 0 1
[17] .dynamic DYNAMIC 00003e48 002e48 000118 08 WA 4 0 4
[18] .got PROGBITS 00003f60 002f60 0000a0 00 WA 0 0 4
[19] .data PROGBITS 00004000 003000 000004 00 WA 0 0 4
[20] .bss NOBITS 00004004 003004 000000 00 WA 0 0 1
[21] .comment PROGBITS 00000000 003004 000065 01 MS 0 0 1
[22] .note.gnu.gold-ve NOTE 00000000 00306c 00001c 00 0 0 4
[23] .ARM.attributes ARM_ATTRIBUTES 00000000 003088 00003b 00 0 0 1
[24] .shstrtab STRTAB 00000000 0030c3 000102 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
y (noread), p (processor specific)
```
比较重要,且和 hook 关系比较大的几个 section 是:
* `.dynstr`:保存了所有的字符串常量信息。
* `.dynsym`:保存了符号(symbol)的信息(符号的类型、起始地址、大小、符号名称在 `.dynstr` 中的索引编号等)。函数也是一种符号。
* `.text`:程序代码经过编译后生成的机器指令。
* `.dynamic`:供动态链接器使用的各项信息,记录了当前 ELF 的外部依赖,以及其他各个重要 section 的起始位置等信息。
* `.got`:Global Offset Table。用于记录外部调用的入口地址。动态链接器(linker)执行重定位(relocate)操作时,这里会被填入真实的外部调用的绝对地址。
* `.plt`:Procedure Linkage Table。外部调用的跳板,主要用于支持 lazy binding 方式的外部调用重定位。(Android 目前只有 MIPS 架构支持 lazy binding)
* `.rel.plt`:对外部函数直接调用的重定位信息。
* `.rel.dyn`:除 `.rel.plt` 以外的重定位信息。(比如通过全局函数指针来调用外部函数)

### PHT(program header table)
ELF 被加载到内存时,是以 segment 为单位的。一个 segment 包含了一个或多个 section。ELF 使用 PHT 来记录所有 segment 的基本信息。主要包括:segment 的类型、在文件中的偏移量、大小、加载到内存后的虚拟内存相对地址、内存中字节的对齐方式等。
libtest.so 的 PHT:
```
caikelun@debian:~$ arm-linux-androideabi-readelf -l ./libtest.so
Elf file type is DYN (Shared object file)
Entry point 0x0
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00100 0x00100 R 0x4
LOAD 0x000000 0x00000000 0x00000000 0x02604 0x02604 R E 0x1000
LOAD 0x002e3c 0x00003e3c 0x00003e3c 0x001c8 0x001c8 RW 0x1000
DYNAMIC 0x002e48 0x00003e48 0x00003e48 0x00118 0x00118 RW 0x4
NOTE 0x000134 0x00000134 0x00000134 0x000bc 0x000bc R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
EXIDX 0x002504 0x00002504 0x00002504 0x00100 0x00100 R 0x4
GNU_RELRO 0x002e3c 0x00003e3c 0x00003e3c 0x001c4 0x001c4 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .note.android.ident .note.gnu.build-id .dynsym .dynstr .hash .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt .text .ARM.extab .ARM.exidx
02 .fini_array .init_array .dynamic .got .data
03 .dynamic
04 .note.android.ident .note.gnu.build-id
05
06 .ARM.exidx
07 .fini_array .init_array .dynamic .got
```
所有类型为 `PT_LOAD` 的 segment 都会被动态链接器(linker)映射(mmap)到内存中。
### 连接视图(Linking View)和执行视图(Execution View)
* 连接视图:ELF 未被加载到内存执行前,以 section 为单位的数据组织形式。
* 执行视图:ELF 被加载到内存后,以 segment 为单位的数据组织形式。
我们关心的 hook 操作,属于动态形式的内存操作,因此主要关心的是执行视图,即 ELF 被加载到内存后,ELF 中的数据是如何组织和存放的。

### .dynamic section
这是一个十分重要和特殊的 section,其中包含了 ELF 中其他各个 section 的内存位置等信息。在执行视图中,总是会存在一个类型为 `PT_DYNAMIC` 的 segment,这个 segment 就包含了 .dynamic section 的内容。
无论是执行 hook 操作时,还是动态链接器执行动态链接时,都需要通过 `PT_DYNAMIC` segment 来找到 .dynamic section 的内存位置,再进一步读取其他各项 section 的信息。
libtest.so 的 .dynamic section:
```
caikelun@debian:~$ arm-linux-androideabi-readelf -d ./libtest.so
Dynamic section at offset 0x2e48 contains 30 entries:
Tag Type Name/Value
0x00000003 (PLTGOT) 0x3f7c
0x00000002 (PLTRELSZ) 240 (bytes)
0x00000017 (JMPREL) 0xcb8
0x00000014 (PLTREL) REL
0x00000011 (REL) 0xc78
0x00000012 (RELSZ) 64 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffa (RELCOUNT) 3
0x00000006 (SYMTAB) 0x1f0
0x0000000b (SYMENT) 16 (bytes)
0x00000005 (STRTAB) 0x590
0x0000000a (STRSZ) 1201 (bytes)
0x00000004 (HASH) 0xa44
0x00000001 (NEEDED) Shared library: [libc.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libstdc++.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x0000000e (SONAME) Library soname: [libtest.so]
0x0000001a (FINI_ARRAY) 0x3e3c
0x0000001c (FINI_ARRAYSZ) 8 (bytes)
0x00000019 (INIT_ARRAY) 0x3e44
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001e (FLAGS) BIND_NOW
0x6ffffffb (FLAGS_1) Flags: NOW
0x6ffffff0 (VERSYM) 0xbc8
0x6ffffffc (VERDEF) 0xc3c
0x6ffffffd (VERDEFNUM) 1
0x6ffffffe (VERNEED) 0xc58
0x6fffffff (VERNEEDNUM) 1
0x00000000 (NULL) 0x0
```
## 动态链接器(linker)
安卓中的动态链接器程序是 linker。源码在 [这里](https://android.googlesource.com/platform/bionic/+/master/linker/)。
动态链接(比如执行 dlopen)的大致步骤是:
1. 检查已加载的 ELF 列表。(如果 libtest.so 已经加载,就不再重复加载了,仅把 libtest.so 的引用计数加一,然后直接返回。)
2. 从 libtest.so 的 .dynamic section 中读取 libtest.so 的外部依赖的 ELF 列表,从此列表中剔除已加载的 ELF,最后得到本次需要加载的 ELF 完整列表(包括 libtest.so 自身)。
3. 逐个加载列表中的 ELF。加载步骤:
* 用 `mmap` 预留一块足够大的内存,用于后续映射 ELF。(`MAP_PRIVATE` 方式)
* 读 ELF 的 PHT,用 `mmap` 把所有类型为 `PT_LOAD` 的 segment 依次映射到内存中。
* 从 .dynamic segment 中读取各信息项,主要是各个 section 的虚拟内存相对地址,然后计算并保存各个 section 的虚拟内存绝对地址。
* 执行重定位操作(relocate),这是最关键的一步。重定位信息可能存在于下面的一个或多个 secion 中:`.rel.plt`, `.rela.plt`, `.rel.dyn`, `.rela.dyn`, `.rel.android`, `.rela.android`。动态链接器需要逐个处理这些 `.relxxx` section 中的重定位诉求。根据已加载的 ELF 的信息,动态链接器查找所需符号的地址(比如 libtest.so 的符号 `malloc`),找到后,将地址值填入 `.relxxx` 中指明的**目标地址**中,这些“**目标地址**”一般存在于`.got` 或 `.data` 中。
* ELF 的引用计数加一。
4. 逐个调用列表中 ELF 的构造函数(constructor),这些构造函数的地址是之前从 .dynamic segment 中读取到的(类型为 `DT_INIT` 和 `DT_INIT_ARRAY`)。各 ELF 的构造函数是按照依赖关系逐层调用的,先调用被依赖 ELF 的构造函数,最后调用 libtest.so 自己的构造函数。(ELF 也可以定义自己的析构函数(destructor),在 ELF 被 unload 的时候会被自动调用)
等一下!我们似乎发现了什么!再看一遍重定位操作(relocate)的部分。难道我们只要从这些 `.relxxx` 中获取到“**目标地址**”,然后在“**目标地址**”中重新填上一个新的函数地址,这样就完成 hook 了吗?也许吧。
## 追踪
静态分析验证一下还是很容易的。以 armeabi-v7a 架构的 libtest.so 为例。先看一下 say_hello 函数对应的汇编代码吧。
```
caikelun@debian:~/$ arm-linux-androideabi-readelf -s ./libtest.so
Symbol table '.dynsym' contains 58 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND snprintf@LIBC (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND malloc@LIBC (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2)
6: 00000f61 60 FUNC GLOBAL DEFAULT 12 say_hello
...............
...............
```
找到了!`say_hello` 在地址 `f61`,对应的汇编指令体积为 `60`(10 进制)字节。用 objdump 查看 `say_hello` 的反汇编输出。
```
caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so
...............
...............
00000f60 <say_hello@@Base>:
f60: b5b0 push {r4, r5, r7, lr}
f62: af02 add r7, sp, #8
f64: f44f 6080 mov.w r0, #1024 ; 0x400
f68: f7ff ef34 blx dd4 <malloc@plt>
f6c: 4604 mov r4, r0
f6e: b16c cbz r4, f8c <say_hello@@Base+0x2c>
f70: a507 add r5, pc, #28 ; (adr r5, f90 <say_hello@@Base+0x30>)
f72: a308 add r3, pc, #32 ; (adr r3, f94 <say_hello@@Base+0x34>)
f74: 4620 mov r0, r4
f76: f44f 6180 mov.w r1, #1024 ; 0x400
f7a: 462a mov r2, r5
f7c: f7ff ef30 blx de0 <snprintf@plt>
f80: 4628 mov r0, r5
f82: 4621 mov r1, r4
f84: e8bd 40b0 ldmia.w sp!, {r4, r5, r7, lr}
f88: f001 ba96 b.w 24b8 <_Unwind_GetTextRelBase@@Base+0x8>
f8c: bdb0 pop {r4, r5, r7, pc}
f8e: bf00 nop
f90: 7325 strb r5, [r4, #12]
f92: 0000 movs r0, r0
f94: 6568 str r0, [r5, #84] ; 0x54
f96: 6c6c ldr r4, [r5, #68] ; 0x44
f98: 0a6f lsrs r7, r5, #9
f9a: 0000 movs r0, r0
...............
...............
```
对 `malloc` 函数的调用对应于指令 `blx dd4`。跳转到了地址 `dd4`。看看这个地址里有什么吧:
```
caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so
...............
...............
00000dd4 <malloc@plt>:
dd4: e28fc600 add ip, pc, #0, 12
dd8: e28cca03 add ip, ip, #12288 ; 0x3000
ddc: e5bcf1b4 ldr pc, [ip, #436]! ; 0x1b4
...............
...............
```
果然,跳转到了 `.plt` 中,经过了几次地址计算,最后跳转到了地址 `3f90` 中的值指向的地址处,`3f90` 是个函数指针。
稍微解释一下:因为 arm 处理器使用 3 级流水线,所以第一条指令取到的 `pc` 的值是当前执行的指令地址 + `8`。
于是:`dd4` + `8` + `3000` + `1b4` = `3f90`。
地址 `3f90` 在哪里呢:
```
caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so
...............
...............
00003f60 <.got>:
...
3f70: 00002604 andeq r2, r0, r4, lsl #12
3f74: 00002504 andeq r2, r0, r4, lsl #10
...
3f88: 00000da8 andeq r0, r0, r8, lsr #27
3f8c: 00000da8 andeq r0, r0, r8, lsr #27
3f90: 00000da8 andeq r0, r0, r8, lsr #27
...............
...............
```
果然,在 `.got` 里。
顺便再看一下 `.rel.plt`:
```
caikelun@debian:~$ arm-linux-androideabi-readelf -r ./libtest.so
Relocation section '.rel.plt' at offset 0xcb8 contains 30 entries:
Offset Info Type Sym.Value Sym. Name
00003f88 00000416 R_ARM_JUMP_SLOT 00000000 __cxa_atexit@LIBC
00003f8c 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_finalize@LIBC
00003f90 00000316 R_ARM_JUMP_SLOT 00000000 malloc@LIBC
...............
...............
```
`malloc` 的地址居然正好存放在 `3f90` 里,这绝对不是巧合啊!还等什么,赶紧改代码吧。我们的 main.c 应该改成这样:
```c
#include <test.h>
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
int main()
{
void **p = (void **)0x3f90;
*p = (void *)my_malloc; // do hook
say_hello();
return 0;
}
```
编译运行一下:
```
caikelun@debian:~$ adb push ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
Segmentation fault
caikelun@debian:~$
```
思路是正确的。但之所以还是失败了,是因为这段代码存在下面的 3 个问题:
1. `3f90` 是个相对内存地址,需要把它换算成绝对地址。
2. `3f90` 对应的绝对地址很可能没有写入权限,直接对这个地址赋值会引起段错误。
3. 新的函数地址即使赋值成功了,`my_malloc` 也不会被执行,因为处理器有指令缓存(instruction cache)。
我们需要解决这些问题。
## 内存
### 基地址
在进程的内存空间中,各种 ELF 的加载地址是随机的,只有在运行时才能拿到加载地址,也就是**基地址**。我们需要知道 ELF 的基地址,才能将相对地址换算成绝对地址。
没有错,熟悉 Linux 开发的聪明的你一定知道,我们可以直接调用 `dl_iterate_phdr`。详细的定义见 [这里](http://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html)。
嗯,先等等,多年的 Android 开发被坑经历告诉我们,还是再看一眼 NDK 里的 `linker.h` 头文件吧:
```c
#if defined(__arm__)
#if __ANDROID_API__ >= 21
int dl_iterate_phdr(int (*__callback)(struct dl_phdr_info*, size_t, void*), void* __data) __INTRODUCED_IN(21);
#endif /* __ANDROID_API__ >= 21 */
#else
int dl_iterate_phdr(int (*__callback)(struct dl_phdr_info*, size_t, void*), void* __data);
#endif
```
为什么?!ARM 架构的 Android 5.0 以下版本居然不支持 `dl_iterate_phdr`!我们的 APP 可是要支持 Android 4.0 以上的所有版本啊。特别是 ARM,怎么能不支持呢?!这还让不让人写代码啦!
幸运的是,我们想到了,我们还可以解析 `/proc/self/maps`:
```
root@android:/ # ps | grep main
ps | grep main
shell 7884 7882 2616 1016 hrtimer_na b6e83824 S /data/local/tmp/main
root@android:/ # cat /proc/7884/maps
cat /proc/7884/maps
address perms offset dev inode pathname
---------------------------------------------------------------------
...........
...........
b6e42000-b6eb5000 r-xp 00000000 b3:17 57457 /system/lib/libc.so
b6eb5000-b6eb9000 r--p 00072000 b3:17 57457 /system/lib/libc.so
b6eb9000-b6ebc000 rw-p 00076000 b3:17 57457 /system/lib/libc.so
b6ec6000-b6ec9000 r-xp 00000000 b3:19 753708 /data/local/tmp/libtest.so
b6ec9000-b6eca000 r--p 00002000 b3:19 753708 /data/local/tmp/libtest.so
b6eca000-b6ecb000 rw-p 00003000 b3:19 753708 /data/local/tmp/libtest.so
b6f03000-b6f20000 r-xp 00000000 b3:17 32860 /system/bin/linker
b6f20000-b6f21000 r--p 0001c000 b3:17 32860 /system/bin/linker
b6f21000-b6f23000 rw-p 0001d000 b3:17 32860 /system/bin/linker
b6f25000-b6f26000 r-xp 00000000 b3:19 753707 /data/local/tmp/main
b6f26000-b6f27000 r--p 00000000 b3:19 753707 /data/local/tmp/main
becd5000-becf6000 rw-p 00000000 00:00 0 [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
...........
...........
```
maps 返回的是指定进程的内存空间中 `mmap` 的映射信息,包括各种动态库、可执行文件(如:linker),栈空间,堆空间,甚至还包括字体文件。maps 格式的详细说明见 [这里](http://man7.org/linux/man-pages/man5/proc.5.html)。
我们的 libtest.so 在 maps 中有 3 行记录。offset 为 `0` 的第一行的起始地址 `b6ec6000` 在**绝大多数情况下**就是我们寻找的**基地址**。
### 内存访问权限
maps 返回的信息中已经包含了权限访问信息。如果要执行 hook,就需要写入的权限,可以使用 `mprotect` 来完成:
```c
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
```
注意修改内存访问权限时,只能以“页”为单位。`mprotect` 的详细说明见 [这里](http://man7.org/linux/man-pages/man2/mprotect.2.html)。
### 指令缓存
注意 `.got` 和 `.data` 的 section 类型是 `PROGBITS`,也就是执行代码。处理器可能会对这部分数据做缓存。修改内存地址后,我们需要清除处理器的指令缓存,让处理器重新从内存中读取这部分指令。方法是调用 `__builtin___clear_cache`:
```c
void __builtin___clear_cache (char *begin, char *end);
```
注意清除指令缓存时,也只能以“页”为单位。`__builtin___clear_cache` 的详细说明见 [这里](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)。
## 验证
### 修改 main.c
我们把 `main.c` 修改为:
```c
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <test.h>
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
void hook()
{
char line[512];
FILE *fp;
uintptr_t base_addr = 0;
uintptr_t addr;
//find base address of libtest.so
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while(fgets(line, sizeof(line), fp))
{
if(NULL != strstr(line, "libtest.so") &&
sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
if(0 == base_addr) return;
//the absolute address
addr = base_addr + 0x3f90;
//add write permission
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
//replace the function address
*(void **)addr = my_malloc;
//clear instruction cache
__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
int main()
{
hook();
say_hello();
return 0;
}
```
重新编译运行:
```
caikelun@debian:~$ adb push ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
1024 bytes memory are allocated by libtest.so
hello
caikelun@debian:~$
```
是的,成功了!我们并没有修改 libtest.so 的代码,甚至没有重新编译它。我们仅仅修改了 main 程序。
libtest.so 和 main 的源码放在 github 上,可以从 [这里](https://github.com/iqiyi/xhook/tree/master/docs/overview/code) 获取到。(根据你使用的编译器不同,或者编译器的版本不同,生成的 libtest.so 中,也许 `malloc` 对应的地址不再是 `0x3f90`,这时你需要先用 readelf 确认,然后再到 `main.c` 中修改。)
### 使用 xhook
当然,我们已经开源了一个叫 xhook 的工具库。使用 xhook,你可以更优雅的完成对 libtest.so 的 hook 操作,也不必担心硬编码 `0x3f90` 导致的兼容性问题。
```c
#include <stdlib.h>
#include <stdio.h>
#include <test.h>
#include <xhook.h>
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
int main()
{
xhook_register(".*/libtest\\.so$", "malloc", my_malloc, NULL);
xhook_refresh(0);
say_hello();
return 0;
}
```
xhook 支持 armeabi, armeabi-v7a 和 arm64-v8a。支持 Android 4.0 (含) 以上版本 (API level >= 14)。经过了产品级的稳定性和兼容性验证。可以在 [这里](https://github.com/iqiyi/xhook) 获取 `xhook`。
总结一下 xhook 中执行 PLT hook 的流程:
1. 读 maps,获取 ELF 的内存首地址(start address)。
2. 验证 ELF 头信息。
3. 从 PHT 中找到类型为 `PT_LOAD` 且 offset 为 `0` 的 segment。计算 ELF 基地址。
4. 从 PHT 中找到类型为 `PT_DYNAMIC` 的 segment,从中获取到 `.dynamic` section,从 `.dynamic` section中获取其他各项 section 对应的内存地址。
5. 在 `.dynstr` section 中找到需要 hook 的 symbol 对应的 index 值。
6. 遍历所有的 `.relxxx` section(重定位 section),查找 symbol index 和 symbol type 都匹配的项,对于这项重定位项,执行 hook 操作。hook 流程如下:
* 读 maps,确认当前 hook 地址的内存访问权限。
* 如果权限不是可读也可写,则用 `mprotect` 修改访问权限为可读也可写。
* 如果调用方需要,就保留 hook 地址当前的值,用于返回。
* 将 hook 地址的值替换为新的值。(执行 hook)
* 如果之前用 `mprotect` 修改过内存访问权限,现在还原到之前的权限。
* 清除 hook 地址所在内存页的处理器指令缓存。
## FAQ
### 可以直接从文件中读取 ELF 信息吗?
可以。而且对于格式解析来说,读文件是最稳妥的方式,因为 ELF 在运行时,原理上有很多 section 不需要一直保留在内存中,可以在加载完之后就从内存中丢弃,这样可以节省少量的内存。但是从实践的角度出发,各种平台的动态链接器和加载器,都不会这么做,可能它们认为增加的复杂度得不偿失。所以我们从内存中读取各种 ELF 信息就可以了,读文件反而增加了性能损耗。另外,某些系统库 ELF 文件,APP 也不一定有访问权限。
### 计算基地址的精确方法是什么?
正如你已经注意到的,前面介绍 libtest.so 基地址获取时,为了简化概念和编码方便,用了“**绝大多数情况下**”这种不应该出现的描述方式。对于 hook 来说,精确的基地址计算流程是:
1. 在 maps 中找到找到 offset 为 `0`,且 `pathname` 为目标 ELF 的行。保存该行的 start address 为 `p0`。
2. 找出 ELF 的 PHT 中第一个类型为 `PT_LOAD` 且 offset 为 `0` 的 segment,保存该 segment 的虚拟内存相对地址(`p_vaddr`)为 `p1`。
3. `p0` - `p1` 即为该 ELF 当前的基地址。
绝大多数的 ELF 第一个 `PT_LOAD` segment 的 `p_vaddr` 都是 `0`。
另外,之所以要在 maps 里找 offset 为 `0` 的行,是因为我们在执行 hook 之前,希望对内存中的 ELF 文件头进行校验,确保当前操作的是一个有效的 ELF,而这种 ELF 文件头只能出现在 offset 为 `0` 的 mmap 区域。
可以在 Android linker 的源码中搜索“load_bias”,可以找到很多详细的注释说明,也可以参考 linker 中对 `load_bias_` 变量的赋值程序逻辑。
### 目标 ELF 使用的编译选项对 hook 有什么影响?
会有一些影响。
对于外部函数的调用,可以分为 3 中情况:
1. 直接调用。无论编译选项如何,都可以被 hook 到。外部函数地址始终保存在 `.got` 中。
2. 通过全局函数指针调用。无论编译选项如何,都可以被 hook 到。外部函数地址始终保存在 `.data` 中。
3. 通过局部函数指针调用。如果编译选项为 -O2(默认值),调用将被优化为直接调用(同情况 1)。如果编译选项为 -O0,则在执行 hook 前已经被赋值到临时变量中的外部函数的指针,通过 PLT 方式无法 hook;对于执行 hook 之后才被赋值的,可以通过 PLT 方式 hook。
一般情况下,产品级的 ELF 很少会使用 -O0 进行编译,所以也不必太纠结。但是如果你希望你的 ELF 尽量不被别人 PLT hook,那可以试试使用 -O0 来编译,然后尽量早的将外部函数的指针赋值给局部函数指针变量,之后一直使用这些局部函数指针来访问外部函数。
总之,查看 C/C++ 的源代码对这个问题的理解没有意义,需要查看使用不同的编译选项后,生成的 ELF 的反汇编输出,比较它们的区别,才能知道哪些情况由于什么原因导致无法被 PLT hook。
### hook 时遇到偶发的段错误是什么原因?如何处理?
我们有时会遇到这样的问题:
* 读取 `/proc/self/maps` 后发现某个内存区域的访问权限为**可读**,当我们读取该区域的内容做 ELF 文件头校验时,发生了段错误(sig: SIGSEGV, code: SEGV_ACCERR)。
* 已经用 `mprotect()` 修改了某个内存区域的访问权限为**可写**,`mprotect()` 返回修改成功,然后再次读取 `/proc/self/maps` 确认对应内存区域的访问权限确实为**可写**,执行写入操作(替换函数指针,执行 hook)时发生段错误(sig: SIGSEGV, code: SEGV_ACCERR)。
* 读取和验证 ELF 文件头成功了,根据 ELF 头中的相对地址值,进一步读取 PHT 或者 `.dynamic` section 时发生段错误(sig: SIGSEGV, code: SEGV_ACCERR 或 SEGV_MAPERR)。
可能的原因是:
* 进程的内存空间是多线程共享的,我们在执行 hook 时,其他线程(甚至 linker)可能正在执行 `dlclose()`,或者正在用 `mprotect()` 修改这块内存区域的访问权限。
* 不同厂家、机型、版本的 Android ROM 可能有未公开的行为,比如在某些情况下对某些内存区域存在**写保护**或者**读保护**机制,而这些保护机制并不反应在 `/proc/self/maps` 的内容中。
问题分析:
* 读内存时发生段错误其实是无害的。
* 我在 hook 执行的流程中,需要直接通过计算内存地址的方式来写入数据的地方只有一处:即替换函数指针的最关键的那一行。只要其他地方的逻辑没有错误,这里就算写入失败了,也不会对其他内存区域造成破坏。
* 加载运行安卓平台的 APP 进程时,加载器已经向我们注入了 signal handler 的注册逻辑,以便 APP 崩溃时与系统的 `debuggerd` 守护进程通讯,`debuggerd` 使用 `ptrace` 调试崩溃进程,获取需要的崩溃现场信息,记录到 tombstone 文件中,然后 APP 自杀。
* 系统会精确的把段错误信号发送给“发生段错误的线程”。
* 我们希望能有一种隐秘的,且可控的方式来避免段错误引起 APP 崩溃。
先明确一个观点:不要只从应用层程序开发的角度来看待段错误,段错误不是洪水猛兽,它只是内核与用户进程的一种正常的交流方式。当用户进程访问了无权限或未 mmap 的虚拟内存地址时,内核向用户进程发送 SIGSEGV 信号,来通知用户进程,仅此而已。只要段错误的发生位置是可控的,我们就可以在用户进程中处理它。
解决方案:
* 当 hook 逻辑进入我们认为的危险区域(直接计算内存地址进行读写)之前,通过一个全局 `flag` 来进行标记,离开危险区域后将 `flag` 复位。
* 注册我们自己的 signal handler,只捕获段错误。在 signal handler 中,通过判断 `flag` 的值,来判断当前线程逻辑是否在危险区域中。如果是,就用 `siglongjmp` 跳出 signal handler,直接跳到我们预先设置好的“危险区域以外的下一行代码处”;如果不是,就恢复之前加载器向我们注入的 signal handler,然后直接返回,这时系统会再次向我们的线程发送段错误信号,由于已经恢复了之前的 signal handler,这时会进入默认的系统 signal handler 中走正常逻辑。
* 我们把这种机制简称为:SFP (segmentation fault protection,段错误保护)
* 注意:SFP需要一个开关,让我们随时能够开启和关闭它。在 APP 开发调试阶段,SFP 应该始终被关闭,这样就不会错过由于编码失误导致的段错误,这些错误是应该被修复的;在正式上线后 SFP 应该被开启,这样能保证 APP 不会崩溃。(当然,以采样的形式部分关闭 SFP,用以观察和分析 hook 机制本身导致的崩溃,也是可以考虑的)
具体代码可以参考 `xhook` 中的实现,在源码中搜索 `siglongjmp` 和 `sigsetjmp`。
### ELF 内部函数之间的调用能 hook 吗?
我们这里介绍的 hook 方式为 PLT hook,不能做 ELF 内部函数之间调用的 hook。
inline hook 可以做到,你需要先知道想要 hook 的内部函数符号名(symbol name)或者地址,然后可以 hook。
有很多开源和非开源的 inline hook 实现,比如:
* substrate:http://www.cydiasubstrate.com/
* frida:https://www.frida.re/
inline hook 方案强大的同时可能带来以下的问题:
* 由于需要直接解析和修改 ELF 中的机器指令(汇编码),对于不同架构的处理器、处理器指令集、编译器优化选项、操作系统版本可能存在不同的兼容性和稳定性问题。
* 发生问题后可能难以分析和定位,一些知名的 inline hook 方案是闭源的。
* 实现起来相对复杂,难度也较大。
* 未知的坑相对较多,这个可以自行 google。
建议如果 PLT hook 够用的话,就不必尝试 inline hook 了。
## 联系作者
* caikelun@gmail.com
* https://github.com/caikelun
## 许可证
Copyright (c) 2018, 爱奇艺, Inc. All rights reserved.
本文使用 [Creative Commons 许可证](https://creativecommons.org/licenses/by/4.0/legalcode) 授权。
================================================
FILE: docs/overview/code/.gitignore
================================================
.DS_Store
main/libs/
main/obj/
libtest/libs/
libtest/obj/
================================================
FILE: docs/overview/code/build.sh
================================================
#!/bin/bash
ndk-build -C ./libtest/jni
ndk-build -C ./main/jni
================================================
FILE: docs/overview/code/clean.sh
================================================
#!/bin/bash
ndk-build -C ./main/jni clean
ndk-build -C ./libtest/jni clean
================================================
FILE: docs/overview/code/libtest/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
LOCAL_CFLAGS := -Wall -Wextra -Werror #-O0
LOCAL_CONLYFLAGS := -std=c11
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: docs/overview/code/libtest/jni/Application.mk
================================================
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14
================================================
FILE: docs/overview/code/libtest/jni/test.c
================================================
#include <stdlib.h>
#include <stdio.h>
void say_hello()
{
char *buf = malloc(1024);
if(NULL != buf)
{
snprintf(buf, 1024, "%s", "hello\n");
printf("%s", buf);
}
}
================================================
FILE: docs/overview/code/libtest/jni/test.h
================================================
#ifndef TEST_H
#define TEST_H 1
#ifdef __cplusplus
extern "C" {
#endif
void say_hello();
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: docs/overview/code/main/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := $(LOCAL_PATH)/../../libtest/libs/$(TARGET_ARCH_ABI)/libtest.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../libtest/jni
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := main
LOCAL_SRC_FILES := main.c
LOCAL_SHARED_LIBRARIES := test
LOCAL_CFLAGS := -Wall -Wextra -Werror -fPIE
LOCAL_CONLYFLAGS := -std=c11
LOCAL_LDLIBS += -fPIE -pie
include $(BUILD_EXECUTABLE)
================================================
FILE: docs/overview/code/main/jni/Application.mk
================================================
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14
================================================
FILE: docs/overview/code/main/jni/main.c
================================================
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <test.h>
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
void hook()
{
char line[512];
FILE *fp;
uintptr_t base_addr = 0;
uintptr_t addr;
//find base address of libtest.so
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while(fgets(line, sizeof(line), fp))
{
if(NULL != strstr(line, "libtest.so") &&
sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
if(0 == base_addr) return;
//the absolute address
addr = base_addr + 0x3f90;
//add write permission
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
//replace the function address
*(void **)addr = my_malloc;
//clear instruction cache
__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
int main()
{
hook();
say_hello();
return 0;
}
================================================
FILE: docs/overview/code/run.sh
================================================
#!/bin/bash
adb push ./main/libs/armeabi-v7a/libtest.so ./main/libs/armeabi-v7a/main /data/local/tmp
adb shell "chmod +x /data/local/tmp/main"
adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
================================================
FILE: install_libs.sh
================================================
#!/bin/bash
mkdir -p ./xhookwrapper/xhook/libs/armeabi
mkdir -p ./xhookwrapper/xhook/libs/armeabi-v7a
mkdir -p ./xhookwrapper/xhook/libs/arm64-v8a
mkdir -p ./xhookwrapper/xhook/libs/x86
mkdir -p ./xhookwrapper/xhook/libs/x86_64
cp -f ./libxhook/libs/armeabi/libxhook.so ./xhookwrapper/xhook/libs/armeabi/
cp -f ./libxhook/libs/armeabi-v7a/libxhook.so ./xhookwrapper/xhook/libs/armeabi-v7a/
cp -f ./libxhook/libs/arm64-v8a/libxhook.so ./xhookwrapper/xhook/libs/arm64-v8a/
cp -f ./libxhook/libs/x86/libxhook.so ./xhookwrapper/xhook/libs/x86/
cp -f ./libxhook/libs/x86_64/libxhook.so ./xhookwrapper/xhook/libs/x86_64/
mkdir -p ./xhookwrapper/biz/libs/armeabi
mkdir -p ./xhookwrapper/biz/libs/armeabi-v7a
mkdir -p ./xhookwrapper/biz/libs/arm64-v8a
mkdir -p ./xhookwrapper/biz/libs/x86
mkdir -p ./xhookwrapper/biz/libs/x86_64
cp -f ./libbiz/libs/armeabi/libbiz.so ./xhookwrapper/biz/libs/armeabi/
cp -f ./libbiz/libs/armeabi-v7a/libbiz.so ./xhookwrapper/biz/libs/armeabi-v7a/
cp -f ./libbiz/libs/arm64-v8a/libbiz.so ./xhookwrapper/biz/libs/arm64-v8a/
cp -f ./libbiz/libs/x86/libbiz.so ./xhookwrapper/biz/libs/x86/
cp -f ./libbiz/libs/x86_64/libbiz.so ./xhookwrapper/biz/libs/x86_64/
mkdir -p ./xhookwrapper/app/libs/armeabi
mkdir -p ./xhookwrapper/app/libs/armeabi-v7a
mkdir -p ./xhookwrapper/app/libs/arm64-v8a
mkdir -p ./xhookwrapper/app/libs/x86
mkdir -p ./xhookwrapper/app/libs/x86_64
cp -f ./libtest/libs/armeabi/libtest.so ./xhookwrapper/app/libs/armeabi/
cp -f ./libtest/libs/armeabi-v7a/libtest.so ./xhookwrapper/app/libs/armeabi-v7a/
cp -f ./libtest/libs/arm64-v8a/libtest.so ./xhookwrapper/app/libs/arm64-v8a/
cp -f ./libtest/libs/x86/libtest.so ./xhookwrapper/app/libs/x86/
cp -f ./libtest/libs/x86_64/libtest.so ./xhookwrapper/app/libs/x86_64/
================================================
FILE: libbiz/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := xhook
LOCAL_SRC_FILES := $(LOCAL_PATH)/../../libxhook/libs/$(TARGET_ARCH_ABI)/libxhook.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../libxhook/jni
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := biz
LOCAL_SRC_FILES := biz.c
LOCAL_SHARED_LIBRARIES := xhook
LOCAL_CFLAGS := -Wall -Wextra -Werror
LOCAL_CONLYFLAGS := -std=c11
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: libbiz/jni/Application.mk
================================================
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-14
================================================
FILE: libbiz/jni/biz.c
================================================
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include "xhook.h"
static int my_system_log_print(int prio, const char* tag, const char* fmt, ...)
{
va_list ap;
char buf[1024];
int r;
snprintf(buf, sizeof(buf), "[%s] %s", (NULL == tag ? "" : tag), (NULL == fmt ? "" : fmt));
va_start(ap, fmt);
r = __android_log_vprint(prio, "xhook_system", buf, ap);
va_end(ap);
return r;
}
static int my_libtest_log_print(int prio, const char* tag, const char* fmt, ...)
{
va_list ap;
char buf[1024];
int r;
snprintf(buf, sizeof(buf), "[%s] %s", (NULL == tag ? "" : tag), (NULL == fmt ? "" : fmt));
va_start(ap, fmt);
r = __android_log_vprint(prio, "xhook_libtest", buf, ap);
va_end(ap);
return r;
}
void Java_com_qiyi_biz_NativeHandler_start(JNIEnv* env, jobject obj)
{
(void)env;
(void)obj;
xhook_register("^/system/.*\\.so$", "__android_log_print", my_system_log_print, NULL);
xhook_register("^/vendor/.*\\.so$", "__android_log_print", my_system_log_print, NULL);
xhook_register(".*/libtest\\.so$", "__android_log_print", my_libtest_log_print, NULL);
//just for testing
xhook_ignore(".*/liblog\\.so$", "__android_log_print"); //ignore __android_log_print in liblog.so
xhook_ignore(".*/libjavacore\\.so$", NULL); //ignore all hooks in libjavacore.so
}
================================================
FILE: libtest/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
LOCAL_CFLAGS := -Wall -Wextra -Werror -O0
LOCAL_CONLYFLAGS := -std=c11
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: libtest/jni/Application.mk
================================================
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-14
================================================
FILE: libtest/jni/test.c
================================================
#include <unistd.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
typedef int (*my_log_t)(int prio, const char* tag, const char* fmt, ...);
my_log_t my_global_log_ptr = (my_log_t)__android_log_print;
static void *new_thread_func(void *arg)
{
(void)arg;
my_log_t my_local_log_ptr2 = (my_log_t)__android_log_print;
unsigned int i = 0;
while(1)
{
my_log_t my_local_log_ptr = (my_log_t)__android_log_print;
__android_log_print(ANDROID_LOG_DEBUG, "mytest", "call directly. %u\n", i);
my_global_log_ptr(ANDROID_LOG_DEBUG, "mytest", "call from global ptr. %u\n", i);
my_local_log_ptr(ANDROID_LOG_DEBUG, "mytest", "call from local ptr. %u\n", i);
my_local_log_ptr2(ANDROID_LOG_DEBUG, "mytest", "call from local ptr2. %u (definitely failed when compiled with -O0)\n", i);
i++;
sleep(1);
}
return NULL;
}
void Java_com_qiyi_test_NativeHandler_start(JNIEnv* env, jobject obj)
{
(void)env;
(void)obj;
pthread_t tid;
pthread_create(&tid, NULL, &new_thread_func, NULL);
}
================================================
FILE: libxhook/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := xhook
LOCAL_SRC_FILES := xhook.c \
xh_core.c \
xh_elf.c \
xh_jni.c \
xh_log.c \
xh_util.c \
xh_version.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
LOCAL_CONLYFLAGS := -std=c11
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
================================================
FILE: libxhook/jni/Application.mk
================================================
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-14
================================================
FILE: libxhook/jni/queue.h
================================================
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* $FreeBSD: stable/9/sys/sys/queue.h 252365 2013-06-29 04:25:40Z lstewart $
*/
#ifndef QUEUE_H
#define QUEUE_H
/* #include <sys/cdefs.h> */
#define __containerof(ptr, type, field) ((type *)((char *)(ptr) - ((char *)&((type *)0)->field)))
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may be traversed in either direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - + - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_FROM + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_FROM_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_FROM - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _FOREACH_REVERSE_FROM_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT - - + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE + + + +
* _SWAP + + + +
*
*/
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type, qual) \
struct name { \
struct type *qual slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type, qual) \
struct { \
struct type *qual sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
#define SLIST_REMOVE(head, elm, type, field) do { \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = SLIST_FIRST((head)); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
} while (0)
#define SLIST_SWAP(head1, head2, type) do { \
struct type *swap_first = SLIST_FIRST(head1); \
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
SLIST_FIRST(head2) = swap_first; \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type, qual) \
struct name { \
struct type *qual lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type, qual) \
struct { \
struct type *qual le_next; /* next element */ \
struct type *qual *le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_PREV(elm, head, type, field) \
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
__containerof((elm)->field.le_prev, struct type, field.le_next))
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INSERT_HEAD(head, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL) \
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_REMOVE(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
struct type *swap_tmp = LIST_FIRST((head1)); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type, qual) \
struct name { \
struct type *qual stqh_first;/* first element */ \
struct type *qual *stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type, qual) \
struct { \
struct type *qual stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? NULL : \
__containerof((head)->stqh_last, struct type, field.stqe_next))
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE(head, elm, type, field) do { \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
struct type *swap_first = STAILQ_FIRST(head1); \
struct type **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type, qual) \
struct name { \
struct type *qual tqh_first; /* first element */ \
struct type *qual *tqh_last; /* addr of last next element */ \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type, qual) \
struct { \
struct type *qual tqe_next; /* next element */ \
struct type *qual *tqe_prev; /* address of previous next element */ \
}
/*
* Tail queue functions.
*/
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
} \
} while (0)
#define TAILQ_REMOVE(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
struct type *swap_first = (head1)->tqh_first; \
struct type **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif
================================================
FILE: libxhook/jni/tree.h
================================================
/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
/* $FreeBSD: stable/9/sys/sys/tree.h 189204 2009-03-01 04:57:23Z bms $ */
/*-
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TREE_H
#define TREE_H
/* #include <sys/cdefs.h> */
#ifndef __unused
#define __unused __attribute__((__unused__))
#endif
/*
* This file defines data structures for different types of trees:
* splay trees and red-black trees.
*
* A splay tree is a self-organizing data structure. Every operation
* on the tree causes a splay to happen. The splay moves the requested
* node to the root of the tree and partly rebalances it.
*
* This has the benefit that request locality causes faster lookups as
* the requested nodes move to the top of the tree. On the other hand,
* every lookup causes memory writes.
*
* The Balance Theorem bounds the total access time for m operations
* and n inserts on an initially empty tree as O((m + n)lg n). The
* amortized cost for a sequence of m accesses to a splay tree is O(lg n);
*
* A red-black tree is a binary search tree with the node color as an
* extra attribute. It fulfills a set of conditions:
* - every search path from the root to a leaf consists of the
* same number of black nodes,
* - each red node (except for the root) has a black parent,
* - each leaf node is black.
*
* Every operation on a red-black tree is bounded as O(lg n).
* The maximum height of a red-black tree is 2lg (n+1).
*/
#define SPLAY_HEAD(name, type) \
struct name { \
struct type *sph_root; /* root of the tree */ \
}
#define SPLAY_INITIALIZER(root) \
{ NULL }
#define SPLAY_INIT(root) do { \
(root)->sph_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ENTRY(type) \
struct { \
struct type *spe_left; /* left element */ \
struct type *spe_right; /* right element */ \
}
#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
#define SPLAY_ROOT(head) (head)->sph_root
#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKLEFT(head, tmp, field) do { \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKRIGHT(head, tmp, field) do { \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define SPLAY_PROTOTYPE(name, type, field, cmp) \
void name##_SPLAY(struct name *, struct type *); \
void name##_SPLAY_MINMAX(struct name *, int); \
struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
\
/* Finds the node with the same key as elm */ \
static __inline struct type * \
name##_SPLAY_FIND(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) \
return(NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) \
return (head->sph_root); \
return (NULL); \
} \
\
static __inline struct type * \
name##_SPLAY_NEXT(struct name *head, struct type *elm) \
{ \
name##_SPLAY(head, elm); \
if (SPLAY_RIGHT(elm, field) != NULL) { \
elm = SPLAY_RIGHT(elm, field); \
while (SPLAY_LEFT(elm, field) != NULL) { \
elm = SPLAY_LEFT(elm, field); \
} \
} else \
elm = NULL; \
return (elm); \
} \
\
static __inline struct type * \
name##_SPLAY_MIN_MAX(struct name *head, int val) \
{ \
name##_SPLAY_MINMAX(head, val); \
return (SPLAY_ROOT(head)); \
}
/* Main splay operation.
* Moves node close to the key of elm to top
*/
#define SPLAY_GENERATE(name, type, field, cmp) \
struct type * \
name##_SPLAY_INSERT(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) { \
SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
} else { \
int __comp; \
name##_SPLAY(head, elm); \
__comp = (cmp)(elm, (head)->sph_root); \
if(__comp < 0) { \
SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
SPLAY_RIGHT(elm, field) = (head)->sph_root; \
SPLAY_LEFT((head)->sph_root, field) = NULL; \
} else if (__comp > 0) { \
SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT(elm, field) = (head)->sph_root; \
SPLAY_RIGHT((head)->sph_root, field) = NULL; \
} else \
return ((head)->sph_root); \
} \
(head)->sph_root = (elm); \
return (NULL); \
} \
\
struct type * \
name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *__tmp; \
if (SPLAY_EMPTY(head)) \
return (NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) { \
if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
} else { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
name##_SPLAY(head, elm); \
SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
} \
return (elm); \
} \
return (NULL); \
} \
\
void \
name##_SPLAY(struct name *head, struct type *elm) \
{ \
struct type __node, *__left, *__right, *__tmp; \
int __comp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) > 0){ \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
} \
\
/* Splay with either the minimum or the maximum element \
* Used to find minimum or maximum element in tree. \
*/ \
void name##_SPLAY_MINMAX(struct name *head, int __comp) \
{ \
struct type __node, *__left, *__right, *__tmp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while (1) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp > 0) { \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
}
#define SPLAY_NEGINF -1
#define SPLAY_INF 1
#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
#define SPLAY_FOREACH(x, name, head) \
for ((x) = SPLAY_MIN(name, head); \
(x) != NULL; \
(x) = SPLAY_NEXT(name, head, x))
/* Macros that define a red-black tree */
#define RB_HEAD(name, type) \
struct name { \
struct type *rbh_root; /* root of the tree */ \
}
#define RB_INITIALIZER(root) \
{ NULL }
#define RB_INIT(root) do { \
(root)->rbh_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define RB_BLACK 0
#define RB_RED 1
#define RB_ENTRY(type) \
struct { \
struct type *rbe_left; /* left element */ \
struct type *rbe_right; /* right element */ \
struct type *rbe_parent; /* parent element */ \
int rbe_color; /* node color */ \
}
#define RB_LEFT(elm, field) (elm)->field.rbe_left
#define RB_RIGHT(elm, field) (elm)->field.rbe_right
#define RB_PARENT(elm, field) (elm)->field.rbe_parent
#define RB_COLOR(elm, field) (elm)->field.rbe_color
#define RB_ROOT(head) (head)->rbh_root
#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
#define RB_SET(elm, parent, field) do { \
RB_PARENT(elm, field) = parent; \
RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
RB_COLOR(elm, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#define RB_SET_BLACKRED(black, red, field) do { \
RB_COLOR(black, field) = RB_BLACK; \
RB_COLOR(red, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#ifndef RB_AUGMENT
#define RB_AUGMENT(x) do {} while (0)
#endif
#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
(tmp) = RB_RIGHT(elm, field); \
if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \
RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_LEFT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
(tmp) = RB_LEFT(elm, field); \
if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \
RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_RIGHT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define RB_PROTOTYPE(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)
#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \
attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
attr struct type *name##_RB_REMOVE(struct name *, struct type *); \
attr struct type *name##_RB_INSERT(struct name *, struct type *); \
attr struct type *name##_RB_FIND(struct name *, struct type *); \
attr struct type *name##_RB_NFIND(struct name *, struct type *); \
attr struct type *name##_RB_NEXT(struct type *); \
attr struct type *name##_RB_PREV(struct type *); \
attr struct type *name##_RB_MINMAX(struct name *, int); \
\
/* Main rb operation.
* Moves node close to the key of elm to top
*/
#define RB_GENERATE(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp,)
#define RB_GENERATE_STATIC(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)
#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
attr void \
name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
{ \
struct type *parent, *gparent, *tmp; \
while ((parent = RB_PARENT(elm, field)) != NULL && \
RB_COLOR(parent, field) == RB_RED) { \
gparent = RB_PARENT(parent, field); \
if (parent == RB_LEFT(gparent, field)) { \
tmp = RB_RIGHT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_RIGHT(parent, field) == elm) { \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_RIGHT(head, gparent, tmp, field); \
} else { \
tmp = RB_LEFT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_LEFT(parent, field) == elm) { \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_LEFT(head, gparent, tmp, field); \
} \
} \
RB_COLOR(head->rbh_root, field) = RB_BLACK; \
} \
\
attr void \
name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
{ \
struct type *tmp; \
while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
elm != RB_ROOT(head)) { \
if (RB_LEFT(parent, field) == elm) { \
tmp = RB_RIGHT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = RB_RIGHT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
struct type *oleft; \
if ((oleft = RB_LEFT(tmp, field)) \
!= NULL) \
RB_COLOR(oleft, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_RIGHT(head, tmp, oleft, field);\
tmp = RB_RIGHT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_RIGHT(tmp, field)) \
RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_LEFT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} else { \
tmp = RB_LEFT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = RB_LEFT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
struct type *oright; \
if ((oright = RB_RIGHT(tmp, field)) \
!= NULL) \
RB_COLOR(oright, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_LEFT(head, tmp, oright, field);\
tmp = RB_LEFT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_LEFT(tmp, field)) \
RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_RIGHT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} \
} \
if (elm) \
RB_COLOR(elm, field) = RB_BLACK; \
} \
\
attr struct type * \
name##_RB_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *child, *parent, *old = elm; \
int color; \
if (RB_LEFT(elm, field) == NULL) \
child = RB_RIGHT(elm, field); \
else if (RB_RIGHT(elm, field) == NULL) \
child = RB_LEFT(elm, field); \
else { \
struct type *left; \
elm = RB_RIGHT(elm, field); \
while ((left = RB_LEFT(elm, field)) != NULL) \
elm = left; \
child = RB_RIGHT(elm, field); \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
if (RB_PARENT(elm, field) == old) \
parent = elm; \
(elm)->field = (old)->field; \
if (RB_PARENT(old, field)) { \
if (RB_LEFT(RB_PARENT(old, field), field) == old)\
RB_LEFT(RB_PARENT(old, field), field) = elm;\
else \
RB_RIGHT(RB_PARENT(old, field), field) = elm;\
RB_AUGMENT(RB_PARENT(old, field)); \
} else \
RB_ROOT(head) = elm; \
RB_PARENT(RB_LEFT(old, field), field) = elm; \
if (RB_RIGHT(old, field)) \
RB_PARENT(RB_RIGHT(old, field), field) = elm; \
if (parent) { \
left = parent; \
do { \
RB_AUGMENT(left); \
} while ((left = RB_PARENT(left, field)) != NULL); \
} \
goto color; \
} \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
color: \
if (color == RB_BLACK) \
name##_RB_REMOVE_COLOR(head, parent, child); \
return (old); \
} \
\
/* Inserts a node into the RB tree */ \
attr struct type * \
name##_RB_INSERT(struct name *head, struct type *elm) \
{ \
struct type *tmp; \
struct type *parent = NULL; \
int comp = 0; \
tmp = RB_ROOT(head); \
while (tmp) { \
parent = tmp; \
comp = (cmp)(elm, parent); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
RB_SET(elm, parent, field); \
if (parent != NULL) { \
if (comp < 0) \
RB_LEFT(parent, field) = elm; \
else \
RB_RIGHT(parent, field) = elm; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = elm; \
name##_RB_INSERT_COLOR(head, elm); \
return (NULL); \
} \
\
/* Finds the node with the same key as elm */ \
attr struct type * \
name##_RB_FIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (NULL); \
} \
\
/* Finds the first node greater than or equal to the search key */ \
attr struct type * \
name##_RB_NFIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *res = NULL; \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) { \
res = tmp; \
tmp = RB_LEFT(tmp, field); \
} \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (res); \
} \
\
/* ARGSUSED */ \
attr struct type * \
name##_RB_NEXT(struct type *elm) \
{ \
if (RB_RIGHT(elm, field)) { \
elm = RB_RIGHT(elm, field); \
while (RB_LEFT(elm, field)) \
elm = RB_LEFT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
} \
\
/* ARGSUSED */ \
attr struct type * \
name##_RB_PREV(struct type *elm) \
{ \
if (RB_LEFT(elm, field)) { \
elm = RB_LEFT(elm, field); \
while (RB_RIGHT(elm, field)) \
elm = RB_RIGHT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
} \
\
attr struct type * \
name##_RB_MINMAX(struct name *head, int val) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *parent = NULL; \
while (tmp) { \
parent = tmp; \
if (val < 0) \
tmp = RB_LEFT(tmp, field); \
else \
tmp = RB_RIGHT(tmp, field); \
} \
return (parent); \
}
#define RB_NEGINF -1
#define RB_INF 1
#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
#define RB_PREV(name, x, y) name##_RB_PREV(y)
#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
#define RB_FOREACH(x, name, head) \
for ((x) = RB_MIN(name, head); \
(x) != NULL; \
(x) = name##_RB_NEXT(x))
#define RB_FOREACH_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_SAFE(x, name, head, y) \
for ((x) = RB_MIN(name, head); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE(x, name, head) \
for ((x) = RB_MAX(name, head); \
(x) != NULL; \
(x) = name##_RB_PREV(x))
#define RB_FOREACH_REVERSE_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
for ((x) = RB_MAX(name, head); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#endif
================================================
FILE: libxhook/jni/xh_core.c
================================================
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2018-04-11.
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <regex.h>
#include <setjmp.h>
#include <errno.h>
#include "queue.h"
#include "tree.h"
#include "xh_errno.h"
#include "xh_log.h"
#include "xh_elf.h"
#include "xh_version.h"
#include "xh_core.h"
#define XH_CORE_DEBUG 0
//registered hook info collection
typedef struct xh_core_hook_info
{
#if XH_CORE_DEBUG
char *pathname_regex_str;
#endif
regex_t pathname_regex;
char *symbol;
void *new_func;
void **old_func;
TAILQ_ENTRY(xh_core_hook_info,) link;
} xh_core_hook_info_t;
typedef TAILQ_HEAD(xh_core_hook_info_queue, xh_core_hook_info,) xh_core_hook_info_queue_t;
//ignored hook info collection
typedef struct xh_core_ignore_info
{
#if XH_CORE_DEBUG
char *pathname_regex_str;
#endif
regex_t pathname_regex;
char *symbol; //NULL meaning for all symbols
TAILQ_ENTRY(xh_core_ignore_info,) link;
} xh_core_ignore_info_t;
typedef TAILQ_HEAD(xh_core_ignore_info_queue, xh_core_ignore_info,) xh_core_ignore_info_queue_t;
//required info from /proc/self/maps
typedef struct xh_core_map_info
{
char *pathname;
uintptr_t base_addr;
xh_elf_t elf;
RB_ENTRY(xh_core_map_info) link;
} xh_core_map_info_t;
static __inline__ int xh_core_map_info_cmp(xh_core_map_info_t *a, xh_core_map_info_t *b)
{
return strcmp(a->pathname, b->pathname);
}
typedef RB_HEAD(xh_core_map_info_tree, xh_core_map_info) xh_core_map_info_tree_t;
RB_GENERATE_STATIC(xh_core_map_info_tree, xh_core_map_info, link, xh_core_map_info_cmp)
//signal handler for SIGSEGV
//for xh_elf_init(), xh_elf_hook(), xh_elf_check_elfheader()
static int xh_core_sigsegv_enable = 1; //enable by default
static struct sigaction xh_core_sigsegv_act_old;
static volatile int xh_core_sigsegv_flag = 0;
static sigjmp_buf xh_core_sigsegv_env;
static void xh_core_sigsegv_handler(int sig)
{
(void)sig;
if(xh_core_sigsegv_flag)
siglongjmp(xh_core_sigsegv_env, 1);
else
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
}
static int xh_core_add_sigsegv_handler()
{
struct sigaction act;
if(!xh_core_sigsegv_enable) return 0;
if(0 != sigemptyset(&act.sa_mask)) return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
act.sa_handler = xh_core_sigsegv_handler;
if(0 != sigaction(SIGSEGV, &act, &xh_core_sigsegv_act_old))
return (0 == errno ? XH_ERRNO_UNKNOWN : errno);
return 0;
}
static void xh_core_del_sigsegv_handler()
{
if(!xh_core_sigsegv_enable) return;
sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL);
}
static xh_core_hook_info_queue_t xh_core_hook_info = TAILQ_HEAD_INITIALIZER(xh_core_hook_info);
static xh_core_ignore_info_queue_t xh_core_ignore_info = TAILQ_HEAD_INITIALIZER(xh_core_ignore_info);
static xh_core_map_info_tree_t xh_core_map_info = RB_INITIALIZER(&xh_core_map_info);
static pthread_mutex_t xh_core_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t xh_core_cond = PTHREAD_COND_INITIALIZER;
static volatile int xh_core_inited = 0;
static volatile int xh_core_init_ok = 0;
static volatile int xh_core_async_inited = 0;
static volatile int xh_core_async_init_ok = 0;
static pthread_mutex_t xh_core_refresh_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t xh_core_refresh_thread_tid;
static volatile int xh_core_refresh_thread_running = 0;
static volatile int xh_core_refresh_thread_do = 0;
int xh_core_register(const char *pathname_regex_str, const char *symbol,
void *new_func, void **old_func)
{
xh_core_hook_info_t *hi;
regex_t regex;
if(NULL == pathname_regex_str || NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL;
if(xh_core_inited)
{
XH_LOG_ERROR("do not register hook after refresh(): %s, %s", pathname_regex_str, symbol);
return XH_ERRNO_INVAL;
}
if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
if(NULL == (hi = malloc(sizeof(xh_core_hook_info_t)))) return XH_ERRNO_NOMEM;
if(NULL == (hi->symbol = strdup(symbol)))
{
free(hi);
return XH_ERRNO_NOMEM;
}
#if XH_CORE_DEBUG
if(NULL == (hi->pathname_regex_str = strdup(pathname_regex_str)))
{
free(hi->symbol);
free(hi);
return XH_ERRNO_NOMEM;
}
#endif
hi->pathname_regex = regex;
hi->new_func = new_func;
hi->old_func = old_func;
pthread_mutex_lock(&xh_core_mutex);
TAILQ_INSERT_TAIL(&xh_core_hook_info, hi, link);
pthread_mutex_unlock(&xh_core_mutex);
return 0;
}
int xh_core_ignore(const char *pathname_regex_str, const char *symbol)
{
xh_core_ignore_info_t *ii;
regex_t regex;
if(NULL == pathname_regex_str) return XH_ERRNO_INVAL;
if(xh_core_inited)
{
XH_LOG_ERROR("do not ignore hook after refresh(): %s, %s", pathname_regex_str, symbol ? symbol : "ALL");
return XH_ERRNO_INVAL;
}
if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL;
if(NULL == (ii = malloc(sizeof(xh_core_ignore_info_t)))) return XH_ERRNO_NOMEM;
if(NULL != symbol)
{
if(NULL == (ii->symbol = strdup(symbol)))
{
free(ii);
return XH_ERRNO_NOMEM;
}
}
else
{
ii->symbol = NULL; //ignore all symbols
}
#if XH_CORE_DEBUG
if(NULL == (ii->pathname_regex_str = strdup(pathname_regex_str)))
{
free(ii->symbol);
free(ii);
return XH_ERRNO_NOMEM;
}
#endif
ii->pathname_regex = regex;
pthread_mutex_lock(&xh_core_mutex);
TAILQ_INSERT_TAIL(&xh_core_ignore_info, ii, link);
pthread_mutex_unlock(&xh_core_mutex);
return 0;
}
static int xh_core_check_elf_header(uintptr_t base_addr, const char *pathname)
{
if(!xh_core_sigsegv_enable)
{
return xh_elf_check_elfheader(base_addr);
}
else
{
int ret = XH_ERRNO_UNKNOWN;
xh_core_sigsegv_flag = 1;
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
{
ret = xh_elf_check_elfheader(base_addr);
}
else
{
ret = XH_ERRNO_SEGVERR;
XH_LOG_WARN("catch SIGSEGV when check_elfheader: %s", pathname);
}
xh_core_sigsegv_flag = 0;
return ret;
}
}
static void xh_core_hook_impl(xh_core_map_info_t *mi)
{
//init
if(0 != xh_elf_init(&(mi->elf), mi->base_addr, mi->pathname)) return;
//hook
xh_core_hook_info_t *hi;
xh_core_ignore_info_t *ii;
int ignore;
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
{
if(0 == regexec(&(hi->pathname_regex), mi->pathname, 0, NULL, 0))
{
ignore = 0;
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
{
if(0 == regexec(&(ii->pathname_regex), mi->pathname, 0, NULL, 0))
{
if(NULL == ii->symbol) //ignore all symbols
return;
if(0 == strcmp(ii->symbol, hi->symbol)) //ignore the current symbol
{
ignore = 1;
break;
}
}
}
if(0 == ignore)
xh_elf_hook(&(mi->elf), hi->symbol, hi->new_func, hi->old_func);
}
}
}
static void xh_core_hook(xh_core_map_info_t *mi)
{
if(!xh_core_sigsegv_enable)
{
xh_core_hook_impl(mi);
}
else
{
xh_core_sigsegv_flag = 1;
if(0 == sigsetjmp(xh_core_sigsegv_env, 1))
{
xh_core_hook_impl(mi);
}
else
{
XH_LOG_WARN("catch SIGSEGV when init or hook: %s", mi->pathname);
}
xh_core_sigsegv_flag = 0;
}
}
static void xh_core_refresh_impl()
{
char line[512];
FILE *fp;
uintptr_t base_addr;
uintptr_t prev_base_addr = 0;
char perm[5];
char prev_perm[5] = "---p";
unsigned long offset;
unsigned long prev_offset = 0;
int pathname_pos;
char *pathname;
char prev_pathname[512] = {0};
size_t pathname_len;
xh_core_map_info_t *mi, *mi_tmp;
xh_core_map_info_t mi_key;
xh_core_hook_info_t *hi;
xh_core_ignore_info_t *ii;
int match;
xh_core_map_info_tree_t map_info_refreshed = RB_INITIALIZER(&map_info_refreshed);
if(NULL == (fp = fopen("/proc/self/maps", "r")))
{
XH_LOG_ERROR("fopen /proc/self/maps failed");
return;
}
while(fgets(line, sizeof(line), fp))
{
if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue;
// do not touch the shared memory
if (perm[3] != 'p') continue;
// Ignore permission PROT_NONE maps
if (perm[0] == '-' && perm[1] == '-' && perm[2] == '-')
continue;
//get pathname
while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
pathname_pos += 1;
if(pathname_pos >= (int)(sizeof(line) - 1)) continue;
pathname = line + pathname_pos;
pathname_len = strlen(pathname);
if(0 == pathname_len) continue;
if(pathname[pathname_len - 1] == '\n')
{
pathname[pathname_len - 1] = '\0';
pathname_len -= 1;
}
if(0 == pathname_len) continue;
if('[' == pathname[0]) continue;
// Find non-executable map, we need record it. Because so maps can begin with
// an non-executable map.
if (perm[2] != 'x') {
prev_offset = offset;
prev_base_addr = base_addr;
memcpy(prev_perm, perm, sizeof(prev_perm));
strcpy(prev_pathname, pathname);
continue;
}
// Find executable map if offset == 0, it OK,
// or we need check previous map for base address.
if (offset != 0) {
if (strcmp(prev_pathname, pathname) || prev_offset != 0 || prev_perm[0] != 'r') {
continue;
}
// The previous map is real begin map
base_addr = prev_base_addr;
}
//check pathname
//if we need to hook this elf?
match = 0;
TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info
{
if(0 == regexec(&(hi->pathname_regex), pathname, 0, NULL, 0))
{
TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info
{
if(0 == regexec(&(ii->pathname_regex), pathname, 0, NULL, 0))
{
if(NULL == ii->symbol)
goto check_finished;
if(0 == strcmp(ii->symbol, hi->symbol))
goto check_continue;
}
}
match = 1;
check_continue:
break;
}
}
check_finished:
if(0 == match) continue;
//check elf header format
//We are trying to do ELF header checking as late as possible.
if(0 != xh_core_check_elf_header(base_addr, pathname)) continue;
//check existed map item
mi_key.pathname = pathname;
if(NULL != (mi = RB_FIND(xh_core_map_info_tree, &xh_core_map_info, &mi_key)))
{
//exist
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
//repeated?
//We only keep the first one, that is the real base address
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
{
#if XH_CORE_DEBUG
XH_LOG_DEBUG("repeated map info when update: %s", line);
#endif
free(mi->pathname);
free(mi);
continue;
}
//re-hook if base_addr changed
if(mi->base_addr != base_addr)
{
mi->base_addr = base_addr;
xh_core_hook(mi);
}
}
else
{
//not exist, create a new map info
if(NULL == (mi = (xh_core_map_info_t *)malloc(sizeof(xh_core_map_info_t)))) continue;
if(NULL == (mi->pathname = strdup(pathname)))
{
free(mi);
continue;
}
mi->base_addr = base_addr;
//repeated?
//We only keep the first one, that is the real base address
if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi))
{
#if XH_CORE_DEBUG
XH_LOG_DEBUG("repeated map info when create: %s", line);
#endif
free(mi->pathname);
free(mi);
continue;
}
//hook
xh_core_hook(mi); //hook
}
}
fclose(fp);
//free all missing map item, maybe dlclosed?
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
{
#if XH_CORE_DEBUG
XH_LOG_DEBUG("remove missing map info: %s", mi->pathname);
#endif
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
if(mi->pathname) free(mi->pathname);
free(mi);
}
//save the new refreshed map info tree
xh_core_map_info = map_info_refreshed;
XH_LOG_INFO("map refreshed");
#if XH_CORE_DEBUG
RB_FOREACH(mi, xh_core_map_info_tree, &xh_core_map_info)
XH_LOG_DEBUG(" %"PRIxPTR" %s\n", mi->base_addr, mi->pathname);
#endif
}
static void *xh_core_refresh_thread_func(void *arg)
{
(void)arg;
pthread_setname_np(pthread_self(), "xh_refresh_loop");
while(xh_core_refresh_thread_running)
{
//waiting for a refresh task or exit
pthread_mutex_lock(&xh_core_mutex);
while(!xh_core_refresh_thread_do && xh_core_refresh_thread_running)
{
pthread_cond_wait(&xh_core_cond, &xh_core_mutex);
}
if(!xh_core_refresh_thread_running)
{
pthread_mutex_unlock(&xh_core_mutex);
break;
}
xh_core_refresh_thread_do = 0;
pthread_mutex_unlock(&xh_core_mutex);
//refresh
pthread_mutex_lock(&xh_core_refresh_mutex);
xh_core_refresh_impl();
pthread_mutex_unlock(&xh_core_refresh_mutex);
}
return NULL;
}
static void xh_core_init_once()
{
if(xh_core_inited) return;
pthread_mutex_lock(&xh_core_mutex);
if(xh_core_inited) goto end;
xh_core_inited = 1;
//dump debug info
XH_LOG_INFO("%s\n", xh_version_str_full());
#if XH_CORE_DEBUG
xh_core_hook_info_t *hi;
TAILQ_FOREACH(hi, &xh_core_hook_info, link)
XH_LOG_INFO(" hook: %s @ %s, (%p, %p)\n", hi->symbol, hi->pathname_regex_str,
hi->new_func, hi->old_func);
xh_core_ignore_info_t *ii;
TAILQ_FOREACH(ii, &xh_core_ignore_info, link)
XH_LOG_INFO(" ignore: %s @ %s\n", ii->symbol ? ii->symbol : "ALL ",
ii->pathname_regex_str);
#endif
//register signal handler
if(0 != xh_core_add_sigsegv_handler()) goto end;
//OK
xh_core_init_ok = 1;
end:
pthread_mutex_unlock(&xh_core_mutex);
}
static void xh_core_init_async_once()
{
if(xh_core_async_inited) return;
pthread_mutex_lock(&xh_core_mutex);
if(xh_core_async_inited) goto end;
xh_core_async_inited = 1;
//create async refresh thread
xh_core_refresh_thread_running = 1;
if(0 != pthread_create(&xh_core_refresh_thread_tid, NULL, &xh_core_refresh_thread_func, NULL))
{
xh_core_refresh_thread_running = 0;
goto end;
}
//OK
xh_core_async_init_ok = 1;
end:
pthread_mutex_unlock(&xh_core_mutex);
}
int xh_core_refresh(int async)
{
//init
xh_core_init_once();
if(!xh_core_init_ok) return XH_ERRNO_UNKNOWN;
if(async)
{
//init for async
xh_core_init_async_once();
if(!xh_core_async_init_ok) return XH_ERRNO_UNKNOWN;
//refresh async
pthread_mutex_lock(&xh_core_mutex);
xh_core_refresh_thread_do = 1;
pthread_cond_signal(&xh_core_cond);
pthread_mutex_unlock(&xh_core_mutex);
}
else
{
//refresh sync
pthread_mutex_lock(&xh_core_refresh_mutex);
xh_core_refresh_impl();
pthread_mutex_unlock(&xh_core_refresh_mutex);
}
return 0;
}
void xh_core_clear()
{
//stop the async refresh thread
if(xh_core_async_init_ok)
{
pthread_mutex_lock(&xh_core_mutex);
xh_core_refresh_thread_running = 0;
pthread_cond_signal(&xh_core_cond);
pthread_mutex_unlock(&xh_core_mutex);
pthread_join(xh_core_refresh_thread_tid, NULL);
xh_core_async_init_ok = 0;
}
xh_core_async_inited = 0;
//unregister the sig handler
if(xh_core_init_ok)
{
xh_core_del_sigsegv_handler();
xh_core_init_ok = 0;
}
xh_core_inited = 0;
pthread_mutex_lock(&xh_core_mutex);
pthread_mutex_lock(&xh_core_refresh_mutex);
//free all map info
xh_core_map_info_t *mi, *mi_tmp;
RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp)
{
RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi);
if(mi->pathname) free(mi->pathname);
free(mi);
}
//free all hook info
xh_core_hook_info_t *hi, *hi_tmp;
TAILQ_FOREACH_SAFE(hi, &xh_core_hook_info, link, hi_tmp)
{
TAILQ_REMOVE(&xh_core_hook_info, hi, link);
#if XH_CORE_DEBUG
free(hi->pathname_regex_str);
#endif
regfree(&(hi->pathname_regex));
free(hi->symbol);
free(hi);
}
//free all ignore info
xh_core_ignore_info_t *ii, *ii_tmp;
TAILQ_FOREACH_SAFE(ii, &xh_core_ignore_info, link, ii_tmp)
{
TAILQ_REMOVE(&xh_core_ignore_info, ii, link);
#if XH_CORE_DEBUG
free(ii->pathname_regex_str);
#endif
regfree(&(ii->pathname_regex));
free(ii->symbol);
free(ii);
}
pthread_mutex_unlock(&xh_core_refresh_mutex);
pthread_mutex_unlock(&xh_core_mutex);
}
void xh_core_enable_debug(int flag)
{
xh_log_priority = (flag ? ANDROID_LOG_DEBUG : ANDROID_LOG_WARN);
}
void xh_core_enable_sigsegv_protection(int flag)
{
xh_core_sigsegv_enable = (flag ? 1 : 0);
}
================================================
FILE: libxhook/jni/xh_core.h
================================================
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2018-04-11.
#ifndef XH_CORE_H
#define XH_CORE_H 1
#ifdef __cplusplus
extern "C" {
#endif
int xh_core_register(const char *pathname_regex_str, const char *symbol,
void *new_func, void **old_func);
int xh_core_ignore(const char *pathname_regex_str, const char *symbol);
int xh_core_refresh(int async);
void xh_core_clear();
void xh_core_enable_debug(int flag);
void xh_core_enable_sigsegv_protection(int flag);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: libxhook/jni/xh_elf.c
================================================
// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Created by caikelun on 2018-04-11.
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <elf.h>
#include <link.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include "xh_errno.h"
#include "xh_log.h"
#include "xh_util.h"
#include "xh_elf.h"
#define XH_ELF_DEBUG 0
#ifndef EI_ABIVERSION
#define EI_ABIVERSION 8
#endif
#if defined(__arm__)
#define XH_ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt
#define XH_ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn
#define XH_ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn
#elif defined(__aarch64__)
#define XH_ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT
#define XH_ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT
#define XH_ELF_R_GENERIC_ABS R_AARCH64_ABS64
#elif defined(__i386__)
#define XH_ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT
#define XH_ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT
#define XH_ELF_R_GENERIC_ABS R_386_32
#elif defined(__x86_64__)
#define XH_ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT
#define XH_ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT
#define XH_ELF_R_GENERIC_ABS R_X86_64_64
#endif
#if defined(__LP64__)
#define XH_ELF_R_SYM(info) ELF64_R_SYM(info)
#define XH_ELF_R_TYPE(info) ELF64_R_TYPE(info)
#else
#define XH_ELF_R_SYM(info) ELF32_R_SYM(info)
#define XH_ELF_R_TYPE(info) ELF32_R_TYPE(info)
#endif
//iterator for plain PLT
typedef struct
{
uint8_t *cur;
uint8_t *end;
int is_use_rela;
} xh_elf_plain_reloc_iterator_t;
static void xh_elf_plain_reloc_iterator_init(xh_elf_plain_reloc_iterator_t *self,
ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela)
{
self->cur = (uint8_t *)rel;
self->end = self->cur + rel_sz;
self->is_use_rela = is_use_rela;
}
static void *xh_elf_plain_reloc_iterator_next(xh_elf_plain_reloc_iterator_t *self)
{
if(self->cur >= self->end) return NULL;
void *ret = (void *)(self->cur);
self->cur += (self->is_use_rela ? sizeof(ElfW(Rela)) : sizeof(ElfW(Rel)));
return ret;
}
//sleb128 decoder
typedef struct
{
uint8_t *cur;
uint8_t *end;
} xh_elf_sleb128_decoder_t;
static void xh_elf_sleb128_decoder_init(xh_elf_sleb128_decoder_t *self,
ElfW(Addr) rel, ElfW(Word) rel_sz)
{
self->cur = (uint8_t *)rel;
self->end = self->cur + rel_sz;
}
static int xh_elf_sleb128_decoder_next(xh_elf_sleb128_decoder_t *self, size_t *ret)
{
size_t value = 0;
static const size_t size = 8 * sizeof(value);
size_t shift = 0;
uint8_t byte;
do
{
if(self->cur >= self->end)
return XH_ERRNO_FORMAT;
byte = *(self->cur)++;
value |= ((size_t)(byte & 127) << shift);
shift += 7;
} while(byte & 128);
if(shift < size && (byte & 64))
{
value |= -((size_t)(1) << shift);
}
*ret = value;
return 0;
}
//iterator for sleb128 decoded packed PLT
typedef struct
{
xh_elf_sleb128_decoder_t decoder;
size_t relocation_count;
size_t group_size;
size_t group_flags;
size_t group_r_offset_delta;
size_t relocation_index;
size_t relocation_group_index;
ElfW(Rela) rela;
ElfW(Rel) rel;
ElfW(Addr) r_offset;
size_t r_info;
ssize_t r_addend;
int is_use_rela;
} xh_elf_packed_reloc_iterator_t;
const size_t RELOCATION_GROUPED_BY_INFO_FLAG = 1;
const size_t RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG = 2;
const size_t RELOCATION_GROUPED_BY_ADDEND_FLAG = 4;
const size_t RELOCATION_GROUP_HAS_ADDEND_FLAG = 8;
static int xh_elf_packed_reloc_iterator_init(xh_elf_packed_reloc_iterator_t *self,
ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela)
{
int r;
memset(self, 0, sizeof(xh_elf_packed_reloc_iterator_t));
xh_elf_sleb128_decoder_init(&(self->decoder), rel, rel_sz);
self->is_use_rela = is_use_rela;
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->relocation_count)))) return r;
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_offset)))) return r;
return 0;
}
static int xh_elf_packed_reloc_iterator_read_group_fields(xh_elf_packed_reloc_iterator_t *self)
{
int r;
size_t val;
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_size)))) return r;
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_flags)))) return r;
if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG)
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_r_offset_delta)))) return r;
if(self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG)
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_info)))) return r;
if((self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) &&
(self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG))
{
if(0 == self->is_use_rela)
{
XH_LOG_ERROR("unexpected r_addend in android.rel section");
return XH_ERRNO_FORMAT;
}
if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &val))) return r;
self->r_addend += (ssize_t)val;
}
else if(0 == (self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG))
{
self->r_addend = 0;
}
self->relocation_group_index = 0;
return 0;
}
static void *xh_elf_packed_reloc_iterator_next(xh_elf_packed_reloc_iterator_t *self)
{
size_t val;
if(self->relocation_index >= self->relocation_count) return NULL;
if(self->relocation_group_index == self->group_size)
{
if(0 != xh_elf_packed_reloc_iterator_read_group_fields(self)) return NULL;
}
if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG)
{
self->r_offset += self->group_r_offset_delta;
}
else
{
if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL;
self->r_offset += val;
}
if(0 == (self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG))
if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &(self->r_info))) return NULL;
if(self->is_use_rela &&
(self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) &&
(0 == (self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG)))
{
if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL;
self->r_addend += (ssize_t)val;
}
self->relocation_index++;
self->relocation_group_index++;
if(self->is_use_rela)
{
self->rela.r_offset = self->r_offset;
self->rela.r_info = self->r_info;
self->rela.r_addend = self->r_addend;
return (void *)(&(self->rela));
}
else
{
self->rel.r_offset = self->r_offset;
self->rel.r_info = self->r_info;
return (void *)(&(self->rel));
}
}
//ELF header checker
int xh_elf_check_elfheader(uintptr_t base_addr)
{
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base_addr;
//check magic
if(0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return XH_ERRNO_FORMAT;
//check class (64/32)
#if defined(__LP64__)
if(ELFCLASS64 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT;
#else
if(ELFCLASS32 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT;
#endif
//check endian (little/big)
if(ELFDATA2LSB != ehdr->e_ident[EI_DATA]) return XH_ERRNO_FORMAT;
//check version
if(EV_CURRENT != ehdr->e_ident[EI_VERSION]) return XH_ERRNO_FORMAT;
//check type
if(ET_EXEC != ehdr->e_type && ET_DYN != ehdr->e_type) return XH_ERRNO_FORMAT;
//check machine
#if defined(__arm__)
if(EM_ARM != ehdr->e_machine) return XH_ERRNO_FORMAT;
#elif defined(__aarch64__)
if(EM_AARCH64 != ehdr->e_machine) return XH_ERRNO_FORMAT;
#elif defined(__i386__)
if(EM_386 != ehdr->e_machine) return XH_ERRNO_FORMAT;
#elif defined(__x86_64__)
if(EM_X86_64 != ehdr->e_machine) return XH_ERRNO_FORMAT;
#else
return XH_ERRNO_FORMAT;
#endif
//check version
if(EV_CURRENT != ehdr->e_version) return XH_ERRNO_FORMAT;
return 0;
}
//ELF hash func
static uint32_t xh_elf_hash(const uint8_t *name)
{
uint32_t h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
//GNU hash func
static uint32_t xh_elf_gnu_hash(const uint8_t *name)
{
uint32_t h = 5381;
while(*name != 0)
{
h += (h << 5) + *name++;
}
return h;
}
static ElfW(Phdr) *xh_elf_get_first_segment_by_type(xh_elf_t *self, ElfW(Word) type)
{
ElfW(Phdr) *phdr;
for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++)
{
if(phdr->p_type == type)
{
return phdr;
}
}
return NULL;
}
static ElfW(Phdr) *xh_elf_get_first_segment_by_type_offset(xh_elf_t *self, ElfW(Word) type, ElfW(Off) offset)
{
ElfW(Phdr) *phdr;
for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++)
{
if(phdr->p_type == type && phdr->p_offset == offset)
{
return phdr;
}
}
return NULL;
}
static int xh_elf_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx)
{
uint32_t hash = xh_elf_hash((uint8_t *)symbol);
const char *symbol_cur;
uint32_t i;
for(i = self->bucket[hash % self->bucket_cnt]; 0 != i; i = self->chain[i])
{
symbol_cur = self->strtab + self->symtab[i].st_name;
if(0 == strcmp(symbol, symbol_cur))
{
*symidx = i;
XH_LOG_INFO("found %s at symidx: %u (ELF_HASH)\n", symbol, *symidx);
return 0;
}
}
return XH_ERRNO_NOTFND;
}
static int xh_elf_gnu_hash_lookup_def(xh_elf_t *self, const char *symbol, uint32_t *symidx)
{
uint32_t hash = xh_elf_gnu_hash((uint8_t *)symbol);
static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8;
size_t word = self->bloom[(hash / elfclass_bits) % self->bloom_sz];
size_t mask = 0
| (size_t)1 << (hash % elfclass_bits)
| (size_t)1 << ((hash >> self->bloom_shift) % elfclass_bits);
//if at least one bit is not set, this symbol is surely missing
if((word & mask) != mask) return XH_ERRNO_NOTFND;
//ignore STN_UNDEF
uint32_t i = self->bucket[hash % self->bucket_cnt];
if(i < self->symoffset) return XH_ERRNO_NOTFND;
//loop through the chain
while(1)
{
const char *symname = self->strtab + self->symtab[i].st_name;
const uint32_t symhash = self->chain[i - self->symoffset];
if((hash | (uint32_t)1) == (symhash | (uint32_t)1) && 0 == strcmp(symbol, symname))
{
*symidx = i;
XH_LOG_INFO("found %s at symidx: %u (GNU_HASH DEF)\n", symbol, *symidx);
return 0;
}
//chain ends with an element with the lowest bit set to 1
if(symhash & (uint32_t)1) break;
i++;
}
return XH_ERRNO_NOTFND;
}
static int xh_elf_gnu_hash_lookup_undef(xh_elf_t *self, const char *symbol, uint32_t *symidx)
{
uint32_t i;
for(i = 0; i < self->symoffset; i++)
{
const char *symname = self->strtab + self->symtab[i].st_name;
if(0 == strcmp(symname, symbol))
{
*symidx = i;
XH_LOG_INFO("found %s at symidx: %u (GNU_HASH UNDEF)\n", symbol, *symidx);
return 0;
}
}
return XH_ERRNO_NOTFND;
}
static int xh_elf_gnu_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx)
{
if(0 == xh_elf_gnu_hash_lookup_def(self, symbol, symidx)) return 0;
if(0 == xh_elf_gnu_hash_lookup_undef(self, symbol, symidx)) return 0;
return XH_ERRNO_NOTFND;
}
static int xh_elf_find_symidx_by_name(xh_elf_t *self, const char *symbol, uint32_t *symidx)
{
if(self->is_use_gnu_hash)
return xh_elf_gnu_hash_lookup(self, symbol, symidx);
else
return xh_elf_hash_lookup(self, symbol, symidx);
}
static int xh_elf_replace_function(xh_elf_t *self, const char *symbol, ElfW(Addr) addr, void *new_func, void **old_func)
{
void *old_addr;
unsigned int old_prot = 0;
unsigned int need_prot = PROT_READ | PROT_WRITE;
int r;
//already replaced?
//here we assume that we always have read permission, is this a problem?
if(*(void **)addr == new_func) return 0;
//get old prot
if(0 != (r = xh_util_get_addr_protect(addr, self->pathname, &old_prot)))
{
XH_LOG_ERROR("get addr prot failed. ret: %d", r);
return r;
}
if(old_prot != need_prot)
{
//set new prot
if(0 != (r = xh_util_set_addr_protect(addr, need_prot)))
{
XH_LOG_ERROR("set addr prot failed. ret: %d", r);
return r;
}
}
//save old func
old_addr = *(void **)addr;
if(NULL != old_func) *old_func = old_addr;
//replace func
*(void **)addr = new_func; //segmentation fault sometimes
if(old_prot != need_prot)
{
//restore the old prot
if(0 != (r = xh_util_set_addr_protect(addr, old_prot)))
{
XH_LOG_WARN("restore addr prot failed. ret: %d", r);
}
}
//clear cache
xh_util_flush_instruction_cache(addr);
XH_LOG_INFO("XH_HK_OK %p: %p -> %p %s %s\n", (void *)addr, old_addr, new_func, symbol, self->pathname);
return 0;
}
static int xh_elf_check(xh_elf_t *self)
{
if(0 == self->base_addr)
{
XH_LOG_ERROR("base_addr == 0\n");
return 1;
}
if(0 == self->bias_addr)
{
XH_LOG_ERROR("bias_addr == 0\n");
return 1;
}
if(NULL == self->ehdr)
{
XH_LOG_ERROR("ehdr == NULL\n");
return 1;
}
if(NULL == self->phdr)
{
XH_LOG_ERROR("phdr == NULL\n");
return 1;
}
if(NULL == self->strtab)
{
XH_LOG_ERROR("strtab == NULL\n");
return 1;
}
if(NULL == self->symtab)
{
XH_LOG_ERROR("symtab == NULL\n");
return 1;
}
if(NULL == self->bucket)
{
XH_LOG_ERROR("bucket == NULL\n");
return 1;
}
if(NULL == self->chain)
{
XH_LOG_ERROR("chain == NULL\n");
return 1;
}
if(1 == self->is_use_gnu_hash && NULL == self->bloom)
{
XH_LOG_ERROR("bloom == NULL\n");
return 1;
}
return 0;
}
#if XH_ELF_DEBUG
static void xh_elf_dump_elfheader(xh_elf_t *self)
{
static char alpha_tab[17] = "0123456789ABCDEF";
int i;
uint8_t ch;
char buff[EI_NIDENT * 3 + 1];
for(i = 0; i < EI_NIDENT; i++)
{
ch = self->ehdr->e_ident[i];
buff[i * 3 + 0] = alpha_tab[(int)((ch >> 4) & 0x0F)];
buff[i * 3 + 1] = alpha_tab[(int)(ch & 0x0F)];
buff[i * 3 + 2] = ' ';
}
buff[EI_NIDENT * 3] = '\0';
XH_LOG_DEBUG("Elf Header:\n");
XH_LOG_DEBUG(" Magic: %s\n", buff);
XH_LOG_DEBUG(" Class: %#x\n", self->ehdr->e_ident[EI_CLASS]);
XH_LOG_DEBUG(" Data: %#x\n", self->ehdr->e_ident[EI_DATA]);
XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_ident[EI_VERSION]);
XH_LOG_DEBUG(" OS/ABI: %#x\n", self->ehdr->e_ident[EI_OSABI]);
XH_LOG_DEBUG(" ABI Version: %#x\n", self->ehdr->e_ident[EI_ABIVERSION]);
XH_LOG_DEBUG(" Type: %#x\n", self->ehdr->e_type);
XH_LOG_DEBUG(" Machine: %#x\n", self->ehdr->e_machine);
XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_version);
XH_LOG_DEBUG(" Entry point address: %"XH_UTIL_FMT_X"\n", self->ehdr->e_entry);
XH_LOG_DEBUG(" Start of program headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_phoff);
XH_LOG_DEBUG(" Start of section headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_shoff);
XH_LOG_DEBUG(" Flags: %#x\n", self->ehdr->e_flags);
XH_LOG_DEBUG(" Size of this header: %u (bytes)\n", self->ehdr->e_ehsize);
XH_LOG_DEBUG(" Size of program headers: %u (bytes)\n", self->ehdr->e_phentsize);
XH_LOG_DEBUG(" Number of program headers: %u\n", self->ehdr->e_phnum);
XH_LOG_DEBUG(" Size of section headers: %u (bytes)\n", self->ehdr->e_shentsize);
XH_LOG_DEBUG(" Number of section headers: %u\n", self->ehdr->e_shnum);
XH_LOG_DEBUG(" Section header string table index: %u\n", self->ehdr->e_shstrndx);
}
static void xh_elf_dump_programheader(xh_elf_t *self)
{
ElfW(Phdr) *phdr = self->phdr;
size_t i;
XH_LOG_DEBUG("Program Headers:\n");
XH_LOG_DEBUG(" %-8s " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-8s " \
"%-s\n",
"Type",
"Offset",
"VirtAddr",
"PhysAddr",
"FileSiz",
"MemSiz",
"Flg",
"Align");
for(i = 0; i < self->ehdr->e_phnum; i++, phdr++)
{
XH_LOG_DEBUG(" %-8x " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%-8x " \
"%"XH_UTIL_FMT_X"\n",
phdr->p_type,
phdr->p_offset,
phdr->p_vaddr,
phdr->p_paddr,
phdr->p_filesz,
phdr->p_memsz,
phdr->p_flags,
phdr->p_align);
}
}
static void xh_elf_dump_dynamic(xh_elf_t *self)
{
ElfW(Dyn) *dyn = self->dyn;
size_t dyn_cnt = (self->dyn_sz / sizeof(ElfW(Dyn)));
size_t i;
XH_LOG_DEBUG("Dynamic section contains %zu entries:\n", dyn_cnt);
XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \
"%s\n",
"Tag",
"Val");
for(i = 0; i < dyn_cnt; i++, dyn++)
{
XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_X" " \
"%-"XH_UTIL_FMT_X"\n",
dyn->d_tag,
dyn->d_un.d_val);
}
}
static void xh_elf_dump_rel(xh_elf_t *self, const char *type, ElfW(Addr) rel_addr, ElfW(Word) rel_sz)
{
ElfW(Rela) *rela;
ElfW(Rel) *rel;
ElfW(Word) cnt;
ElfW(Word) i;
ElfW(Sym) *sym;
if(self->is_use_rela)
{
rela = (ElfW(Rela) *)(rel_addr);
cnt = rel_sz / sizeof(ElfW(Rela));
}
else
{
rel = (ElfW(Rel) *)(rel_addr);
cnt = rel_sz / sizeof(ElfW(Rel));
}
XH_LOG_DEBUG("Relocation section '.rel%s%s' contains %u entries:\n",
(self->is_use_rela ? "a" : ""), type, cnt);
XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%-8s " \
"%-8s " \
"%-8s " \
"%s\n",
"Offset",
"Info",
"Type",
"Sym.Idx",
"Sym.Val",
"Sym.Name");
const char *fmt = " %."XH_UTIL_FMT_FIXED_X" " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%.8x " \
"%.8u " \
"%.8x " \
"%s\n";
for(i = 0; i < cnt; i++)
{
if(self->is_use_rela)
{
sym = &(self->symtab[XH_ELF_R_SYM(rela[i].r_info)]);
XH_LOG_DEBUG(fmt,
rela[i].r_offset,
rela[i].r_info,
XH_ELF_R_TYPE(rela[i].r_info),
XH_ELF_R_SYM(rela[i].r_info),
sym->st_value,
self->strtab + sym->st_name);
}
else
{
sym = &(self->symtab[XH_ELF_R_SYM(rel[i].r_info)]);
XH_LOG_DEBUG(fmt,
rel[i].r_offset,
rel[i].r_info,
XH_ELF_R_TYPE(rel[i].r_info),
XH_ELF_R_SYM(rel[i].r_info),
sym->st_value,
self->strtab + sym->st_name);
}
}
}
static void xh_elf_dump_symtab(xh_elf_t *self)
{
if(self->is_use_gnu_hash) return;
ElfW(Word) symtab_cnt = self->chain_cnt;
ElfW(Word) i;
XH_LOG_DEBUG("Symbol table '.dynsym' contains %u entries:\n", symtab_cnt);
XH_LOG_DEBUG(" %-8s " \
"%-"XH_UTIL_FMT_FIXED_S" " \
"%s\n",
"Idx",
"Value",
"Name");
for(i = 0; i < symtab_cnt; i++)
{
XH_LOG_DEBUG(" %-8u " \
"%."XH_UTIL_FMT_FIXED_X" " \
"%s\n",
i,
self->symtab[i].st_value,
self->strtab + self->symtab[i].st_name);
}
}
static void xh_elf_dump(xh_elf_t *self)
{
if(xh_log_priority < ANDROID_LOG_DEBUG) return;
XH_LOG_DEBUG("Elf Pathname: %s\n", self->pathname);
XH_LOG_DEBUG("Elf bias addr: %p\n", (void *)self->bias_addr);
xh_elf_dump_elfheader(self);
xh_elf_dump_programheader(self);
xh_elf_dump_dynamic(self);
xh_elf_dump_rel(self, ".plt", self->relp
gitextract_ohqx2nmx/
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE-docs
├── README.md
├── README.zh-CN.md
├── build_libs.sh
├── clean_libs.sh
├── docs/
│ ├── overview/
│ │ ├── android_plt_hook_overview.zh-CN.md
│ │ └── code/
│ │ ├── .gitignore
│ │ ├── build.sh
│ │ ├── clean.sh
│ │ ├── libtest/
│ │ │ └── jni/
│ │ │ ├── Android.mk
│ │ │ ├── Application.mk
│ │ │ ├── test.c
│ │ │ └── test.h
│ │ ├── main/
│ │ │ └── jni/
│ │ │ ├── Android.mk
│ │ │ ├── Application.mk
│ │ │ └── main.c
│ │ └── run.sh
│ └── xhooklogo.sketch
├── install_libs.sh
├── libbiz/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ └── biz.c
├── libtest/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ └── test.c
├── libxhook/
│ └── jni/
│ ├── Android.mk
│ ├── Application.mk
│ ├── queue.h
│ ├── tree.h
│ ├── xh_core.c
│ ├── xh_core.h
│ ├── xh_elf.c
│ ├── xh_elf.h
│ ├── xh_errno.h
│ ├── xh_jni.c
│ ├── xh_log.c
│ ├── xh_log.h
│ ├── xh_util.c
│ ├── xh_util.h
│ ├── xh_version.c
│ ├── xh_version.h
│ ├── xhook.c
│ └── xhook.h
└── xhookwrapper/
├── .gitignore
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── qiyi/
│ │ ├── test/
│ │ │ ├── NativeHandler.java
│ │ │ └── Test.java
│ │ └── xhookwrapper/
│ │ └── MainActivity.java
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ └── activity_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── biz/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── qiyi/
│ │ └── biz/
│ │ ├── Biz.java
│ │ └── NativeHandler.java
│ └── res/
│ └── values/
│ └── strings.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── xhook/
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── qiyi/
│ └── xhook/
│ ├── NativeHandler.java
│ └── XHook.java
└── res/
└── values/
└── strings.xml
SYMBOL INDEX (134 symbols across 19 files)
FILE: docs/overview/code/libtest/jni/test.c
function say_hello (line 4) | void say_hello()
FILE: docs/overview/code/main/jni/main.c
function hook (line 17) | void hook()
function main (line 48) | int main()
FILE: libbiz/jni/biz.c
function my_system_log_print (line 8) | static int my_system_log_print(int prio, const char* tag, const char* fm...
function my_libtest_log_print (line 22) | static int my_libtest_log_print(int prio, const char* tag, const char* f...
function Java_com_qiyi_biz_NativeHandler_start (line 36) | void Java_com_qiyi_biz_NativeHandler_start(JNIEnv* env, jobject obj)
FILE: libtest/jni/test.c
function Java_com_qiyi_test_NativeHandler_start (line 29) | void Java_com_qiyi_test_NativeHandler_start(JNIEnv* env, jobject obj)
FILE: libxhook/jni/tree.h
type type (line 80) | struct type
type type (line 129) | struct type
type name (line 130) | struct name
type type (line 130) | struct type
type type (line 140) | struct type
type name (line 141) | struct name
type type (line 141) | struct type
type type (line 154) | struct type
type name (line 155) | struct name
function _SPLAY_MINMAX (line 248) | void name##_SPLAY_MINMAX(struct name *head, int __comp) \
type type (line 316) | struct type
type type (line 317) | struct type
type name (line 599) | struct name
type type (line 601) | struct type
type type (line 602) | struct type
type name (line 630) | struct name
type type (line 632) | struct type
type name (line 648) | struct name
type type (line 650) | struct type
type type (line 651) | struct type
type type (line 669) | struct type
type type (line 691) | struct type
type name (line 712) | struct name
type type (line 714) | struct type
type type (line 715) | struct type
FILE: libxhook/jni/xh_core.c
type xh_core_hook_info_t (line 45) | typedef struct xh_core_hook_info
type xh_core_hook_info_queue_t (line 56) | typedef TAILQ_HEAD(xh_core_hook_info_queue, xh_core_hook_info,) xh_core_...
type xh_core_ignore_info_t (line 59) | typedef struct xh_core_ignore_info
type xh_core_ignore_info_queue_t (line 68) | typedef TAILQ_HEAD(xh_core_ignore_info_queue, xh_core_ignore_info,) xh_c...
type xh_core_map_info_t (line 71) | typedef struct xh_core_map_info
function xh_core_map_info_cmp (line 78) | static __inline__ int xh_core_map_info_cmp(xh_core_map_info_t *a, xh_cor...
type xh_core_map_info_tree_t (line 82) | typedef RB_HEAD(xh_core_map_info_tree, xh_core_map_info) xh_core_map_inf...
type sigaction (line 88) | struct sigaction
function xh_core_sigsegv_handler (line 91) | static void xh_core_sigsegv_handler(int sig)
function xh_core_add_sigsegv_handler (line 100) | static int xh_core_add_sigsegv_handler()
function xh_core_del_sigsegv_handler (line 114) | static void xh_core_del_sigsegv_handler()
function xh_core_register (line 137) | int xh_core_register(const char *pathname_regex_str, const char *symbol,
function xh_core_ignore (line 178) | int xh_core_ignore(const char *pathname_regex_str, const char *symbol)
function xh_core_check_elf_header (line 223) | static int xh_core_check_elf_header(uintptr_t base_addr, const char *pat...
function xh_core_hook_impl (line 248) | static void xh_core_hook_impl(xh_core_map_info_t *mi)
function xh_core_hook (line 283) | static void xh_core_hook(xh_core_map_info_t *mi)
function xh_core_refresh_impl (line 304) | static void xh_core_refresh_impl()
function xh_core_init_once (line 516) | static void xh_core_init_once()
function xh_core_init_async_once (line 549) | static void xh_core_init_async_once()
function xh_core_refresh (line 574) | int xh_core_refresh(int async)
function xh_core_clear (line 603) | void xh_core_clear()
function xh_core_enable_debug (line 668) | void xh_core_enable_debug(int flag)
function xh_core_enable_sigsegv_protection (line 673) | void xh_core_enable_sigsegv_protection(int flag)
FILE: libxhook/jni/xh_elf.c
type xh_elf_plain_reloc_iterator_t (line 73) | typedef struct
function xh_elf_plain_reloc_iterator_init (line 80) | static void xh_elf_plain_reloc_iterator_init(xh_elf_plain_reloc_iterator...
type xh_elf_sleb128_decoder_t (line 98) | typedef struct
function xh_elf_sleb128_decoder_init (line 104) | static void xh_elf_sleb128_decoder_init(xh_elf_sleb128_decoder_t *self,
function xh_elf_sleb128_decoder_next (line 111) | static int xh_elf_sleb128_decoder_next(xh_elf_sleb128_decoder_t *self, s...
type xh_elf_packed_reloc_iterator_t (line 138) | typedef struct
function xh_elf_packed_reloc_iterator_init (line 160) | static int xh_elf_packed_reloc_iterator_init(xh_elf_packed_reloc_iterato...
function xh_elf_packed_reloc_iterator_read_group_fields (line 174) | static int xh_elf_packed_reloc_iterator_read_group_fields(xh_elf_packed_...
function xh_elf_check_elfheader (line 259) | int xh_elf_check_elfheader(uintptr_t base_addr)
function xh_elf_hash (line 302) | static uint32_t xh_elf_hash(const uint8_t *name)
function xh_elf_gnu_hash (line 317) | static uint32_t xh_elf_gnu_hash(const uint8_t *name)
function xh_elf_hash_lookup (line 356) | static int xh_elf_hash_lookup(xh_elf_t *self, const char *symbol, uint32...
function xh_elf_gnu_hash_lookup_def (line 377) | static int xh_elf_gnu_hash_lookup_def(xh_elf_t *self, const char *symbol...
function xh_elf_gnu_hash_lookup_undef (line 416) | static int xh_elf_gnu_hash_lookup_undef(xh_elf_t *self, const char *symb...
function xh_elf_gnu_hash_lookup (line 433) | static int xh_elf_gnu_hash_lookup(xh_elf_t *self, const char *symbol, ui...
function xh_elf_find_symidx_by_name (line 440) | static int xh_elf_find_symidx_by_name(xh_elf_t *self, const char *symbol...
function xh_elf_replace_function (line 448) | static int xh_elf_replace_function(xh_elf_t *self, const char *symbol, E...
function xh_elf_check (line 499) | static int xh_elf_check(xh_elf_t *self)
function xh_elf_dump_elfheader (line 552) | static void xh_elf_dump_elfheader(xh_elf_t *self)
function xh_elf_dump_programheader (line 590) | static void xh_elf_dump_programheader(xh_elf_t *self)
function xh_elf_dump_dynamic (line 633) | static void xh_elf_dump_dynamic(xh_elf_t *self)
function xh_elf_dump_rel (line 653) | static void xh_elf_dump_rel(xh_elf_t *self, const char *type, ElfW(Addr)...
function xh_elf_dump_symtab (line 719) | static void xh_elf_dump_symtab(xh_elf_t *self)
function xh_elf_dump (line 744) | static void xh_elf_dump(xh_elf_t *self)
function xh_elf_init (line 760) | int xh_elf_init(xh_elf_t *self, uintptr_t base_addr, const char *pathname)
function xh_elf_find_and_replace_func (line 928) | static int xh_elf_find_and_replace_func(xh_elf_t *self, const char *sect...
function xh_elf_hook (line 983) | int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void...
FILE: libxhook/jni/xh_elf.h
type xh_elf_t (line 35) | typedef struct
FILE: libxhook/jni/xh_jni.c
function JNIEXPORT (line 29) | JNIEXPORT jint JNI_API_DEF(refresh)(JNIEnv *env, jobject obj, jboolean a...
function JNIEXPORT (line 37) | JNIEXPORT void JNI_API_DEF(clear)(JNIEnv *env, jobject obj)
function JNIEXPORT (line 45) | JNIEXPORT void JNI_API_DEF(enableDebug)(JNIEnv *env, jobject obj, jboole...
function JNIEXPORT (line 53) | JNIEXPORT void JNI_API_DEF(enableSigSegvProtection)(JNIEnv *env, jobject...
FILE: libxhook/jni/xh_util.c
function xh_util_get_mem_protect (line 44) | int xh_util_get_mem_protect(uintptr_t addr, size_t len, const char *path...
function xh_util_get_addr_protect (line 105) | int xh_util_get_addr_protect(uintptr_t addr, const char *pathname, unsig...
function xh_util_set_addr_protect (line 110) | int xh_util_set_addr_protect(uintptr_t addr, unsigned int prot)
function xh_util_flush_instruction_cache (line 118) | void xh_util_flush_instruction_cache(uintptr_t addr)
FILE: libxhook/jni/xh_version.c
function xh_version (line 53) | unsigned int xh_version()
FILE: libxhook/jni/xhook.c
function xhook_register (line 27) | int xhook_register(const char *pathname_regex_str, const char *symbol,
function xhook_ignore (line 33) | int xhook_ignore(const char *pathname_regex_str, const char *symbol)
function xhook_refresh (line 38) | int xhook_refresh(int async)
function xhook_clear (line 43) | void xhook_clear()
function xhook_enable_debug (line 48) | void xhook_enable_debug(int flag)
function xhook_enable_sigsegv_protection (line 53) | void xhook_enable_sigsegv_protection(int flag)
FILE: xhookwrapper/app/src/main/java/com/qiyi/test/NativeHandler.java
class NativeHandler (line 7) | public class NativeHandler {
method getInstance (line 10) | public static NativeHandler getInstance() {
method NativeHandler (line 14) | private NativeHandler() {
method start (line 17) | public native void start();
FILE: xhookwrapper/app/src/main/java/com/qiyi/test/Test.java
class Test (line 7) | public class Test {
method getInstance (line 10) | public static Test getInstance() {
method Test (line 14) | private Test() {
method init (line 17) | public synchronized void init() {
method start (line 21) | public synchronized void start() {
FILE: xhookwrapper/app/src/main/java/com/qiyi/xhookwrapper/MainActivity.java
class MainActivity (line 7) | public class MainActivity extends AppCompatActivity {
method onCreate (line 9) | @Override
FILE: xhookwrapper/biz/src/main/java/com/qiyi/biz/Biz.java
class Biz (line 7) | public class Biz {
method getInstance (line 10) | public static Biz getInstance() {
method Biz (line 14) | private Biz() {
method init (line 17) | public synchronized void init() {
method start (line 21) | public synchronized void start() {
FILE: xhookwrapper/biz/src/main/java/com/qiyi/biz/NativeHandler.java
class NativeHandler (line 7) | public class NativeHandler {
method getInstance (line 10) | public static NativeHandler getInstance() {
method NativeHandler (line 14) | private NativeHandler() {
method start (line 17) | public native void start();
FILE: xhookwrapper/xhook/src/main/java/com/qiyi/xhook/NativeHandler.java
class NativeHandler (line 7) | public class NativeHandler {
method getInstance (line 10) | public static NativeHandler getInstance() {
method NativeHandler (line 14) | private NativeHandler() {
method refresh (line 17) | public native int refresh(boolean async);
method clear (line 19) | public native void clear();
method enableDebug (line 21) | public native void enableDebug(boolean flag);
method enableSigSegvProtection (line 23) | public native void enableSigSegvProtection(boolean flag);
FILE: xhookwrapper/xhook/src/main/java/com/qiyi/xhook/XHook.java
class XHook (line 10) | public class XHook {
method getInstance (line 14) | public static XHook getInstance() {
method XHook (line 18) | private XHook() {
method isInited (line 25) | public synchronized boolean isInited() {
method init (line 34) | public synchronized boolean init(Context ctx) {
method refresh (line 59) | public synchronized void refresh(boolean async) {
method clear (line 75) | public synchronized void clear() {
method enableDebug (line 92) | public synchronized void enableDebug(boolean flag) {
method enableSigSegvProtection (line 109) | public synchronized void enableSigSegvProtection(boolean flag) {
Condensed preview — 83 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (274K chars).
[
{
"path": ".gitignore",
"chars": 193,
"preview": ".DS_Store\n\nlibxhook/libs/\nlibxhook/obj/\nlibbiz/libs/\nlibbiz/obj/\nlibtest/libs/\nlibtest/obj/\n\n#for xhookwrapper\nbuild/\n.g"
},
{
"path": "CONTRIBUTING.md",
"chars": 1458,
"preview": "# Contributing to xHook\n\nWelcome to the xHook project. Read on to learn more about our development process and how to pr"
},
{
"path": "LICENSE",
"chars": 4295,
"preview": "Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n\nMost source code in xhook are MIT licensed. Some other sou"
},
{
"path": "LICENSE-docs",
"chars": 18525,
"preview": "Attribution 4.0 International\n\n=======================================================================\n\nCreative Commons"
},
{
"path": "README.md",
"chars": 7247,
"preview": "<p align=\"center\"><img src=\"https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true\" alt=\"xhook\" width=\"5"
},
{
"path": "README.zh-CN.md",
"chars": 5477,
"preview": "<p align=\"center\"><img src=\"https://github.com/iqiyi/xHook/blob/master/docs/xhooklogo.png?raw=true\" alt=\"xhook\" width=\"5"
},
{
"path": "build_libs.sh",
"chars": 94,
"preview": "#!/bin/bash\n\nndk-build -C ./libxhook/jni\nndk-build -C ./libbiz/jni\nndk-build -C ./libtest/jni\n"
},
{
"path": "clean_libs.sh",
"chars": 112,
"preview": "#!/bin/bash\n\nndk-build -C ./libbiz/jni clean\nndk-build -C ./libxhook/jni clean\nndk-build -C ./libtest/jni clean\n"
},
{
"path": "docs/overview/android_plt_hook_overview.zh-CN.md",
"chars": 26316,
"preview": "# Android PLT hook 概述\n\n\n## 获取代码和资源\n\n你始终可以从 [这里](https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hoo"
},
{
"path": "docs/overview/code/.gitignore",
"chars": 59,
"preview": ".DS_Store\n\nmain/libs/\nmain/obj/\nlibtest/libs/\nlibtest/obj/\n"
},
{
"path": "docs/overview/code/build.sh",
"chars": 64,
"preview": "#!/bin/bash\n\nndk-build -C ./libtest/jni\nndk-build -C ./main/jni\n"
},
{
"path": "docs/overview/code/clean.sh",
"chars": 76,
"preview": "#!/bin/bash\n\nndk-build -C ./main/jni clean\nndk-build -C ./libtest/jni clean\n"
},
{
"path": "docs/overview/code/libtest/jni/Android.mk",
"chars": 212,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := test\nLOCAL_SRC_FILES := test.c\nLOCAL_CFLAGS "
},
{
"path": "docs/overview/code/libtest/jni/Application.mk",
"chars": 55,
"preview": "APP_ABI := armeabi-v7a\nAPP_PLATFORM := android-14\n"
},
{
"path": "docs/overview/code/libtest/jni/test.c",
"chars": 196,
"preview": "#include <stdlib.h>\n#include <stdio.h>\n\nvoid say_hello()\n{\n char *buf = malloc(1024);\n if(NULL != buf)\n {\n "
},
{
"path": "docs/overview/code/libtest/jni/test.h",
"chars": 128,
"preview": "#ifndef TEST_H\n#define TEST_H 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid say_hello();\n\n#ifdef __cplusplus\n}\n#endif\n"
},
{
"path": "docs/overview/code/main/jni/Android.mk",
"chars": 546,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := test\nLOCAL_SRC_FILES := $(LOCAL_P"
},
{
"path": "docs/overview/code/main/jni/Application.mk",
"chars": 55,
"preview": "APP_ABI := armeabi-v7a\nAPP_PLATFORM := android-14\n"
},
{
"path": "docs/overview/code/main/jni/main.c",
"chars": 1215,
"preview": "#include <inttypes.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <sys/mman.h>\n#include <test.h>"
},
{
"path": "docs/overview/code/run.sh",
"chars": 217,
"preview": "#!/bin/bash\n\nadb push ./main/libs/armeabi-v7a/libtest.so ./main/libs/armeabi-v7a/main /data/local/tmp\nadb shell \"chmod +"
},
{
"path": "install_libs.sh",
"chars": 1844,
"preview": "#!/bin/bash\n\nmkdir -p ./xhookwrapper/xhook/libs/armeabi\nmkdir -p ./xhookwrapper/xhook/libs/armeabi-v7a\nmkdir -p ./xhookw"
},
{
"path": "libbiz/jni/Android.mk",
"chars": 542,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := xhook\nLOCAL_SRC_FILES := $(LOCAL_"
},
{
"path": "libbiz/jni/Application.mk",
"chars": 84,
"preview": "APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64\nAPP_PLATFORM := android-14\n"
},
{
"path": "libbiz/jni/biz.c",
"chars": 1410,
"preview": "#include <stdio.h>\n#include <unistd.h>\n#include <pthread.h>\n#include <jni.h>\n#include <android/log.h>\n#include \"xhook.h\""
},
{
"path": "libtest/jni/Android.mk",
"chars": 237,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := test\nLOCAL_SRC_FILES := test.c\nLOCAL_CFLAGS "
},
{
"path": "libtest/jni/Application.mk",
"chars": 84,
"preview": "APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64\nAPP_PLATFORM := android-14\n"
},
{
"path": "libtest/jni/test.c",
"chars": 1093,
"preview": "#include <unistd.h>\n#include <pthread.h>\n#include <jni.h>\n#include <android/log.h>\n\ntypedef int (*my_log_t)(int prio, co"
},
{
"path": "libxhook/jni/Android.mk",
"chars": 481,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := xhook\nLOCAL_SRC_FILES := xhook.c \\\n "
},
{
"path": "libxhook/jni/Application.mk",
"chars": 84,
"preview": "APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64\nAPP_PLATFORM := android-14\n"
},
{
"path": "libxhook/jni/queue.h",
"chars": 28015,
"preview": "/*-\n * Copyright (c) 1991, 1993\n * The Regents of the University of California. All rights reserved.\n *\n * Redistr"
},
{
"path": "libxhook/jni/tree.h",
"chars": 49580,
"preview": "/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */\n/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 ar"
},
{
"path": "libxhook/jni/xh_core.c",
"chars": 20115,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_core.h",
"chars": 1644,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_elf.c",
"chars": 33999,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_elf.h",
"chars": 2506,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_errno.h",
"chars": 1501,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_jni.c",
"chars": 1885,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_log.c",
"chars": 1284,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_log.h",
"chars": 2018,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_util.c",
"chars": 3841,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_util.h",
"chars": 1880,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_version.c",
"chars": 2206,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xh_version.h",
"chars": 1398,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xhook.c",
"chars": 1859,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "libxhook/jni/xhook.h",
"chars": 1765,
"preview": "// Copyright (c) 2018-present, iQIYI, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to a"
},
{
"path": "xhookwrapper/.gitignore",
"chars": 118,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
},
{
"path": "xhookwrapper/app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "xhookwrapper/app/build.gradle",
"chars": 876,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 29\n defaultConfig {\n applicationId \"c"
},
{
"path": "xhookwrapper/app/proguard-rules.pro",
"chars": 751,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "xhookwrapper/app/src/main/AndroidManifest.xml",
"chars": 715,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "xhookwrapper/app/src/main/java/com/qiyi/test/NativeHandler.java",
"chars": 329,
"preview": "package com.qiyi.test;\n\n/**\n * Created by caikelun on 18/01/2018.\n */\n\npublic class NativeHandler {\n private static f"
},
{
"path": "xhookwrapper/app/src/main/java/com/qiyi/test/Test.java",
"chars": 437,
"preview": "package com.qiyi.test;\n\n/**\n * Created by caikelun on 18/01/2018.\n */\n\npublic class Test {\n private static final Test"
},
{
"path": "xhookwrapper/app/src/main/java/com/qiyi/xhookwrapper/MainActivity.java",
"chars": 1937,
"preview": "package com.qiyi.xhookwrapper;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "xhookwrapper/app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 5606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "xhookwrapper/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 1880,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "xhookwrapper/app/src/main/res/layout/activity_main.xml",
"chars": 474,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "xhookwrapper/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "xhookwrapper/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "xhookwrapper/app/src/main/res/values/colors.xml",
"chars": 208,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#3F51B5</color>\n <color name=\"color"
},
{
"path": "xhookwrapper/app/src/main/res/values/strings.xml",
"chars": 75,
"preview": "<resources>\n <string name=\"app_name\">xhookwrapper</string>\n</resources>\n"
},
{
"path": "xhookwrapper/app/src/main/res/values/styles.xml",
"chars": 383,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
},
{
"path": "xhookwrapper/biz/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "xhookwrapper/biz/build.gradle",
"chars": 755,
"preview": "apply plugin: 'com.android.library'\n\nandroid {\n compileSdkVersion 29\n defaultConfig {\n minSdkVersion 14\n "
},
{
"path": "xhookwrapper/biz/proguard-rules.pro",
"chars": 751,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "xhookwrapper/biz/src/main/AndroidManifest.xml",
"chars": 99,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"com.qiyi.biz\" />\n"
},
{
"path": "xhookwrapper/biz/src/main/java/com/qiyi/biz/Biz.java",
"chars": 429,
"preview": "package com.qiyi.biz;\n\n/**\n * Created by caikelun on 18/01/2018.\n */\n\npublic class Biz {\n private static final Biz ou"
},
{
"path": "xhookwrapper/biz/src/main/java/com/qiyi/biz/NativeHandler.java",
"chars": 328,
"preview": "package com.qiyi.biz;\n\n/**\n * Created by caikelun on 18/01/2018.\n */\n\npublic class NativeHandler {\n private static fi"
},
{
"path": "xhookwrapper/biz/src/main/res/values/strings.xml",
"chars": 66,
"preview": "<resources>\n <string name=\"app_name\">biz</string>\n</resources>\n"
},
{
"path": "xhookwrapper/build.gradle",
"chars": 546,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n \n"
},
{
"path": "xhookwrapper/gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Tue Mar 26 19:19:54 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "xhookwrapper/gradle.properties",
"chars": 730,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
},
{
"path": "xhookwrapper/gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "xhookwrapper/gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "xhookwrapper/settings.gradle",
"chars": 33,
"preview": "include ':app', ':xhook', ':biz'\n"
},
{
"path": "xhookwrapper/xhook/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "xhookwrapper/xhook/build.gradle",
"chars": 1077,
"preview": "apply plugin: 'com.android.library'\napply plugin: 'maven'\n\ndef version = \"1.2.0\"\n\nandroid {\n compileSdkVersion 29\n "
},
{
"path": "xhookwrapper/xhook/proguard-rules.pro",
"chars": 751,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "xhookwrapper/xhook/src/main/AndroidManifest.xml",
"chars": 159,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"com.qiyi.xhook\"\n android:versionCod"
},
{
"path": "xhookwrapper/xhook/src/main/java/com/qiyi/xhook/NativeHandler.java",
"chars": 492,
"preview": "package com.qiyi.xhook;\n\n/**\n * Created by caikelun on 18/01/2018.\n */\n\npublic class NativeHandler {\n private static "
},
{
"path": "xhookwrapper/xhook/src/main/java/com/qiyi/xhook/XHook.java",
"chars": 3085,
"preview": "package com.qiyi.xhook;\n\nimport android.content.Context;\nimport android.util.Log;\n\n/**\n * Created by caikelun on 18/01/2"
},
{
"path": "xhookwrapper/xhook/src/main/res/values/strings.xml",
"chars": 68,
"preview": "<resources>\n <string name=\"app_name\">xhook</string>\n</resources>\n"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the iqiyi/xHook GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 83 files (253.2 KB), approximately 68.6k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.