├── .clang-format ├── .github └── workflows │ ├── android.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── README_CN.md ├── Stub ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── dalvik │ └── system │ └── VMRuntime.java ├── browser_stack_test.py ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sanfengandroid │ │ └── demo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── native_test.cpp │ │ ├── shared_demo.cpp │ │ ├── static_demo.cpp │ │ └── symbol.map.txt │ ├── java │ │ └── com │ │ │ └── sanfengandroid │ │ │ └── demo │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── sanfengandroid │ └── demo │ └── ExampleUnitTest.java ├── emulator-testapp ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sanfengandroid │ │ └── fakelinker │ │ └── emulator_test │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── Dobby │ │ │ ├── arm64-v8a │ │ │ │ └── libdobby.a │ │ │ ├── armeabi-v7a │ │ │ │ └── libdobby.a │ │ │ ├── dobby.h │ │ │ ├── x86 │ │ │ │ └── libdobby.a │ │ │ └── x86_64 │ │ │ │ └── libdobby.a │ │ ├── fakelinker_module.cpp │ │ ├── symbol.map.txt │ │ └── test_module.cpp │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── sanfengandroid │ │ │ └── fakelinker │ │ │ └── emulator_test │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── sanfengandroid │ └── fakelinker │ └── emulator_test │ └── ExampleUnitTest.java ├── fakelinker-test ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sanfengandroid │ │ └── fakelinker │ │ └── test │ │ └── FakelinkerGTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── test │ │ │ ├── test_elf_reader.cpp │ │ │ └── test_fakelinker.cpp │ ├── java │ │ └── com │ │ │ └── sanfengandroid │ │ │ └── fakelinker │ │ │ └── test │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── sanfengandroid │ └── fakelinker │ └── test │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── LICENSE ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── common │ │ │ ├── maps_util.cpp │ │ │ ├── unique_fd.cpp │ │ │ └── unique_memory.cpp │ │ ├── include │ │ │ └── fakelinker │ │ │ │ ├── alog.h │ │ │ │ ├── android_level_compat.h │ │ │ │ ├── art_symbol.h │ │ │ │ ├── default_trace_jni.h │ │ │ │ ├── elf_reader.h │ │ │ │ ├── fake_linker.h │ │ │ │ ├── jni_helper.h │ │ │ │ ├── linker.h │ │ │ │ ├── linker_macros.h │ │ │ │ ├── linker_mapped_file_fragment.h │ │ │ │ ├── linker_note_gnu_property.h │ │ │ │ ├── macros.h │ │ │ │ ├── maps_util.h │ │ │ │ ├── proxy_jni.h │ │ │ │ ├── scoped_local_ref.h │ │ │ │ ├── scoped_utf_chars.h │ │ │ │ ├── trace_jni.h │ │ │ │ ├── type.h │ │ │ │ ├── unique_fd.h │ │ │ │ └── unique_memory.h │ │ ├── installer │ │ │ ├── hook_installer.cpp │ │ │ └── hook_installer.h │ │ ├── linker │ │ │ ├── art │ │ │ │ ├── art_symbol.cpp │ │ │ │ ├── default_trace_jni.cpp │ │ │ │ ├── hook_jni_native_interface_impl.cpp │ │ │ │ ├── hook_jni_native_interface_impl.h │ │ │ │ ├── jni_helper.cpp │ │ │ │ └── trace_jni.cpp │ │ │ ├── bionic │ │ │ │ └── get_tls.h │ │ │ ├── elf_reader.cpp │ │ │ ├── linked_list.h │ │ │ ├── linker_block_allocator.cpp │ │ │ ├── linker_block_allocator.h │ │ │ ├── linker_common_types.cpp │ │ │ ├── linker_common_types.h │ │ │ ├── linker_dynamic.cpp │ │ │ ├── linker_export.cpp │ │ │ ├── linker_globals.cpp │ │ │ ├── linker_globals.h │ │ │ ├── linker_gnu_hash_neon.cpp │ │ │ ├── linker_gnu_hash_neon.h │ │ │ ├── linker_main.cpp │ │ │ ├── linker_mapped_file_fragment.cpp │ │ │ ├── linker_namespaces.cpp │ │ │ ├── linker_namespaces.h │ │ │ ├── linker_note_gnu_property.cpp │ │ │ ├── linker_phdr_16kib_compat.cpp │ │ │ ├── linker_relocate.cpp │ │ │ ├── linker_relocate.h │ │ │ ├── linker_relocs.h │ │ │ ├── linker_sleb128.h │ │ │ ├── linker_soinfo.cpp │ │ │ ├── linker_soinfo.h │ │ │ ├── linker_symbol.cpp │ │ │ ├── linker_symbol.h │ │ │ ├── linker_tls.cpp │ │ │ ├── linker_tls.h │ │ │ ├── linker_util.cpp │ │ │ ├── linker_util.h │ │ │ ├── local_block_allocator.cpp │ │ │ ├── local_block_allocator.h │ │ │ ├── scoped_pthread_mutex_locker.h │ │ │ └── xz │ │ │ │ ├── xz.h │ │ │ │ ├── xz_config.h │ │ │ │ ├── xz_crc32.c │ │ │ │ ├── xz_crc64.c │ │ │ │ ├── xz_dec_bcj.c │ │ │ │ ├── xz_dec_lzma2.c │ │ │ │ ├── xz_dec_stream.c │ │ │ │ ├── xz_lzma2.h │ │ │ │ ├── xz_private.h │ │ │ │ └── xz_stream.h │ │ ├── module_config.h.in │ │ └── symbol.map.txt │ └── java │ │ └── com │ │ └── sanfengandroid │ │ └── fakelinker │ │ ├── ErrorCode.java │ │ └── FakeLinker.java │ └── stub │ └── java │ └── dalvik │ └── system │ └── BaseDexClassLoader.java ├── local.properties.sample └── settings.gradle /.clang-format: -------------------------------------------------------------------------------- 1 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | --- 3 | BasedOnStyle: LLVM 4 | IndentWidth: 2 5 | TabWidth: 2 6 | --- 7 | Language: Cpp 8 | BasedOnStyle: LLVM 9 | 10 | AllowShortFunctionsOnASingleLine: true 11 | AllowShortIfStatementsOnASingleLine: false 12 | AlwaysBreakTemplateDeclarations: Yes 13 | AlignAfterOpenBracket: Align 14 | AlignArrayOfStructures: Left 15 | AlignConsecutiveMacros: AcrossEmptyLinesAndComments 16 | AlignOperands: false 17 | AlignTrailingComments: true 18 | AlignEscapedNewlines: Right 19 | AllowAllArgumentsOnNextLine: true 20 | AllowShortBlocksOnASingleLine: Empty 21 | AllowShortLambdasOnASingleLine: Empty 22 | AllowShortLoopsOnASingleLine: true 23 | BraceWrapping: 24 | AfterCaseLabel: false 25 | AfterClass: false 26 | AfterControlStatement: false 27 | AfterEnum: false 28 | AfterFunction: false 29 | AfterNamespace: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | BeforeLambdaBody: false 36 | BeforeWhile: false 37 | SplitEmptyFunction: false 38 | SplitEmptyRecord: false 39 | SplitEmptyNamespace: false 40 | BreakBeforeBraces: Custom 41 | BreakConstructorInitializers: AfterColon 42 | ColumnLimit: 120 43 | ContinuationIndentWidth: 2 44 | IncludeCategories: 45 | - Regex: '^<.*' 46 | Priority: 1 47 | - Regex: '^".*' 48 | Priority: 2 49 | - Regex: '.*' 50 | Priority: 3 51 | IncludeIsMainRegex: '([-_](test|unittest))?$' 52 | MacroBlockBegin: '' 53 | MacroBlockEnd: '' 54 | MaxEmptyLinesToKeep: 2 55 | ... 56 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths: 7 | - "**.gradle" 8 | - "**.py" 9 | - ".github/**" 10 | - "emulator-testapp/**" 11 | - "library/**" 12 | - "fakelinker-test/**" 13 | - "Stub/**" 14 | tags: 15 | - "v*" 16 | pull_request: 17 | branches: ["main"] 18 | workflow_dispatch: 19 | 20 | env: 21 | BROWSER_TEST_MAX_DURATION: 600 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: set up JDK 17 30 | uses: actions/setup-java@v4 31 | with: 32 | java-version: "17" 33 | distribution: "temurin" 34 | cache: gradle 35 | 36 | - name: Install store file 37 | run: echo ${{ secrets.STORE_FILE }} | base64 -d > fake.jks 38 | - name: Install build configuration 39 | run: | 40 | echo storeFile=`pwd`/fake.jks > local.properties 41 | echo storePassword=${{ secrets.STORE_PASSWORD }} >> local.properties 42 | echo keyAlias=${{ secrets.KEY_ALIAS }} >> local.properties 43 | echo keyPassword=${{ secrets.KEY_PASSWORD }} >> local.properties 44 | echo logLevel=3 >> local.properties 45 | echo targetSdk=34 >> local.properties 46 | - name: Grant execute permission for gradlew 47 | run: chmod +x gradlew 48 | - name: Build with Gradle 49 | run: ./gradlew -PmergeBuild=true -PemulatorBuild=false fakelinker-test:assembleDebug fakelinker-test:assembleDebugAndroidTest library:assembleDebug library:assembleRelease 50 | - name: Copy output 51 | run: | 52 | mkdir -p out/test_binary out/test_apk 53 | mv fakelinker-test/src/main/cpp/build/Debug out/test_binary 54 | mv fakelinker-test/build/outputs/apk/debug/fakelinker-test-debug.apk out/test_apk/fakelinker-test-debug.apk 55 | mv fakelinker-test/build/outputs/apk/androidTest/debug/fakelinker-test-debug-androidTest.apk out/test_apk/fakelinker-androidtest-debug-testsuite.apk 56 | mv library/build/outputs/aar/library-debug.aar out/fakelinker-lib-debug.aar 57 | mv library/build/outputs/aar/library-release.aar out/fakelinker-lib-release.aar 58 | 59 | - name: Build emulator version with Gradle 60 | run: | 61 | rm -rf library/build/intermediates/merged_native_libs 62 | ./gradlew -PmergeBuild=true -PemulatorBuild=true emulator-testapp:assembleDebug library:assembleDebug library:assembleRelease 63 | 64 | - name: Copy emulator version output 65 | run: | 66 | mv emulator-testapp/build/outputs/apk/debug/emulator-testapp-debug.apk out/test_apk/fakelinker-emulator-test-debug.apk 67 | mv library/build/outputs/aar/library-debug.aar out/fakelinker-emulator-lib-debug.aar 68 | mv library/build/outputs/aar/library-release.aar out/fakelinker-emulator-lib-release.aar 69 | 70 | - name: Build 32-bit test app 71 | run: | 72 | rm -rf library/build/intermediates/merged_native_libs 73 | rm -rf fakelinker-test/build/intermediates/merged_native_libs 74 | echo "abis=armeabi-v7a,x86" >> local.properties 75 | ./gradlew fakelinker-test:assembleDebug 76 | 77 | - name: Copy 32-bit test app 78 | run: | 79 | mv fakelinker-test/build/outputs/apk/debug/fakelinker-test-debug.apk out/test_apk/fakelinker-test-32bit-debug.apk 80 | 81 | - name: Uninstall store file 82 | run: rm -f fake.jks 83 | - name: Upload Build Artifact 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: ${{ github.sha }}-build 87 | # A file, directory or wildcard pattern that describes what to upload 88 | path: out 89 | 90 | test_apk: 91 | runs-on: ubuntu-latest 92 | needs: build 93 | steps: 94 | - uses: actions/checkout@v4 95 | - name: Setup Python 96 | uses: actions/setup-python@v5 97 | with: 98 | python-version: "3.12" 99 | - name: Install dependencies 100 | run: | 101 | pip install requests 102 | - name: Download artifact 103 | uses: actions/download-artifact@v4 104 | with: 105 | name: ${{ github.sha }}-build 106 | path: ./artifacts 107 | - name: Run BrowerStack 64bit Test 108 | run: | 109 | python ./browser_stack_test.py test \ 110 | --apk artifacts/test_apk/fakelinker-test-debug.apk \ 111 | --test-suite artifacts/test_apk/fakelinker-androidtest-debug-testsuite.apk \ 112 | --apk-custom-id FakelinkerTestApp \ 113 | --test-suite-custom-id FakelinkerTestSuite \ 114 | --build-last-failed \ 115 | --all-api \ 116 | --device-log 117 | env: 118 | BROWSER_STACK_KEY: ${{ secrets.BROWSER_STACK_KEY }} 119 | - name: Run BrowerStack 32bit Test 120 | run: | 121 | python ./browser_stack_test.py test \ 122 | --apk artifacts/test_apk/fakelinker-test-32bit-debug.apk \ 123 | --test-suite artifacts/test_apk/fakelinker-androidtest-debug-testsuite.apk \ 124 | --apk-custom-id FakelinkerTestApp \ 125 | --test-suite-custom-id FakelinkerTestSuite \ 126 | --build-last-failed \ 127 | --all-api \ 128 | --device-log \ 129 | --is-32bit 130 | env: 131 | BROWSER_STACK_KEY: ${{ secrets.BROWSER_STACK_KEY }} 132 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Android CI"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | if: github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'v') && contains(github.event.workflow_run.head_branch, '.') 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Download artifact 19 | uses: actions/download-artifact@v4 20 | with: 21 | name: ${{ github.sha }}-build 22 | path: ./artifacts 23 | github-token: ${{ secrets.GITHUB_TOKEN }} 24 | run-id: ${{ github.event.workflow_run.id }} 25 | 26 | - name: Extract Release Files 27 | run: | 28 | mkdir -p ./release 29 | cp ./artifacts/fakelinker-lib-debug.aar ./release/fakelinker-debug.aar 30 | cp ./artifacts/fakelinker-lib-release.aar ./release/fakelinker-release.aar 31 | - name: Release Tag 32 | uses: softprops/action-gh-release@v2 33 | with: 34 | files: | 35 | release/fakelinker-debug.aar 36 | release/fakelinker-release.aar 37 | name: ${{ github.event.workflow_run.head_branch }} 38 | tag_name: ${{ github.event.workflow_run.head_branch }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | build/ 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | local.properties 18 | library/src/main/cpp/include/fakelinker/linker_version.h 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fake-linker"] 2 | path = fake-linker 3 | url = https://github.com/sanfengAndroid/fake-linker.git 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/beichenzhizuoshi/pre-commit-hooks 3 | rev: v1.0.2 4 | hooks: 5 | # automatically convert file encoding before submitting file 6 | - id: transform-encoding 7 | - id: chinese-transform-encoding 8 | - id: crlf-to-lf 9 | - repo: https://github.com/pre-commit/mirrors-clang-format 10 | rev: v19.1.4 11 | hooks: 12 | - id: clang-format 13 | - repo: https://github.com/pre-commit/pre-commit-hooks 14 | rev: v5.0.0 15 | hooks: 16 | - id: trailing-whitespace 17 | - id: double-quote-string-fixer 18 | - id: name-tests-test 19 | - id: requirements-txt-fixer 20 | - id: check-case-conflict 21 | - id: fix-encoding-pragma 22 | - id: mixed-line-ending 23 | - repo: https://github.com/hhatto/autopep8 24 | rev: v2.3.1 25 | hooks: 26 | - id: autopep8 27 | args: 28 | ["-i", "--indent-size=2", "--max-line-length=120", "--experimental"] 29 | - repo: https://github.com/asottile/reorder_python_imports 30 | rev: v3.14.0 31 | hooks: 32 | - id: reorder-python-imports 33 | -------------------------------------------------------------------------------- /Stub/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /Stub/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | namespace 'com.sanfengandroid.stub' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | minSdk 21 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles "consumer-rules.pro" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | dependencies { 29 | } -------------------------------------------------------------------------------- /Stub/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengAndroid/fake-linker/7bd4250dd63c06e53af0ee6b71f7c3f38ca9236e/Stub/consumer-rules.pro -------------------------------------------------------------------------------- /Stub/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /Stub/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Stub/src/main/java/dalvik/system/VMRuntime.java: -------------------------------------------------------------------------------- 1 | package dalvik.system; 2 | 3 | public class VMRuntime { 4 | 5 | public static VMRuntime getRuntime() { 6 | throw new UnsupportedOperationException("Stub"); 7 | } 8 | 9 | public boolean is64Bit() { throw new UnsupportedOperationException("Stub"); } 10 | } 11 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.sanfengandroid.demo' 7 | compileSdk rootProject.targetSdk 8 | 9 | defaultConfig { 10 | minSdk 21 11 | targetSdk rootProject.targetSdk 12 | versionCode 100 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | externalNativeBuild { 18 | cmake { 19 | cppFlags '' 20 | } 21 | } 22 | } 23 | 24 | if (rootProject.hasSign) { 25 | signingConfigs { 26 | sign { 27 | storeFile file(rootProject.storeFile) 28 | storePassword rootProject.storePassword 29 | keyAlias rootProject.keyAlias 30 | keyPassword rootProject.keyPassword 31 | } 32 | } 33 | } 34 | 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | externalNativeBuild { 46 | cmake { 47 | path file('src/main/cpp/CMakeLists.txt') 48 | version '3.22.1' 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | testImplementation 'junit:junit:4.13.2' 55 | implementation(project(':library')) 56 | androidTestImplementation 'androidx.test.ext:junit:1.2.1' 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' 58 | } -------------------------------------------------------------------------------- /demo/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanfengAndroid/fake-linker/7bd4250dd63c06e53af0ee6b71f7c3f38ca9236e/demo/consumer-rules.pro -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /demo/src/androidTest/java/com/sanfengandroid/demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.sanfengandroid.demo; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | @Test 19 | public void useAppContext() { 20 | // Context of the app under test. 21 | Context appContext = 22 | InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | assertEquals("com.sanfengandroid.demo.test", appContext.getPackageName()); 24 | } 25 | } -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /demo/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # For more information about using CMake with Android Studio, read the 3 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 4 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 5 | 6 | # Sets the minimum CMake version required for this project. 7 | cmake_minimum_required(VERSION 3.4.1) 8 | project("demo") 9 | 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | set(CMAKE_CXX_STANDARD 17) 12 | 13 | if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") 14 | set(FAKELINKER_LOG_LEVEL 0) 15 | endif() 16 | 17 | set(FAKELINKER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../library/src/main/cpp) 18 | 19 | add_subdirectory(${FAKELINKER_DIR} lib) 20 | 21 | find_library(log-lib log) 22 | 23 | add_library(static_demo SHARED 24 | static_demo.cpp 25 | ) 26 | 27 | target_link_libraries(static_demo 28 | PRIVATE 29 | fakelinker_static 30 | ${log-lib} 31 | ) 32 | 33 | set_target_properties(static_demo PROPERTIES 34 | LINK_FLAGS 35 | "${LINK_FLAGS} -Wl,--gc-sections,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/symbol.map.txt\"" 36 | ) 37 | 38 | add_library(shared_demo SHARED 39 | shared_demo.cpp 40 | ) 41 | 42 | target_link_libraries(shared_demo 43 | PRIVATE 44 | fakelinker 45 | ${log-lib} 46 | ) 47 | 48 | set_target_properties(shared_demo PROPERTIES 49 | LINK_FLAGS 50 | "${LINK_FLAGS} -Wl,--gc-sections,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/symbol.map.txt\"" 51 | ) 52 | 53 | add_library(native_test SHARED 54 | native_test.cpp 55 | ) 56 | target_link_libraries(native_test PRIVATE 57 | ${log-lib} 58 | ) -------------------------------------------------------------------------------- /demo/src/main/cpp/native_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by beichen on 2025/3/12. 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | #define LOG_TAG "NativeTest" 9 | 10 | #define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__); 11 | 12 | extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { 13 | JNIEnv *env; 14 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { 15 | LOGE("JNI environment error"); 16 | return JNI_EVERSION; 17 | } 18 | 19 | if (access("/test/hook/path", F_OK) != 0) { 20 | LOGE("hook module error"); 21 | return JNI_ERR; 22 | } 23 | return JNI_VERSION_1_6; 24 | } -------------------------------------------------------------------------------- /demo/src/main/cpp/shared_demo.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by beichen on 2025/3/12. 3 | // 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | static int (*orig_access)(const char *pathname, int mode) = nullptr; 11 | 12 | int g_log_level = 0; 13 | 14 | C_API API_PUBLIC int access(const char *pathname, int mode) { 15 | LOGW("shared hook access function path: %s, mode: %d", pathname, mode); 16 | if (strncmp(pathname, "/test/hook/path", 15) == 0) { 17 | LOGW("fake access /test/hook/path"); 18 | return 0; 19 | } 20 | // Does not block other calls 21 | return orig_access(pathname, mode); 22 | } 23 | 24 | C_API JNIEXPORT void fakelinker_module_init(JNIEnv *env, SoinfoPtr fake_soinfo, const FakeLinker *fake_linker) { 25 | if (!fake_linker->is_init_success()) { 26 | LOGE("fakelinker environment initialization failed, cannot continue testing"); 27 | return; 28 | } 29 | SoinfoPtr libc = fake_linker->soinfo_find(SoinfoFindType::kSTName, "libc.so", nullptr); 30 | if (!libc) { 31 | LOGE("fakelinker find libc soinfo failed"); 32 | return; 33 | } 34 | 35 | orig_access = reinterpret_cast( 36 | fake_linker->soinfo_get_export_symbol_address(libc, "access", nullptr)); 37 | if (!orig_access) { 38 | LOGE("fakelinker find libc symbol access failed"); 39 | return; 40 | } 41 | // Use so itself as a global library and hook all so loaded later 42 | if (!fake_linker->soinfo_add_to_global(fake_soinfo)) { 43 | LOGE("fakelinker add global soinfo failed"); 44 | return; 45 | } 46 | const char *loaded_libs[] = { 47 | "libjavacore.so", 48 | }; 49 | // Since libjavacore has been loaded before we load it, symbol relocation will not be triggered. 50 | // At this time, manually relocate it to make the hook take effect. 51 | if (!fake_linker->call_manual_relocation_by_names(fake_soinfo, 1, loaded_libs)) { 52 | LOGE("relocation libjavacore.so failed"); 53 | return; 54 | } 55 | LOGW("fakelinker shared load successfully"); 56 | } -------------------------------------------------------------------------------- /demo/src/main/cpp/static_demo.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by beichen on 2025/3/12. 3 | // 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | static int (*orig_access)(const char *pathname, int mode) = nullptr; 11 | 12 | C_API API_PUBLIC int access(const char *pathname, int mode) { 13 | LOGW("static hook access function path: %s, mode: %d", pathname, mode); 14 | if (strncmp(pathname, "/test/hook/path", 15) == 0) { 15 | LOGW("fake access /test/hook/path"); 16 | return 0; 17 | } 18 | // Does not block other calls 19 | return orig_access(pathname, mode); 20 | } 21 | 22 | C_API JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { 23 | JNIEnv *env; 24 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { 25 | LOGE("JNI environment error"); 26 | return JNI_EVERSION; 27 | } 28 | if (init_fakelinker(env, 29 | static_cast(FakeLinkerMode::kFMSoinfo | FakeLinkerMode::kFMNativeHook | 30 | FakeLinkerMode::kFMJavaRegister), 31 | nullptr) != 0) { 32 | LOGE("fakelinker environment error"); 33 | return JNI_ERR; 34 | } 35 | 36 | const FakeLinker *fake_linker = get_fakelinker(); 37 | 38 | SoinfoPtr libc = fake_linker->soinfo_find(SoinfoFindType::kSTName, "libc.so", nullptr); 39 | if (!libc) { 40 | LOGE("fakelinker find libc soinfo failed"); 41 | return JNI_ERR; 42 | } 43 | 44 | orig_access = reinterpret_cast( 45 | fake_linker->soinfo_get_export_symbol_address(libc, "access", nullptr)); 46 | if (!orig_access) { 47 | LOGE("fakelinker find libc symbol access failed"); 48 | return JNI_ERR; 49 | } 50 | 51 | SoinfoPtr fake_soinfo = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, nullptr); 52 | if (fake_soinfo == nullptr) { 53 | LOGE("find self soinfo failed"); 54 | return JNI_ERR; 55 | } 56 | 57 | // Use so itself as a global library and hook all so loaded later 58 | if (!fake_linker->soinfo_add_to_global(fake_soinfo)) { 59 | LOGE("fakelinker add global soinfo failed"); 60 | return JNI_ERR; 61 | } 62 | const char *loaded_libs[] = { 63 | "libjavacore.so", 64 | }; 65 | // Since libjavacore has been loaded before we load it, symbol relocation will not be triggered. 66 | // At this time, manually relocate it to make the hook take effect. 67 | if (!fake_linker->call_manual_relocation_by_names(fake_soinfo, 1, loaded_libs)) { 68 | LOGE("relocation libjavacore.so failed"); 69 | return JNI_ERR; 70 | } 71 | LOGW("fakelinker static load successfully"); 72 | return JNI_VERSION_1_6; 73 | } -------------------------------------------------------------------------------- /demo/src/main/cpp/symbol.map.txt: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | JNI_OnLoad*; 4 | access*; 5 | fakelinker_module_init*; 6 | local: 7 | *; 8 | }; -------------------------------------------------------------------------------- /demo/src/main/java/com/sanfengandroid/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 WebViewDebugHook by sanfengAndroid. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sanfengandroid.demo; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.app.Activity; 21 | import android.os.Bundle; 22 | import android.util.Log; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | import com.sanfengandroid.fakelinker.FakeLinker; 26 | import java.io.File; 27 | 28 | public class MainActivity extends Activity { 29 | private static final String TAG = "FakeLinkerTest"; 30 | private TextView loadStaticResult; 31 | private TextView loadSharedResult; 32 | private TextView hookTestResult; 33 | private TextView hookNativeTestResult; 34 | 35 | @SuppressLint("MissingInflatedId") 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_main); 40 | loadStaticResult = findViewById(R.id.load_static_result); 41 | loadSharedResult = findViewById(R.id.load_shared_result); 42 | hookTestResult = findViewById(R.id.hook_test_result); 43 | hookNativeTestResult = findViewById(R.id.hook_native_test_result); 44 | findViewById(R.id.load_static).setOnClickListener(v -> { 45 | if (isInit(loadSharedResult)) { 46 | Toast 47 | .makeText(this, 48 | "currently the shared demo has been tested, please " 49 | + "restart the app and try again", 50 | Toast.LENGTH_LONG) 51 | .show(); 52 | return; 53 | } 54 | try { 55 | System.loadLibrary("static_demo"); 56 | loadStaticResult.setText("pass"); 57 | } catch (Throwable e) { 58 | Log.e(TAG, "load static demo failed", e); 59 | loadStaticResult.setText("failed"); 60 | } 61 | }); 62 | findViewById(R.id.load_shared).setOnClickListener(v -> { 63 | if (isInit(loadStaticResult)) { 64 | Toast 65 | .makeText(this, 66 | "currently the static demo has been tested, please " 67 | + "restart the app and try again", 68 | Toast.LENGTH_LONG) 69 | .show(); 70 | return; 71 | } 72 | try { 73 | FakeLinker.initFakeLinker(null, "libshared_demo.so"); 74 | loadSharedResult.setText("pass"); 75 | } catch (Throwable e) { 76 | Log.e(TAG, "load shared demo failed", e); 77 | loadSharedResult.setText("failed"); 78 | } 79 | }); 80 | 81 | findViewById(R.id.hook_native_test).setOnClickListener(v -> { 82 | if (!isInit(loadStaticResult) && !isInit(loadSharedResult)) { 83 | Toast 84 | .makeText(this, "Please click Load Static/Shared Demo first", 85 | Toast.LENGTH_LONG) 86 | .show(); 87 | return; 88 | } 89 | try { 90 | System.loadLibrary("native_test"); 91 | hookNativeTestResult.setText("pass"); 92 | } catch (Throwable e) { 93 | Log.e(TAG, "test fakelinker native hook failed"); 94 | hookNativeTestResult.setText("failed"); 95 | } 96 | }); 97 | 98 | findViewById(R.id.hook_test).setOnClickListener(v -> { 99 | if (!isInit(loadStaticResult) && !isInit(loadSharedResult)) { 100 | Toast 101 | .makeText(this, "Please click Load Static/Shared Demo first", 102 | Toast.LENGTH_LONG) 103 | .show(); 104 | return; 105 | } 106 | hookTestResult.setText(this.test() ? "pass" : "failed"); 107 | }); 108 | } 109 | 110 | boolean test() { 111 | File file = new File("/test/hook/path"); 112 | return file.exists(); 113 | } 114 | 115 | private boolean isInit(TextView tv) { 116 | if ("pass".equals(tv.getText().toString())) { 117 | return true; 118 | } 119 | return "failed".equals(tv.getText().toString()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 |