├── .gitignore ├── README.md ├── ThreadHookSample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── dodola │ │ └── thread │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── linker │ │ │ ├── Build.h │ │ │ ├── CMakeLists.txt │ │ │ ├── abort_with_reason.c │ │ │ ├── abort_with_reason.h │ │ │ ├── bionic_linker.h │ │ │ ├── elfSharedLibData.cpp │ │ │ ├── elfSharedLibData.h │ │ │ ├── hooks.cpp │ │ │ ├── hooks.h │ │ │ ├── link.cpp │ │ │ ├── link.h │ │ │ ├── linker.cpp │ │ │ ├── linker.h │ │ │ ├── linux_syscall_support.h │ │ │ ├── locks.h │ │ │ ├── log_assert.h │ │ │ ├── phaser.c │ │ │ ├── phaser.h │ │ │ ├── sharedlibs.cpp │ │ │ ├── sharedlibs.h │ │ │ ├── sig_safe_write.c │ │ │ ├── sig_safe_write.h │ │ │ ├── sigmux.c │ │ │ ├── sigmux.h │ │ │ ├── trampoline.cpp │ │ │ ├── trampoline.h │ │ │ ├── trampoline_arm.c │ │ │ └── trampoline_x86.c │ │ └── threadHook.cpp │ ├── java │ │ └── com │ │ │ └── dodola │ │ │ └── thread │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ └── ThreadHook.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.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── dodola │ └── thread │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | **/build/ 6 | /captures 7 | **/.externalNativeBuild/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chapter06-plus 2 | 3 | 该项目展示了如何使用 PLTHook 技术来获取线程创建的堆栈 4 | 5 | 运行环境 6 | ===== 7 | AndroidStudio3.2 8 | NDK16~19 9 | 支持 `x86` `armeabi-v7a` 10 | 11 | 说明 12 | ==== 13 | 14 | 运行项目后点击`开启 Thread Hook`按钮,然后点击`新建 Thread`按钮。在Logcat 日志中查看到捕获的日志,类似如下: 15 | 16 | ``` 17 | com.dodola.thread.MainActivity$2.onClick(MainActivity.java:33) 18 | android.view.View.performClick(View.java:5637) 19 | android.view.View$PerformClick.run(View.java:22429) 20 | android.os.Handler.handleCallback(Handler.java:751) 21 | android.os.Handler.dispatchMessage(Handler.java:95) 22 | android.os.Looper.loop(Looper.java:154) 23 | android.app.ActivityThread.main(ActivityThread.java:6121) 24 | java.lang.reflect.Method.invoke(Native Method) 25 | com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) 26 | com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) 27 | ``` 28 | 29 | 30 | 实现步骤 31 | ==== 32 | 33 | ### 寻找Hook点 34 | 35 | 我们来先线程的启动流程,可以参考这篇文章[Android线程的创建过程](https://www.jianshu.com/p/a26d11502ec8) 36 | 37 | [java_lang_Thread.cc](http://androidxref.com/9.0.0_r3/xref/art/runtime/native/java_lang_Thread.cc#43):Thread_nativeCreate 38 | ``` 39 | static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size, jboolean daemon) { 40 | Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE); 41 | } 42 | ``` 43 | 44 | [thread.cc](http://androidxref.com/9.0.0_r3/xref/art/runtime/thread.cc) 中的CreateNativeThread函数 45 | 46 | ``` 47 | void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { 48 | ... 49 | pthread_create_result = pthread_create(&new_pthread, 50 | &attr, 51 | Thread::CreateCallback, 52 | child_thread); 53 | ... 54 | } 55 | ``` 56 | 57 | 整个流程就非常简单了,我们可以使用inline hook函数Thread_nativeCreate或者CreateNativeThread。 58 | 59 | 不过考虑到inline hook的兼容性,我们更希望使用got hook或者plt hook。 60 | 61 | pthread_create就是一个非常好的点,我们可以利用它来做文章 62 | 63 | ### 查找Hook的So 64 | 上面Thread_nativeCreate、CreateNativeThread和pthread_create函数分别编译在哪个library中呢? 65 | 66 | 很简单,我们看看编译脚本[Android.bp](http://androidxref.com/9.0.0_r3/xref/art/runtime/Android.bp)就知道了。 67 | 68 | ``` 69 | art_cc_library { 70 | name: "libart", 71 | defaults: ["libart_defaults"], 72 | } 73 | 74 | cc_defaults { 75 | name: "libart_defaults", 76 | defaults: ["art_defaults"], 77 | host_supported: true, 78 | srcs: [ 79 | thread.cc", 80 | ] 81 | } 82 | ``` 83 | 84 | 可以看到是在"libart.so"中,而pthread_create熟悉的人都知道它是在"libc.so"中的。 85 | 86 | ### 查找Hook函数的符号 87 | 88 | C++ 的函数名会 Name Mangling,我们需要看看导出符号。 89 | 90 | ``` 91 | readelf -a libart.so 92 | 93 | ``` 94 | 95 | pthread_create函数的确是在libc.so中,而且因为c编译的不需要deMangling 96 | 97 | ``` 98 | 001048a0 0007fc16 R_ARM_JUMP_SLOT 00000000 pthread_create@LIBC 99 | ``` 100 | 101 | ### 真正实现 102 | 103 | 剩下的实现就非常简单了,如果你想监控其他so库的pthread_create。 104 | 105 | profilo中也有一种做法是把目前已经加载的所有so都统一hook了,考虑到性能问题,我们并没有这么做,而且只hook指定的so. 106 | 107 | ``` 108 | hook_plt_method("libart.so", "pthread_create", (hook_func) &pthread_create_hook); 109 | 110 | ``` 111 | 112 | 而pthread_create的参数直接查看[pthread.h](http://androidxref.com/9.0.0_r3/xref/bionic/libc/include/pthread.h)就可以了。 113 | 114 | ``` 115 | int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*); 116 | ``` 117 | 118 | 获取堆栈是在native反射Java的方法 119 | 120 | ``` 121 | jstring java_stack = static_cast(jniEnv->CallStaticObjectMethod(kJavaClass, kMethodGetStack)); 122 | ``` 123 | 124 | 可以看到整个流程的确是so easy. -------------------------------------------------------------------------------- /ThreadHookSample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ThreadHookSample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.dodola.thread" 7 | minSdkVersion 18 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | externalNativeBuild { 13 | cmake { 14 | abiFilters "armeabi-v7a", "x86" 15 | } 16 | } 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | externalNativeBuild { 25 | cmake { 26 | path "src/main/cpp/CMakeLists.txt" 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 34 | testImplementation 'junit:junit:4.12' 35 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 36 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 37 | } 38 | -------------------------------------------------------------------------------- /ThreadHookSample/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 22 | -------------------------------------------------------------------------------- /ThreadHookSample/src/androidTest/java/com/dodola/thread/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.dodola.thread; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.dodola.atrace", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.6) 3 | set(CMAKE_CXX_EXTENSIONS OFF) 4 | set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -std=gnu11 -pthread -marm ") 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -std=c++11 -pthread ") 7 | #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -Wl,--hash-style=gun") 8 | 9 | 10 | if (${ANDROID_ABI} STREQUAL "x86") 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -std=c++11 -pthread -D__X86__") 12 | else () 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -std=c++11 -pthread -D__ARM__ ") 14 | endif () 15 | 16 | 17 | add_library( 18 | threadhook 19 | SHARED 20 | threadHook.cpp 21 | ) 22 | 23 | 24 | add_subdirectory(linker) 25 | include_directories(linker) 26 | 27 | 28 | find_library( 29 | log-lib 30 | log) 31 | 32 | 33 | target_link_libraries( 34 | threadhook linker 35 | ${log-lib}) -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/Build.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include 20 | 21 | #if defined(__ANDROID__) 22 | #include 23 | #endif 24 | 25 | namespace facebook { 26 | namespace build { 27 | 28 | struct Build { 29 | static int getAndroidSdk() { 30 | static auto android_sdk = ([] { 31 | char sdk_version_str[PROP_VALUE_MAX]; 32 | __system_property_get("ro.build.version.sdk", sdk_version_str); 33 | return atoi(sdk_version_str); 34 | })(); 35 | return android_sdk; 36 | } 37 | }; 38 | 39 | } // namespace build 40 | } // namespace facebook 41 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set(UTILS_SOURCE 4 | elfSharedLibData.cpp 5 | hooks.cpp 6 | link.cpp 7 | linker.cpp 8 | phaser.c 9 | sharedlibs.cpp 10 | sig_safe_write.c 11 | sigmux.c 12 | trampoline.cpp 13 | abort_with_reason.c 14 | ) 15 | 16 | if (${ANDROID_ABI} STREQUAL "x86") 17 | set(TRAMPOLINE trampoline_x86.c) 18 | else () 19 | set(TRAMPOLINE trampoline_arm.c) 20 | endif () 21 | 22 | message(${TRAMPOLINE}) 23 | add_library(linker 24 | STATIC 25 | ${UTILS_SOURCE} 26 | ${TRAMPOLINE} 27 | ) 28 | target_link_libraries(linker) -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/abort_with_reason.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "abort_with_reason.h" 18 | #include 19 | 20 | void abortWithReasonImpl(const char* reason) { 21 | // FBLOGE(reason); 22 | abort(); 23 | } 24 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/abort_with_reason.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #define __ABORT_MESSAGE_PREFIX(file, line) file ":" line " " 24 | #define __TO_STR_INDIRECTED(x) #x 25 | #define __TO_STR(x) __TO_STR_INDIRECTED(x) 26 | 27 | #define abortWithReason(msg) \ 28 | abortWithReasonImpl(__ABORT_MESSAGE_PREFIX(__FILE__, __TO_STR(__LINE__)) msg) 29 | 30 | __attribute__((noreturn)) void abortWithReasonImpl(const char* reason); 31 | 32 | #ifdef __cplusplus 33 | } // extern "C" 34 | #endif 35 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/bionic_linker.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-present, Facebook, Inc. 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 | /* 18 | * Copyright (C) 2008 The Android Open Source Project 19 | * All rights reserved. 20 | * 21 | * Redistribution and use in source and binary forms, with or without 22 | * modification, are permitted provided that the following conditions 23 | * are met: 24 | * * Redistributions of source code must retain the above copyright 25 | * notice, this list of conditions and the following disclaimer. 26 | * * Redistributions in binary form must reproduce the above copyright 27 | * notice, this list of conditions and the following disclaimer in 28 | * the documentation and/or other materials provided with the 29 | * distribution. 30 | * 31 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 34 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 35 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 36 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 37 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 38 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 39 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 40 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 41 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 42 | * SUCH DAMAGE. 43 | */ 44 | 45 | #pragma once 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | 53 | // Magic shared structures that GDB knows about. 54 | struct link_map_t { 55 | uintptr_t l_addr; 56 | char *l_name; 57 | uintptr_t l_ld; 58 | link_map_t *l_next; 59 | link_map_t *l_prev; 60 | }; 61 | 62 | #define SOINFO_NAME_LEN 128 63 | 64 | typedef void (*linker_function_t)(); 65 | 66 | // NOTE: This struct is ONLY accurate for API <21. Do not use for later versions. 67 | // It uses 32-bit ELF structures (pre-L didn't have x64 support) and the struct 68 | // offsets are different in newer versions. 69 | // We don't need this on 21+, as we'll grab the info we require from dl_iterate_phdr 70 | struct soinfo { 71 | public: 72 | char name[SOINFO_NAME_LEN]; 73 | const Elf32_Phdr *phdr; 74 | size_t phnum; 75 | Elf32_Addr entry; 76 | Elf32_Addr base; 77 | unsigned size; 78 | 79 | uint32_t unused1; // DO NOT USE, maintained for compatibility. 80 | 81 | Elf32_Dyn *dynamic; 82 | 83 | uint32_t unused2; // DO NOT USE, maintained for compatibility 84 | uint32_t unused3; // DO NOT USE, maintained for compatibility 85 | 86 | soinfo *next; 87 | unsigned flags; 88 | 89 | const char *strtab; 90 | Elf32_Sym *symtab; 91 | 92 | size_t nbucket; 93 | size_t nchain; 94 | unsigned *bucket; 95 | unsigned *chain; 96 | 97 | unsigned *plt_got; 98 | 99 | Elf32_Rel *plt_rel; 100 | size_t plt_rel_count; 101 | 102 | Elf32_Rel *rel; 103 | size_t rel_count; 104 | 105 | linker_function_t *preinit_array; 106 | size_t preinit_array_count; 107 | 108 | linker_function_t *init_array; 109 | size_t init_array_count; 110 | linker_function_t *fini_array; 111 | size_t fini_array_count; 112 | 113 | linker_function_t init_func; 114 | linker_function_t fini_func; 115 | 116 | #if defined(__arm__) 117 | // ARM EABI section used for stack unwinding. 118 | unsigned* ARM_exidx; 119 | size_t ARM_exidx_count; 120 | #endif 121 | 122 | size_t ref_count; 123 | link_map_t link_map; 124 | 125 | bool constructors_called; 126 | 127 | // When you read a virtual address from the ELF file, add this 128 | // value to get the corresponding address in the process' address space. 129 | Elf32_Addr load_bias; 130 | 131 | bool has_text_relocations; 132 | bool has_DT_SYMBOLIC; 133 | }; 134 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/elfSharedLibData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "bionic_linker.h" 18 | #include "locks.h" 19 | #include "elfSharedLibData.h" 20 | 21 | #include "Build.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #define R_386_JUMP_SLOT 7 30 | #define R_ARM_JUMP_SLOT 22 31 | #define R_X86_64_JUMP_SLOT 7 32 | #define R_AARCH64_JUMP_SLOT 1026 33 | 34 | #if defined(__arm__) 35 | #define PLT_RELOCATION_TYPE R_ARM_JUMP_SLOT 36 | #elif defined(__i386__) 37 | #define PLT_RELOCATION_TYPE R_386_JUMP_SLOT 38 | #elif defined(__aarch64__) 39 | #define PLT_RELOCATION_TYPE R_AARCH64_JUMP_SLOT 40 | #elif defined(__x86_64__) 41 | #define PLT_RELOCATION_TYPE R_X86_64_JUMP_SLOT 42 | #else 43 | #error invalid arch 44 | #endif 45 | 46 | #define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ 47 | 48 | #if defined(__x86_64__) || defined(__aarch64__) 49 | # define ELF_RELOC_TYPE ELF64_R_TYPE 50 | # define ELF_RELOC_SYM ELF64_R_SYM 51 | #else 52 | # define ELF_RELOC_TYPE ELF32_R_TYPE 53 | # define ELF_RELOC_SYM ELF32_R_SYM 54 | #endif 55 | 56 | namespace facebook { 57 | namespace linker { 58 | 59 | namespace { 60 | 61 | static uint32_t elfhash(const char* name) { 62 | uint32_t h = 0, g; 63 | 64 | while (*name) { 65 | h = (h << 4) + *name++; 66 | g = h & 0xf0000000; 67 | h ^= g; 68 | h ^= g >> 24; 69 | } 70 | return h; 71 | } 72 | 73 | static uint32_t gnuhash(char const* name) { 74 | uint32_t h = 5381; 75 | while (*name != 0) { 76 | h += (h << 5) + *name++; // h*33 + c == h + h * 32 + c == h + h << 5 + c 77 | } 78 | 79 | return h; 80 | } 81 | 82 | } // namespace (anonymous) 83 | 84 | // pltRelocations is explicitly set to nullptr as a sentinel to operator bool 85 | elfSharedLibData::elfSharedLibData() {} 86 | 87 | elfSharedLibData::elfSharedLibData(dl_phdr_info const* info) { 88 | ElfW(Dyn) const* dynamic_table = nullptr; 89 | 90 | loadBias = info->dlpi_addr; 91 | libName = info->dlpi_name; 92 | 93 | for (int i = 0; i < info->dlpi_phnum; ++i) { 94 | ElfW(Phdr) const* phdr = &info->dlpi_phdr[i]; 95 | if (phdr->p_type == PT_DYNAMIC) { 96 | dynamic_table = reinterpret_cast(loadBias + phdr->p_vaddr); 97 | break; 98 | } 99 | } 100 | 101 | if (!dynamic_table) { 102 | throw input_parse_error("dynamic_table == null"); 103 | } 104 | 105 | for (ElfW(Dyn) const* entry = dynamic_table; entry && entry->d_tag != DT_NULL; ++entry) { 106 | switch (entry->d_tag) { 107 | case DT_PLTRELSZ: 108 | pltRelocationsLen = entry->d_un.d_val / sizeof(Elf_Reloc); 109 | break; 110 | 111 | case DT_JMPREL: // DT_PLTREL just declares the Rel/Rela type in use, not the table data 112 | pltRelocations = 113 | reinterpret_cast(loadBias + entry->d_un.d_ptr); 114 | break; 115 | 116 | case DT_RELSZ: 117 | case DT_RELASZ: 118 | // bionic's linker already handles sanity checking/blowing up if wrong Rel/Rela match 119 | relocationsLen = entry->d_un.d_val / sizeof(Elf_Reloc); 120 | break; 121 | 122 | case DT_REL: 123 | case DT_RELA: 124 | // bionic's linker already handles sanity checking/blowing up if wrong Rel/Rela match 125 | relocations = 126 | reinterpret_cast(loadBias + entry->d_un.d_ptr); 127 | break; 128 | 129 | // TODO (t30088113): handle DT_ANDROID_REL[A][SZ] 130 | 131 | case DT_SYMTAB: 132 | dynSymbolsTable = 133 | reinterpret_cast(loadBias + entry->d_un.d_ptr); 134 | break; 135 | 136 | case DT_STRTAB: 137 | dynStrsTable = 138 | reinterpret_cast(loadBias + entry->d_un.d_ptr); 139 | break; 140 | 141 | case DT_HASH: 142 | elfHash_.numbuckets_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[0]; 143 | elfHash_.numchains_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[1]; 144 | elfHash_.buckets_ = reinterpret_cast(loadBias + entry->d_un.d_ptr + 8); 145 | // chains_ is stored immediately after buckets_ and is the same type, so the index after 146 | // the last valid bucket value is the first valid chain value. 147 | elfHash_.chains_ = &elfHash_.buckets_[elfHash_.numbuckets_]; 148 | break; 149 | 150 | case DT_GNU_HASH: // see http://www.linker-aliens.org/blogs/ali/entry/gnu_hash_elf_sections/ 151 | // the original AOSP code uses several binary-math optimizations that differ from the "standard" 152 | // gnu hash implementation, and have been left in place with explanatory comments to avoid diverging 153 | gnuHash_.numbuckets_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[0]; 154 | gnuHash_.symoffset_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[1]; 155 | gnuHash_.bloom_size_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[2]; 156 | gnuHash_.bloom_shift_ = reinterpret_cast(loadBias + entry->d_un.d_ptr)[3]; 157 | gnuHash_.bloom_filter_ = reinterpret_cast(loadBias + entry->d_un.d_ptr + 16); 158 | gnuHash_.buckets_ = reinterpret_cast(&gnuHash_.bloom_filter_[gnuHash_.bloom_size_]); 159 | 160 | // chains_ is stored immediately after buckets_ and is the same type, so the index after 161 | // the last valid bucket value is the first valid chain value. 162 | // However, note that we subtract symoffset_ (and thus actually start the chains_ array 163 | // INSIDE the buckets_ array)! This is because the chains_ index for a symbol is negatively 164 | // offset from its dynSymbolsTable index by symoffset_. Normally, once you find a match in 165 | // chains_ you'd add symoffset_ and then you'd have your dynSymbolsTable index... but by 166 | // "shifting" the array backwards we can make the chains_ indices line up exactly with 167 | // dynSymbolsTable right from the start. 168 | // We don't have to ever worry about indexing into invalid chains_ data, because the 169 | // chain-start indices that live in buckets_ are indices into dynSymbolsTable and will thus 170 | // also never be less than symoffset_. 171 | gnuHash_.chains_ = &gnuHash_.buckets_[gnuHash_.numbuckets_ - gnuHash_.symoffset_]; 172 | 173 | // verify that bloom_size_ is a power of 2 174 | if ((((uint32_t)(gnuHash_.bloom_size_ - 1)) & gnuHash_.bloom_size_) != 0) { 175 | // shouldn't be possible; the android linker has already performed this check 176 | throw input_parse_error("bloom_size_ not power of 2"); 177 | } 178 | // since we know that bloom_size_ is a power of two, we can simplify modulus division later in 179 | // gnu_find_symbol_by_name by decrementing by 1 here and then using logical-AND instead of mod-div 180 | // in the lookup (0x100 - 1 == 0x0ff, 0x1c3 & 0x0ff == 0x0c3.. the "remainder") 181 | gnuHash_.bloom_size_--; 182 | break; 183 | } 184 | 185 | if (is_complete()) { 186 | break; 187 | } 188 | } 189 | 190 | if (!is_complete()) { 191 | // Error, go to next library 192 | throw input_parse_error("not all info found"); 193 | } 194 | } 195 | 196 | #ifndef __LP64__ 197 | elfSharedLibData::elfSharedLibData(soinfo const* si) { 198 | pltRelocationsLen = si->plt_rel_count; 199 | pltRelocations = si->plt_rel; 200 | relocationsLen = si->rel_count; 201 | relocations = si->rel; 202 | dynSymbolsTable = si->symtab; 203 | dynStrsTable = si->strtab; 204 | 205 | elfHash_.numbuckets_ = si->nbucket; 206 | elfHash_.numchains_ = si->nchain; 207 | elfHash_.buckets_ = si->bucket; 208 | elfHash_.chains_ = si->chain; 209 | 210 | gnuHash_ = {}; 211 | 212 | if (facebook::build::Build::getAndroidSdk() >= 17) { 213 | loadBias = si->load_bias; 214 | } else { 215 | loadBias = si->base; 216 | } 217 | 218 | libName = si->name; 219 | } 220 | #endif 221 | 222 | ElfW(Sym) const* elfSharedLibData::find_symbol_by_name(char const* name) const { 223 | auto sym = usesGnuHashTable() ? gnu_find_symbol_by_name(name) 224 | : elf_find_symbol_by_name(name); 225 | 226 | // the GNU hash table doesn't include entries for any STN_UNDEF symbols in the object, 227 | // and although the ELF hash table "should" according to the spec contain entries for 228 | // every symbol, there may be some noncompliant binaries out in the world. 229 | 230 | // I *think* that if a symbol is STN_UNDEF, it's "extern"-linked and will thus have an 231 | // entry in either the DT_JMPREL[A] or DT_REL[A] sections. Not entirely sure though. 232 | 233 | // note the !sym check in each loop: don't perform this work if we've already found it. 234 | for (size_t i = 0; !sym && i < pltRelocationsLen; i++) { 235 | size_t sym_idx = ELF_RELOC_SYM(pltRelocations[i].r_info); 236 | if (strcmp(dynStrsTable + dynSymbolsTable[sym_idx].st_name, name) == 0) { 237 | sym = &dynSymbolsTable[sym_idx]; 238 | } 239 | } 240 | for (size_t i = 0; !sym && i < relocationsLen; i++) { 241 | size_t sym_idx = ELF_RELOC_SYM(relocations[i].r_info); 242 | if (strcmp(dynStrsTable + dynSymbolsTable[sym_idx].st_name, name) == 0) { 243 | sym = &dynSymbolsTable[sym_idx]; 244 | } 245 | } 246 | 247 | return sym; 248 | } 249 | 250 | ElfW(Sym) const* elfSharedLibData::elf_find_symbol_by_name(char const* name) const { 251 | uint32_t hash = elfhash(name); 252 | 253 | for (uint32_t n = elfHash_.buckets_[hash % elfHash_.numbuckets_]; n != 0; n = elfHash_.chains_[n]) { 254 | ElfW(Sym) const* s = dynSymbolsTable + n; // identical to &dynSymbolsTable[n] 255 | if (strcmp(dynStrsTable + s->st_name, name) == 0) { 256 | return s; 257 | } 258 | } 259 | 260 | return nullptr; 261 | } 262 | 263 | // the original AOSP code uses several binary-math optimizations that differ from the "standard" 264 | // gnu hash implementation, and have been left in place with explanatory comments to avoid diverging 265 | ElfW(Sym) const* elfSharedLibData::gnu_find_symbol_by_name(char const* name) const { 266 | uint32_t hash = gnuhash(name); 267 | uint32_t h2 = hash >> gnuHash_.bloom_shift_; 268 | 269 | // bloom_size_ has been decremented by 1 from its original value (which was guaranteed 270 | // to be a power of two), meaning that this is mathematically equivalent to modulus division: 271 | // 0x100 - 1 == 0x0ff, and 0x1c3 & 0x0ff = 0x0c3.. the "remainder" 272 | uint32_t word_num = (hash / kBloomMaskBits) & gnuHash_.bloom_size_; 273 | ElfW(Addr) bloom_word = gnuHash_.bloom_filter_[word_num]; 274 | 275 | // test against bloom filter 276 | if ((1 & (bloom_word >> (hash % kBloomMaskBits)) & (bloom_word >> (h2 % kBloomMaskBits))) == 0) { 277 | return nullptr; 278 | } 279 | 280 | // bloom test says "probably yes"... 281 | uint32_t n = gnuHash_.buckets_[hash % gnuHash_.numbuckets_]; 282 | 283 | if (n == 0) { 284 | return nullptr; 285 | } 286 | 287 | do { 288 | ElfW(Sym) const* s = dynSymbolsTable + n; // identical to &dynSymbolsTable[n] 289 | // this XOR is mathematically equivalent to (hash1 | 1) == (hash2 | 1), but faster 290 | // basically, test for equality while ignoring LSB 291 | if (((gnuHash_.chains_[n] ^ hash) >> 1) == 0 && 292 | strcmp(dynStrsTable + s->st_name, name) == 0) { 293 | return s; 294 | } 295 | } while ((gnuHash_.chains_[n++] & 1) == 0); // gnu hash chains use the LSB as an end-of-chain marker 296 | 297 | return nullptr; 298 | } 299 | 300 | std::vector elfSharedLibData::get_relocations(void *symbol) const { 301 | std::vector relocs; 302 | 303 | for (size_t i = 0; i < relocationsLen; i++) { 304 | auto const& relocation = relocations[i]; 305 | void** relocated = reinterpret_cast(loadBias + relocation.r_offset); 306 | if (*relocated == symbol) { 307 | relocs.push_back(relocated); 308 | } 309 | } 310 | 311 | return relocs; 312 | } 313 | 314 | std::vector elfSharedLibData::get_plt_relocations(ElfW(Sym) const* elf_sym) const { 315 | // TODO: merge this and get_relocations into one helper-based implementation 316 | std::vector relocs; 317 | 318 | for (unsigned int i = 0; i < pltRelocationsLen; i++) { 319 | Elf_Reloc const& rel = pltRelocations[i]; 320 | 321 | // is this necessary? will there ever be a type of relocation in pltRelocations 322 | // that points to symbol and we *don't* want to fix up? 323 | if (ELF_RELOC_TYPE(rel.r_info) != PLT_RELOCATION_TYPE) { 324 | continue; 325 | } 326 | 327 | if (&dynSymbolsTable[ELF_RELOC_SYM(rel.r_info)] == elf_sym) { 328 | // Found the address of the relocation 329 | void** plt_got_entry = reinterpret_cast(loadBias + rel.r_offset); 330 | relocs.push_back(plt_got_entry); 331 | } 332 | } 333 | 334 | return relocs; 335 | } 336 | 337 | bool elfSharedLibData::is_complete() const { 338 | return pltRelocationsLen && pltRelocations && 339 | // relocationsLen && relocations && TODO (t30088113): re-enable when DT_ANDROID_REL is supported 340 | dynSymbolsTable && dynStrsTable && 341 | (elfHash_.numbuckets_ > 0 || gnuHash_.numbuckets_ > 0); 342 | } 343 | 344 | /* It can happen that, after caching a shared object's data in sharedLibData, 345 | * the library is unloaded, so references to memory in that address space 346 | * result in SIGSEGVs. Thus, check here that the addresses are still valid. 347 | */ 348 | elfSharedLibData::operator bool() const { 349 | Dl_info info; 350 | 351 | if (!is_complete()) { 352 | return false; 353 | } 354 | 355 | // pltRelocations is somewhat special: the "bad" constructor explicitly sets 356 | // this to nullptr in order to mark the entire object as invalid. if this check 357 | // is removed, be sure to use some other form of sentinel. 358 | if (!pltRelocations || 359 | !dladdr(pltRelocations, &info) || 360 | strcmp(info.dli_fname, libName) != 0) { 361 | return false; 362 | } 363 | 364 | if (!dynSymbolsTable || 365 | !dladdr(dynSymbolsTable, &info) || 366 | strcmp(info.dli_fname, libName) != 0) { 367 | return false; 368 | } 369 | 370 | if (!dynStrsTable || 371 | !dladdr(dynStrsTable, &info) || 372 | strcmp(info.dli_fname, libName) != 0) { 373 | return false; 374 | } 375 | 376 | return true; 377 | } 378 | 379 | } } // namespace facebook::linker 380 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/elfSharedLibData.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include "bionic_linker.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace facebook { 26 | namespace linker { 27 | 28 | class input_parse_error : public virtual std::runtime_error { 29 | public: 30 | input_parse_error(std::string what) 31 | : std::runtime_error(what) {} 32 | }; 33 | 34 | // Android uses RELA for aarch64 and x86_64. mips64 still uses REL. 35 | #if defined(__aarch64__) || defined(__x86_64__) 36 | typedef ElfW(Rela) Elf_Reloc; 37 | #else 38 | typedef ElfW(Rel) Elf_Reloc; 39 | #endif 40 | 41 | class elfSharedLibData { 42 | public: 43 | 44 | elfSharedLibData(); 45 | // throws input_parse_error if it fails to parse all the info it needs from dlpi 46 | explicit elfSharedLibData(dl_phdr_info const* dlpi); 47 | #if !defined(__aarch64__) 48 | explicit elfSharedLibData(soinfo const* si); 49 | #endif 50 | 51 | /** 52 | * Returns a pointer to the ElfW(Sym) for the given symbol name. 53 | */ 54 | ElfW(Sym) const* find_symbol_by_name(char const* name) const; 55 | 56 | /** 57 | * Returns a vector of all fixed-up memory addresses that point to symbol 58 | */ 59 | std::vector get_relocations(void* symbol) const; 60 | 61 | /** 62 | * Returns a vector of all fixed-up PLT entries that point to the symbol represented 63 | * by elf_sym 64 | */ 65 | std::vector get_plt_relocations(ElfW(Sym) const* elf_sym) const; 66 | 67 | /** 68 | * Finds the actual in-memory address of the given symbol 69 | */ 70 | void* getLoadedAddress(ElfW(Sym) const* sym) const { 71 | return reinterpret_cast(loadBias + sym->st_value); 72 | }; 73 | 74 | /** 75 | * Returns whether or not we will use the GNU hash table instead of the ELF hash table 76 | */ 77 | bool usesGnuHashTable() const { 78 | return gnuHash_.numbuckets_ > 0; 79 | } 80 | 81 | /** 82 | * Checks validity of this structure, including whether or not its tables are still 83 | * valid in our virtual memory. 84 | */ 85 | operator bool() const; 86 | 87 | bool operator==(elfSharedLibData const& other) const { 88 | return loadBias == other.loadBias; 89 | } 90 | 91 | bool operator!=(elfSharedLibData const& other) const { 92 | return !(*this == other); 93 | } 94 | 95 | private: 96 | 97 | ElfW(Sym) const* elf_find_symbol_by_name(char const*) const; 98 | ElfW(Sym) const* gnu_find_symbol_by_name(char const*) const; 99 | std::vector get_relocations_internal(void*, Elf_Reloc const*, size_t) const; 100 | bool is_complete() const; 101 | 102 | uintptr_t loadBias {}; 103 | Elf_Reloc const* pltRelocations {}; 104 | size_t pltRelocationsLen {}; 105 | Elf_Reloc const* relocations {}; 106 | size_t relocationsLen {}; 107 | ElfW(Sym) const* dynSymbolsTable {}; 108 | char const* dynStrsTable {}; 109 | char const* libName {}; 110 | 111 | struct { 112 | uint32_t numbuckets_ {}; 113 | uint32_t numchains_ {}; 114 | uint32_t const* buckets_ {}; 115 | uint32_t const* chains_ {}; 116 | } elfHash_; 117 | 118 | struct { 119 | uint32_t numbuckets_ {}; 120 | uint32_t symoffset_ {}; 121 | uint32_t bloom_size_ {}; 122 | uint32_t bloom_shift_ {}; 123 | ElfW(Addr) const* bloom_filter_ {}; 124 | uint32_t const* buckets_ {}; 125 | uint32_t const* chains_ {}; 126 | } gnuHash_; 127 | 128 | static constexpr uint32_t kBloomMaskBits = sizeof(ElfW(Addr)) * 8; 129 | }; 130 | 131 | } } // namespace facebook::linker 132 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/hooks.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "hooks.h" 18 | #include "locks.h" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace facebook { 27 | namespace linker { 28 | namespace hooks { 29 | 30 | namespace { 31 | 32 | struct InternalHookInfo { 33 | HookId id; 34 | uintptr_t got_address; 35 | std::vector hooks; 36 | 37 | pthread_rwlock_t mutex; 38 | InternalHookInfo() 39 | : id(), got_address(), hooks(), mutex(PTHREAD_RWLOCK_INITIALIZER) {} 40 | }; 41 | 42 | struct Globals { 43 | // These are std::map instead of unordered_map because GOT addresses 44 | // are not sufficiently random for a hash map. 45 | std::map> hooks_by_id; 46 | std::map> hooks_by_got_address; 47 | pthread_rwlock_t map_mutex; 48 | 49 | std::atomic next_id; 50 | 51 | Globals() 52 | : hooks_by_id(), 53 | hooks_by_got_address(), 54 | map_mutex(PTHREAD_RWLOCK_INITIALIZER), 55 | next_id(1) {} 56 | }; 57 | 58 | Globals& getGlobals() { 59 | static Globals globals{}; 60 | return globals; 61 | } 62 | 63 | } // namespace 64 | 65 | inline HookId allocate_id() { 66 | return getGlobals().next_id.fetch_add(1); 67 | } 68 | 69 | bool is_hooked(uintptr_t got_address) { 70 | auto& globals = getGlobals(); 71 | auto& map = globals.hooks_by_got_address; 72 | ReaderLock lock(&globals.map_mutex); 73 | return map.find(got_address) != map.end(); 74 | } 75 | 76 | ssize_t list_size(HookId id) { 77 | auto& globals = getGlobals(); 78 | auto& map = globals.hooks_by_id; 79 | ReaderLock map_lock(&globals.map_mutex); 80 | auto it = map.find(id); 81 | if (it == map.end()) { 82 | // Hook not registered 83 | return -1; 84 | } 85 | 86 | auto& info = *it->second; 87 | ReaderLock info_lock(&info.mutex); 88 | return info.hooks.size(); 89 | } 90 | 91 | std::vector get_run_list(HookId id) { 92 | auto& globals = getGlobals(); 93 | auto& map = globals.hooks_by_id; 94 | ReaderLock map_lock(&globals.map_mutex); 95 | auto it = map.find(id); 96 | if (it == map.end()) { 97 | // Hook not registered 98 | return {}; 99 | } 100 | 101 | auto& info = *it->second; 102 | ReaderLock info_lock(&info.mutex); 103 | 104 | return info.hooks; 105 | } 106 | 107 | HookResult add(HookInfo& info) { 108 | auto& globals = getGlobals(); 109 | if (info.previous_function == nullptr || info.new_function == nullptr || 110 | info.got_address == 0) { 111 | return WRONG_HOOK_INFO; 112 | } 113 | 114 | auto& got_map = globals.hooks_by_got_address; 115 | 116 | // First try just taking the reader lock, in case we already have an entry. 117 | { 118 | ReaderLock map_lock(&globals.map_mutex); 119 | auto it = got_map.find(info.got_address); 120 | if (it != got_map.end()) { 121 | // Success, we already have an entry for this GOT address. 122 | auto& internal_info = *it->second; 123 | WriterLock info_lock(&internal_info.mutex); 124 | internal_info.hooks.emplace_back(info.new_function); 125 | return ALREADY_HOOKED_APPENDED; 126 | } 127 | } 128 | // Okay, we need to do this from scratch. 129 | 130 | auto internal_info = std::make_shared(); 131 | // No one else can have internal_info so no point taking the writer lock. 132 | 133 | internal_info->id = allocate_id(); 134 | internal_info->got_address = info.got_address; 135 | internal_info->hooks.emplace_back(info.previous_function); 136 | internal_info->hooks.emplace_back(info.new_function); 137 | 138 | WriterLock map_lock(&globals.map_mutex); 139 | globals.hooks_by_got_address.emplace( 140 | internal_info->got_address, internal_info); 141 | globals.hooks_by_id.emplace(internal_info->id, internal_info); 142 | 143 | info.out_id = internal_info->id; 144 | return NEW_HOOK; 145 | } 146 | 147 | HookResult remove(HookInfo& info) { 148 | auto& globals = getGlobals(); 149 | if (info.new_function == nullptr || info.got_address == 0) { 150 | return WRONG_HOOK_INFO; 151 | } 152 | 153 | WriterLock map_lock(&globals.map_mutex); 154 | auto& got_map = globals.hooks_by_got_address; 155 | auto it = got_map.find(info.got_address); 156 | if (it == got_map.end()) { 157 | return WRONG_HOOK_INFO; 158 | } 159 | // Success, we already have an entry for this GOT address. 160 | // Take a reference to the shared_ptr to keep the data around 161 | // as we go about clearing the indices. 162 | auto internal_info = it->second; 163 | WriterLock info_lock(&internal_info->mutex); 164 | 165 | if (internal_info->hooks.size() == 1) { 166 | // Double-check the case where there's only one item left. 167 | if (internal_info->hooks.at(0) != info.new_function) { 168 | return WRONG_HOOK_INFO; 169 | } 170 | // Okay, we have one item and we want to remove it, let's 171 | // clear all the data structures. 172 | globals.hooks_by_got_address.erase(it); 173 | 174 | // We cannot remove this hook from hooks_by_id because another thread 175 | // may be racing with us and entering the trampoline, so it would need 176 | // to be able to look things up by id. 177 | 178 | return REMOVED_FULLY; 179 | } 180 | 181 | auto hooks_it = std::find( 182 | internal_info->hooks.begin(), 183 | internal_info->hooks.end(), 184 | info.new_function); 185 | if (hooks_it == internal_info->hooks.end()) { 186 | return WRONG_HOOK_INFO; 187 | } 188 | 189 | if (hooks_it == internal_info->hooks.begin()) { 190 | // Can't remove the original function if you have hooks after it. 191 | return WRONG_HOOK_INFO; 192 | } 193 | 194 | internal_info->hooks.erase(hooks_it); 195 | auto previous_function = internal_info->hooks.at(0); // original function 196 | info.previous_function = previous_function; 197 | 198 | return internal_info->hooks.size() == 1 ? REMOVED_TRIVIAL 199 | : REMOVED_STILL_HOOKED; 200 | } 201 | 202 | } // namespace hooks 203 | } // namespace linker 204 | } // namespace facebook 205 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/hooks.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using HookId = uint32_t; 24 | 25 | namespace facebook { 26 | namespace linker { 27 | namespace hooks { 28 | 29 | struct HookInfo { 30 | HookId out_id; 31 | uintptr_t got_address; 32 | void* new_function; 33 | void* previous_function; 34 | }; 35 | 36 | enum HookResult { 37 | WRONG_HOOK_INFO = -1, 38 | NEW_HOOK = 1, 39 | ALREADY_HOOKED_APPENDED = 2, 40 | REMOVED_STILL_HOOKED = 3, 41 | REMOVED_TRIVIAL = 4, // only one item left in the run list 42 | REMOVED_FULLY = 5, 43 | }; 44 | 45 | HookResult add(HookInfo&); 46 | 47 | /** 48 | * Only uses new_function and got_address from HookInfo. 49 | * This call fills previous_function with the *original* value of the slot. 50 | * 51 | * If the run list only contains one function and this calls removes it, 52 | * all knowledge of the hook is erased. 53 | * 54 | * Returns REMOVED_STILL_HOOKED if the run list contains more than one item. 55 | * Returns REMOVED_TRIVIAL if only one item is left in the run list. 56 | * Returns REMOVED_FULLY if all information about this hook has been removed. 57 | * Returns WRONG_HOOK_INFO otherwise. 58 | */ 59 | HookResult remove(HookInfo&); 60 | 61 | bool is_hooked(uintptr_t got_address); 62 | 63 | ssize_t list_size(HookId); 64 | 65 | std::vector get_run_list(HookId id); 66 | 67 | } // namespace hooks 68 | } // namespace linker 69 | } // namespace facebook 70 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/link.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "link.h" 18 | #include "sharedlibs.h" 19 | #include "log_assert.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace facebook::linker; 27 | 28 | #ifdef __ANDROID__ 29 | 30 | extern "C" { 31 | 32 | int dladdr1(void* addr, Dl_info* info, void** extra_info, int flags) { 33 | if (flags != RTLD_DL_SYMENT) { 34 | errno = ENOSYS; 35 | return 0; 36 | } 37 | 38 | if (!dladdr(addr, info)) { 39 | return 0; // docs specify dlerror not set in this case, which makes it easy for us 40 | } 41 | 42 | elfSharedLibData lib; 43 | try { 44 | lib = sharedLib(basename(info->dli_fname)); 45 | } catch (std::out_of_range& e) { 46 | return 0; 47 | } 48 | 49 | auto sym_info = const_cast(reinterpret_cast(extra_info)); 50 | *sym_info = lib.find_symbol_by_name(info->dli_sname); 51 | if (*sym_info) { 52 | if (lib.getLoadedAddress(*sym_info) != info->dli_saddr) { 53 | std::stringstream ss; 54 | ss << "tried to resolve address 0x" << std::hex << addr << " "; 55 | ss << "but dladdr returned \"" << info->dli_sname << "\" (0x" << info->dli_saddr << ") "; 56 | ss << "while find_symbol_by_name returned " << (*sym_info)->st_value; 57 | log_assert(ss.str().c_str()); 58 | } 59 | return 1; 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | } // extern "C" 66 | 67 | #endif // __ANDROID__ 68 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/link.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include 20 | 21 | #if defined(__BIONIC__) 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | enum { 28 | /* Matching symbol table entry (const ElfNN_Sym *). */ 29 | RTLD_DL_SYMENT = 1, 30 | 31 | /* The object containing the address (struct link_map *). */ 32 | RTLD_DL_LINKMAP = 2 33 | }; 34 | 35 | /** 36 | * Implementation of glibc's dladdr1. 37 | * 38 | * addr - Address to look up 39 | * info - Pointer to Dl_info struct to fill in 40 | * extra_info - Pointer to ElfW(Sym) const* that will be filled in 41 | * flags - only RTLD_DL_SYMENT supported 42 | * 43 | * Returns 1 on success, 0 on failure. dlerror will not be set. 44 | */ 45 | int dladdr1(void* addr, Dl_info* info, void** extra_info, int flags); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif // defined(__BIONIC__) 52 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/linker.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "link.h" 18 | #include "linker.h" 19 | #include "sharedlibs.h" 20 | #include "hooks.h" 21 | #include "locks.h" 22 | #include "log_assert.h" 23 | #include "trampoline.h" 24 | 25 | #include "abort_with_reason.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "sig_safe_write.h" 43 | #include "sigmux.h" 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | #define PAGE_ALIGN(ptr, pagesize) (void*) (((uintptr_t) (ptr)) & ~((pagesize) - 1)) 57 | 58 | using namespace facebook::linker; 59 | 60 | typedef void* prev_func; 61 | typedef void* lib_base; 62 | 63 | namespace { 64 | 65 | static std::atomic g_linker_enabled(true); 66 | 67 | // Global lock on any GOT slot modification. 68 | static pthread_rwlock_t g_got_modification_lock = PTHREAD_RWLOCK_INITIALIZER; 69 | 70 | } 71 | 72 | void 73 | linker_set_enabled(int enabled) 74 | { 75 | g_linker_enabled = enabled; 76 | } 77 | 78 | int 79 | linker_initialize() 80 | { 81 | if (!g_linker_enabled.load()) { 82 | return 1; 83 | } 84 | if (sigmux_init(SIGSEGV) || 85 | sigmux_init(SIGBUS)) 86 | { 87 | return 1; 88 | } 89 | 90 | return refresh_shared_libs(); 91 | } 92 | 93 | int 94 | get_relocations(symbol sym, reloc* relocs_out, size_t relocs_out_len) { 95 | Dl_info info; 96 | if (!dladdr(sym, &info)) { 97 | errno = ENOENT; 98 | return -1; 99 | } 100 | 101 | try { 102 | auto lib = sharedLib(info.dli_sname); 103 | auto relocs = lib.get_relocations(sym); 104 | 105 | if (relocs.size() > relocs_out_len) { 106 | errno = ERANGE; 107 | return -1; 108 | } 109 | 110 | memcpy(relocs_out, relocs.data(), relocs.size() * sizeof(reloc)); 111 | return relocs.size(); 112 | } catch (std::out_of_range& e) { 113 | errno = ENODATA; 114 | return -1; 115 | } 116 | } 117 | 118 | int unsafe_patch_relocation_address( 119 | prev_func* plt_got_entry, 120 | hook_func new_value) { 121 | try { 122 | int rc = 123 | sig_safe_write(plt_got_entry, reinterpret_cast(new_value)); 124 | 125 | if (rc && errno == EFAULT) { 126 | // if we need to mprotect, it must be done under lock - don't want to 127 | // set +w, then have somebody else finish and set -w, before we're done 128 | // with our write 129 | static pthread_rwlock_t mprotect_mutex_ = PTHREAD_RWLOCK_INITIALIZER; 130 | WriterLock wl(&mprotect_mutex_); 131 | 132 | int pagesize = getpagesize(); 133 | void* page = PAGE_ALIGN(plt_got_entry, pagesize); 134 | 135 | if (mprotect(page, pagesize, PROT_READ | PROT_WRITE)) { 136 | return 5; 137 | } 138 | 139 | rc = sig_safe_write(plt_got_entry, reinterpret_cast(new_value)); 140 | 141 | int old_errno = errno; 142 | if (mprotect(page, pagesize, PROT_READ)) { 143 | abort(); 144 | }; 145 | errno = old_errno; 146 | } 147 | 148 | return rc; 149 | } catch (std::runtime_error const&) { 150 | return 6; 151 | } 152 | } 153 | 154 | int 155 | patch_relocation_address_for_hook(prev_func* plt_got_entry, plt_hook_spec* spec) { 156 | auto got_addr = reinterpret_cast(plt_got_entry); 157 | 158 | // Take the pessimistic writer lock. This enforces a global serial order 159 | // on GOT slot modifications but makes the code much easier to reason about. 160 | // For slots that we've already hooked, this is overkill but is easier 161 | // than tracking modification conflicts. 162 | 163 | WriterLock lock(&g_got_modification_lock); 164 | if (hooks::is_hooked(got_addr)) { 165 | // No point in safety checks if we've already once hooked this GOT slot. 166 | hooks::HookInfo info { 167 | .out_id = 0, 168 | .got_address = got_addr, 169 | .new_function = spec->hook_fn, 170 | .previous_function = *plt_got_entry, 171 | }; 172 | auto ret = hooks::add(info); 173 | if (ret != hooks::ALREADY_HOOKED_APPENDED) { 174 | return 1; 175 | } else { 176 | return 0; 177 | } 178 | } 179 | 180 | // We haven't hooked this slot yet. We also need to make a trampoline. 181 | hooks::HookInfo hook_info { 182 | .out_id = 0, 183 | .got_address = got_addr, 184 | .new_function = spec->hook_fn, 185 | .previous_function = *plt_got_entry, 186 | }; 187 | auto ret = hooks::add(hook_info); 188 | if (ret != hooks::NEW_HOOK) { 189 | return 1; 190 | } 191 | hook_func trampoline = create_trampoline(hook_info.out_id); 192 | 193 | return unsafe_patch_relocation_address(plt_got_entry, trampoline); 194 | } 195 | 196 | static bool 197 | verify_got_entry_for_spec(prev_func* got_addr, plt_hook_spec* spec) { 198 | if (hooks::is_hooked(reinterpret_cast(got_addr))) { 199 | // We've done this already, stop checking. 200 | return true; 201 | } 202 | 203 | Dl_info info; 204 | if (!dladdr(got_addr, &info)) { 205 | // LOGE("GOT entry not part of a DSO: %p", got_addr); 206 | return false; 207 | } 208 | if (!dladdr(*got_addr, &info)) { 209 | // LOGE("GOT entry does not point to a DSO: %p", *got_addr); 210 | return false; 211 | } 212 | if (strcmp(info.dli_sname, spec->fn_name)) { 213 | // LOGE( 214 | // "GOT entry does not point to symbol we need: %s vs %s", 215 | // info.dli_sname, 216 | // spec->fn_name); 217 | return false; 218 | } 219 | return true; 220 | } 221 | 222 | int hook_plt_method(const char* libname, const char* name, hook_func hook) { 223 | plt_hook_spec spec(name, hook); 224 | auto ret = hook_single_lib(libname, &spec, 1); 225 | if (ret == 0 && spec.hook_result == 1) { 226 | return 0; 227 | } 228 | return 1; 229 | } 230 | 231 | int unhook_plt_method(const char* libname, const char* name, hook_func hook) { 232 | plt_hook_spec spec(name, hook); 233 | if (unhook_single_lib(libname, &spec, 1) == 0 && spec.hook_result == 1) { 234 | return 0; 235 | } 236 | return 1; 237 | } 238 | 239 | int hook_single_lib(char const* libname, plt_hook_spec* specs, size_t num_specs) { 240 | int failures = 0; 241 | 242 | try { 243 | auto elfData = sharedLib(libname); 244 | for (unsigned int specCnt = 0; specCnt < num_specs; ++specCnt) { 245 | plt_hook_spec* spec = &specs[specCnt]; 246 | 247 | if (!spec->hook_fn || !spec->fn_name) { 248 | // Invalid spec. 249 | failures++; 250 | continue; 251 | } 252 | 253 | ElfW(Sym) const* sym = elfData.find_symbol_by_name(spec->fn_name); 254 | if (!sym) { 255 | continue; // Did not find symbol in the hash table, so go to next spec 256 | } 257 | 258 | for (prev_func* plt_got_entry : elfData.get_plt_relocations(sym)) { 259 | 260 | // Run sanity checks on what we parsed as the GOT slot. 261 | if (!verify_got_entry_for_spec(plt_got_entry, spec)) { 262 | failures++; 263 | continue; 264 | } 265 | 266 | if (patch_relocation_address_for_hook(plt_got_entry, spec) == 0) { 267 | spec->hook_result++; 268 | } else { 269 | failures++; 270 | } 271 | } 272 | } 273 | } catch (std::out_of_range& e) { /* no op */ } 274 | 275 | return failures; 276 | } 277 | 278 | int unhook_single_lib( 279 | char const* libname, 280 | plt_hook_spec* specs, 281 | size_t num_specs) { 282 | int failures = 0; 283 | 284 | try { 285 | auto elfData = sharedLib(libname); 286 | 287 | // Take the GOT lock to prevent other threads from modifying our state. 288 | WriterLock lock(&g_got_modification_lock); 289 | 290 | for (unsigned int specCnt = 0; specCnt < num_specs; ++specCnt) { 291 | plt_hook_spec& spec = specs[specCnt]; 292 | 293 | ElfW(Sym) const* sym = elfData.find_symbol_by_name(spec.fn_name); 294 | if (!sym) { 295 | continue; // Did not find symbol in the hash table, so go to next spec 296 | } 297 | for (prev_func* plt_got_entry : elfData.get_plt_relocations(sym)) { 298 | auto addr = reinterpret_cast(plt_got_entry); 299 | if (!hooks::is_hooked(addr)) { 300 | continue; 301 | } 302 | // Remove the entry for this GOT address and this particular hook. 303 | hooks::HookInfo info{ 304 | .got_address = addr, 305 | .new_function = spec.hook_fn, 306 | }; 307 | auto result = hooks::remove(info); 308 | if (result == hooks::REMOVED_STILL_HOOKED) { 309 | // There are other hooks at this slot, continue. 310 | spec.hook_result++; 311 | continue; 312 | } else if (result == hooks::REMOVED_TRIVIAL) { 313 | // Only one entry left at this slot, patch the original function 314 | // to lower the overhead. 315 | auto original = info.previous_function; 316 | if (unsafe_patch_relocation_address(plt_got_entry, original) != 0) { 317 | abortWithReason("Unable to unhook GOT slot"); 318 | } 319 | // Restored the GOT slot, let's remove all knowledge about this 320 | // hook. 321 | hooks::HookInfo original_info{ 322 | .got_address = reinterpret_cast(plt_got_entry), 323 | .new_function = original, 324 | }; 325 | if (hooks::remove(original_info) != hooks::REMOVED_FULLY) { 326 | abortWithReason("GOT slot modified while we were working on it"); 327 | } 328 | spec.hook_result++; 329 | continue; 330 | } else { 331 | failures++; 332 | } 333 | } 334 | } 335 | } catch (std::out_of_range& e) { /* no op */ 336 | } 337 | 338 | return failures; 339 | } 340 | 341 | int hook_all_libs( 342 | plt_hook_spec* specs, 343 | size_t num_specs, 344 | bool (*allowHookingLib)(char const* libname, void* data), 345 | void* data) { 346 | if (refresh_shared_libs()) { 347 | // Could not properly refresh the cache of shared library data 348 | return -1; 349 | } 350 | 351 | int failures = 0; 352 | 353 | for (auto const& lib : allSharedLibs()) { 354 | if (allowHookingLib(lib.first.c_str(), data)) { 355 | failures += hook_single_lib(lib.first.c_str(), specs, num_specs); 356 | } 357 | } 358 | 359 | return failures; 360 | } 361 | 362 | int unhook_all_libs( 363 | plt_hook_spec* specs, 364 | size_t num_specs) { 365 | int failures = 0; 366 | 367 | for (auto const& lib : allSharedLibs()) { 368 | failures += unhook_single_lib(lib.first.c_str(), specs, num_specs); 369 | } 370 | 371 | return failures; 372 | } 373 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/linker.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | /** 30 | * Helper typedefs for conceptual separation in return values and parameters. 31 | */ 32 | typedef void* symbol; 33 | typedef void* hook_func; 34 | typedef void** reloc; 35 | 36 | /** 37 | * Guards initialization from above. Can be used to no-op calls to 38 | * linker_initialize by calling this with `false`. 39 | */ 40 | void 41 | linker_set_enabled(int enabled); 42 | 43 | /** 44 | * Initializes the library, call to this function is expected before any other 45 | * function is invoked. 46 | * This is additionally guarded by linker_set_enabled() above. 47 | * 48 | * On failure returns non-zero value and errno is set appropriately. 49 | */ 50 | int 51 | linker_initialize(); 52 | 53 | /** 54 | * Populates the global cache of shared library data which is used to install 55 | * hooks. Returns 0 on success, nonzero on failure. 56 | */ 57 | int 58 | refresh_shared_libs(); 59 | 60 | /** 61 | * Finds all linker relocations that point to the resolved symbol. relocs_out is an 62 | * array that gets filled with pointers to memory locations that point to sym. 63 | * 64 | * Returns the number of relocations written to relocs_out, or -1 on error (errno 65 | * will be set). 66 | * 67 | * Note: These are different than PLT relocations, and aren't used for PLT hooking. 68 | */ 69 | int 70 | get_relocations(symbol sym, reloc* relocs_out, size_t relocs_out_len); 71 | 72 | /** 73 | * Overwrites GOT entry for particular function with provided address, 74 | * effectively hijacking all invocations of given function in given library. 75 | * 76 | * Returns 0 on success, nonzero on failure. 77 | */ 78 | int 79 | hook_plt_method(const char* libname, const char* name, hook_func hook); 80 | 81 | typedef struct _plt_hook_spec { 82 | const char* fn_name; 83 | hook_func hook_fn; 84 | int hook_result; 85 | 86 | #if defined(__cplusplus) 87 | _plt_hook_spec(const char* fname, hook_func hfn) 88 | : fn_name(fname), hook_fn(hfn), hook_result(0) 89 | {} 90 | _plt_hook_spec(void*, const char* fname, hook_func hfn) 91 | : _plt_hook_spec(fname, hfn) {} 92 | #endif //defined(__cplusplus) 93 | } plt_hook_spec; 94 | 95 | /** 96 | * Overwrites GOT entry for specified functions with provided addresses, 97 | * effectively hijacking all invocations of given function in given library. 98 | * 99 | * Returns the number of failures that occurred during hooking (0 for total success), 100 | * and increments plt_hook_spec::hook_result for each hook that succeeds. Note that 101 | * it is possible to have some, but not all, hooks fail. (Not finding a PLT entry in 102 | * a library is *not* counted as a failure.) 103 | */ 104 | int 105 | hook_single_lib(char const* libname, plt_hook_spec* specs, size_t num_specs); 106 | 107 | /** 108 | * Overwrites GOT entries for specified functions with provided addresses, 109 | * effectively hijacking all invocations of given function in given library. 110 | * 111 | * Returns the number of failures that occurred during hooking (0 for total success), 112 | * and increments plt_hook_spec::hook_result for each hook that succeeds. Note that 113 | * it is possible to have some, but not all, hooks fail. (Not finding a PLT entry in 114 | * a library is *not* counted as a failure.) 115 | */ 116 | int 117 | hook_all_libs(plt_hook_spec* specs, size_t num_specs, 118 | bool (*allowHookingLib)(char const* libname, void* data), void* data); 119 | 120 | // Counterparts to the all hook_* routines: 121 | 122 | int 123 | unhook_plt_method(const char* libname, const char* name, hook_func hook); 124 | 125 | int 126 | unhook_single_lib(char const* libname, plt_hook_spec* specs, size_t num_specs); 127 | 128 | int 129 | unhook_all_libs(plt_hook_spec* specs, size_t num_specs); 130 | 131 | /** 132 | * Calls the original (or at least, previous) method pointed to by the PLT. 133 | * Looks up PLT entries by hook *and* by library, since each library has its 134 | * own PLT and thus could have different entries. 135 | * 136 | * Takes as the first parameter the hook function itself, and then the args 137 | * as normal of the function. Returns the same type as the hooked function. 138 | * 139 | * If C99, a second parameter is required before the args describing the function 140 | * pointer type. 141 | * 142 | * Example: 143 | * int write_hook(int fd, const void* buf, size_t count) { 144 | * // do_some_hooky_stuff 145 | * 146 | * ret = CALL_PREV(write_hook, fd, buf, count); // if C++ 147 | * 148 | * ret = CALL_PREV(write_hook, // if C99 149 | * int(*)(int, void const*, size_t), 150 | * fd, buf, count); 151 | * } 152 | * 153 | * Aborts loudly if unable to find the previous function. 154 | */ 155 | #if defined(__cplusplus) 156 | #define CALL_PREV(hook, ...) __CALL_PREV_IMPL(hook, decltype(&hook), __VA_ARGS__) 157 | #else 158 | #define CALL_PREV(hook, hook_sig, ...) __CALL_PREV_IMPL(hook, hook_sig, __VA_ARGS__) 159 | #endif 160 | 161 | #define __CALL_PREV_IMPL(hook, hook_sig, ...) \ 162 | ({ \ 163 | void* _prev = get_previous_from_hook((void*) hook); \ 164 | ((hook_sig)_prev)(__VA_ARGS__); \ 165 | }) 166 | /** 167 | * Looks up the previous PLT entry for a given hook and library. Here be 168 | * dragons; you probably want CALL_PREV(..) instead. 169 | * 170 | * Returns: void* - code address of the function previously pointed to by the 171 | * appropriate entry of the appropriate PLT 172 | */ 173 | void* 174 | get_previous_from_hook(void* hook); 175 | 176 | #ifdef __cplusplus 177 | } // extern "C" 178 | #endif 179 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/locks.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include "log_assert.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | class ReaderLock { 28 | private: 29 | pthread_rwlock_t* const mutex_; 30 | 31 | public: 32 | inline ReaderLock(pthread_rwlock_t* mutex) 33 | : mutex_(mutex) { 34 | auto ret = pthread_rwlock_rdlock(mutex); 35 | if (ret != 0) { 36 | std::stringstream ss; 37 | ss << "pthread_rwlock_rdlock returned " << strerror(ret); 38 | log_assert(ss.str().c_str()); 39 | } 40 | } 41 | 42 | inline ~ReaderLock() { 43 | auto ret = pthread_rwlock_unlock(mutex_); 44 | if (ret != 0) { 45 | std::stringstream ss; 46 | ss << "pthread_rwlock_unlock returned " << strerror(ret); 47 | log_assert(ss.str().c_str()); 48 | } 49 | } 50 | }; 51 | 52 | class WriterLock { 53 | private: 54 | pthread_rwlock_t *mutex_; 55 | 56 | public: 57 | inline WriterLock(pthread_rwlock_t *mutex) 58 | : mutex_(mutex) { 59 | int ret = pthread_rwlock_wrlock(mutex); 60 | if (ret != 0) { 61 | std::stringstream ss; 62 | ss << "pthread_rwlock_wrlock returned " << strerror(ret); 63 | log_assert(ss.str().c_str()); 64 | } 65 | } 66 | 67 | inline ~WriterLock() { 68 | int ret = pthread_rwlock_unlock(mutex_); 69 | if (ret != 0) { 70 | std::stringstream ss; 71 | ss << "pthread_rwlock_unlock returned " << strerror(ret); 72 | log_assert(ss.str().c_str()); 73 | } 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/log_assert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #ifdef __ANDROID__ 20 | 21 | #include 22 | #include 23 | 24 | #ifdef LOG_TAG 25 | #define LINKER_ASSERT_LOG_TAG LOG_TAG 26 | #else 27 | #define LINKER_ASSERT_LOG_TAG "linkerlib" 28 | #endif 29 | 30 | __attribute__ ((noreturn)) 31 | static void log_assert(char const* msg) { 32 | __android_log_assert("", LINKER_ASSERT_LOG_TAG, "%s", msg); 33 | abort(); // just for good measure 34 | } 35 | 36 | #else 37 | 38 | #include 39 | #include 40 | 41 | __attribute__ ((noreturn)) 42 | static void log_assert(char const* msg) { 43 | fputs("Assertion Failure: ", stderr); 44 | fputs(msg, stderr); 45 | fputc('\n', stderr); 46 | abort(); 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/phaser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-present, Facebook, Inc. 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 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "phaser.h" 28 | 29 | #if PHASER_STRATEGY == STRATEGY_FUTEX 30 | #include 31 | #endif 32 | 33 | // 34 | // Fundamentally, Phaser is an RCU facility. A basic understanding of 35 | // Linux kernel RCU is helpful, but not necessary, for the discussion 36 | // below. What RCU calls "read-side critical sections", we just call 37 | // "critical sections". 38 | // 39 | // Phaser allows arbitrary threads to enter "critical sections" (to 40 | // borrow RCU terminology) using phaser_enter and exit them with 41 | // phaser_exit. The purpose of phaser_drain is to wait for the 42 | // termination of all critical sections that were active at the 43 | // instant phaser_drain began executing. 44 | // 45 | // Entry and exit from critical sections needs to be fast, 46 | // non-blocking, and completely reentrant. Note that we need to be 47 | // able to enter a critical section from *inside* phaser_drain, as 48 | // arbitrary signals can arrive during phaser_drain calls. 49 | // 50 | // We optimize for critical sections, since they're much more common 51 | // than phaser_drain calls. In the common case, phaser_enter is an 52 | // atomic increment and phaser_exit is an atomic decrement. It's only 53 | // when phaser_drain is running that we need something more complex. 54 | // 55 | // Normally, phaser_enter just increments one of the counter values, 56 | // but if it finds the counter's high bit (the DRAINING bit) set, we 57 | // try incrementing another counter instead. phaser_drain guarantees 58 | // that at most one counter has a DRAINING bit set, so phaser_enter 59 | // will always be able to find a counter to increment. 60 | // 61 | // phaser_exit decrements the counter phaser_enter incremented. If 62 | // the DRAINING bit is set on the counter after increment and the 63 | // counter has reached COUNT_DRAINED (which is (DRAINING | 0)), make a 64 | // FUTEX_WAKE system call so that phaser_drain knows it's safe to 65 | // continue. 66 | // 67 | // phaser_drain itself: at the start, from the perspective of the 68 | // thread running phaser_drain, we don't know anything about the 69 | // values of our counters, except that none of them has the DRAINING 70 | // bit set. We walk through all our counters and drain them by 71 | // setting the DRAINING bit and FUTEX_WAITing for them to go to 72 | // COUNT_DRAINED. We make sure to drain only one counter at a time, 73 | // so phaser_enter will always be able to make progress; it just might 74 | // have to settle for its second choice of counter. 75 | // 76 | // Architecture notes: on ARM, we only have single-word CAS available, 77 | // so any solution to the problem that involves CAS on double-word 78 | // values won't work. We try to distribute cacheline updates across 79 | // different cache lines; doing that improves benchmarks by 25% or so. 80 | // 81 | // An earlier version of this code distributed the counts across cache 82 | // lines and was able to speed up a four-thread benchmark by 50%. A 83 | // factor of two speedup wasn't worth the significant additional 84 | // complexity. 85 | // 86 | // (If the implementation seems trivial, that's because it is: 87 | // most of the work is proving that the trivial implementation 88 | // is correct. 89 | // 90 | 91 | #define ARRAY_SIZE(x) (sizeof ((x)) / sizeof ((x)[0])) 92 | 93 | #define SIZE_T_HIGH_BIT (SIZE_MAX - (SIZE_MAX >> 1)) 94 | static const size_t DRAINING = SIZE_T_HIGH_BIT; 95 | static const size_t COUNT_DRAINED = SIZE_T_HIGH_BIT | 0; 96 | 97 | #define likely(x) __builtin_expect((x),1) 98 | #define unlikely(x) __builtin_expect((x),0) 99 | 100 | #if PHASER_STRATEGY == STRATEGY_FUTEX 101 | static int 102 | phaser_futex(void* uaddr, int op, int val, 103 | const struct timespec* timeout, 104 | int* uaddr2, 105 | int val3) 106 | { 107 | return syscall(__NR_futex, uaddr, op, val, timeout, uaddr2, val3); 108 | } 109 | #endif 110 | 111 | int 112 | phaser_init(phaser_t* ph) 113 | { 114 | memset(ph->counter, 0, sizeof (ph->counter)); 115 | #if PHASER_STRATEGY == STRATEGY_PIPE 116 | int success = pipe(ph->counter_pipe); 117 | if (success != 0) { 118 | return -1; 119 | } 120 | #endif 121 | return 0; 122 | } 123 | 124 | void 125 | phaser_destroy(phaser_t* ph) 126 | { 127 | #if PHASER_STRATEGY == STRATEGY_PIPE 128 | close(ph->counter_pipe[0]); 129 | close(ph->counter_pipe[1]); 130 | #endif 131 | } 132 | 133 | static bool 134 | phaser_counter_try_inc(size_t* counter) 135 | { 136 | // N.B. It's very important to try reading the value and test for 137 | // the DRAINING bit _before_ trying to increment the value. If we 138 | // don't, we live-lock once a sufficient number of threads pound on 139 | // a DRAINING counter and never let it actually reach COUNT_DRAINED. 140 | 141 | if (unlikely(__atomic_load_n(counter, __ATOMIC_RELAXED) & DRAINING)) { 142 | return false; 143 | } 144 | 145 | // Use increment, not CAS: XADD is much faster than CMPXCHG on x86, 146 | // and on ARM it doesn't make a difference. We can tolerate the 147 | // race, as explained below. 148 | 149 | __atomic_add_fetch(counter, 1, __ATOMIC_RELAXED); 150 | 151 | // No need to check incremented value. Yes, phaser_drain races 152 | // against phaser_enter's check of the DRAINING bit, but we 153 | // consciously check DRAINING _before_ atomically incrementing the 154 | // counter, knowing that phaser_drain might set DRAINING between the 155 | // time of check and time of increment. That's okay: we'll go on to 156 | // decrement the counter, and this race can happen only a small 157 | // number of times. 158 | 159 | return true; 160 | } 161 | 162 | phaser_phase 163 | phaser_enter(phaser_t* ph) 164 | { 165 | phaser_phase nr_phases = ARRAY_SIZE(ph->counter); 166 | phaser_phase phase = 0; 167 | 168 | while (!phaser_counter_try_inc(&ph->counter[phase])) { 169 | phase = (phase + 1) & (nr_phases - 1); 170 | } 171 | 172 | __atomic_thread_fence(__ATOMIC_SEQ_CST); 173 | return phase; 174 | } 175 | 176 | void 177 | phaser_exit(phaser_t* ph, phaser_phase phase) 178 | { 179 | size_t* counter; 180 | size_t value; 181 | int ret; 182 | 183 | __atomic_thread_fence(__ATOMIC_SEQ_CST); 184 | 185 | counter = &ph->counter[phase]; 186 | value = __atomic_add_fetch(counter, -1, __ATOMIC_RELAXED); 187 | if (unlikely(value == COUNT_DRAINED)) { 188 | #if PHASER_STRATEGY == STRATEGY_FUTEX 189 | /* Using INT_MAX here is an abundance of caution. The API contract 190 | limits us to one waiter. */ 191 | ret = phaser_futex(counter, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); 192 | assert(ret != -1); 193 | (void) ret; 194 | #elif PHASER_STRATEGY == STRATEGY_PIPE 195 | /* up our "semaphore" */ 196 | char buf = '\0'; 197 | int old_errno = errno; 198 | do { 199 | ret = write(ph->counter_pipe[1], &buf, 1); 200 | if (ret == -1) { 201 | assert(errno == EINTR); 202 | } 203 | } while (ret == -1); 204 | errno = old_errno; 205 | #else 206 | #error "Unknown phaser strategy" 207 | #endif 208 | } 209 | } 210 | 211 | static void 212 | phaser_drain_counter(phaser_t* ph, phaser_phase phase) 213 | { 214 | size_t* counter = &ph->counter[phase]; 215 | size_t value; 216 | 217 | value = __atomic_or_fetch(counter, DRAINING, __ATOMIC_RELEASE); 218 | #if PHASER_STRATEGY == STRATEGY_FUTEX 219 | while (value != COUNT_DRAINED) { 220 | if (phaser_futex(counter, FUTEX_WAIT, value, NULL, NULL, 0) == -1) { 221 | assert(errno == EWOULDBLOCK || errno == EINTR); 222 | } 223 | 224 | __atomic_load(counter, &value, __ATOMIC_RELAXED); 225 | } 226 | 227 | #elif PHASER_STRATEGY == STRATEGY_PIPE 228 | if (value != COUNT_DRAINED) { 229 | /* down our "semaphore" */ 230 | int ret; 231 | char junk; 232 | do { 233 | ret = read(ph->counter_pipe[0], &junk, 1); 234 | } while (ret < 0 && errno == EINTR); 235 | assert(ret == 1); 236 | } 237 | #else 238 | #error "Unknown phaser strategy" 239 | #endif 240 | __atomic_and_fetch(counter, ~DRAINING, __ATOMIC_RELAXED); 241 | } 242 | 243 | void 244 | phaser_drain(phaser_t* ph) 245 | { 246 | for (phaser_phase i = 0; i < ARRAY_SIZE(ph->counter); ++i) { 247 | phaser_drain_counter(ph, i); 248 | __atomic_thread_fence(__ATOMIC_SEQ_CST); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/phaser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-present, Facebook, Inc. 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 | #pragma once 18 | #include 19 | #include 20 | 21 | /** 22 | * DESCRIPTION 23 | * ----------- 24 | * 25 | * A "phaser" is a synchronization primitive that allows a thread to 26 | * know that all other threads have passed a certain point in 27 | * execution, but without blocking any of these threads. It is useful 28 | * in cases where callers want to guarantee that a shared resource is 29 | * no longer visible before deallocating it. 30 | * 31 | * Any number of threads can enter and exit a "critical region" 32 | * guarded by a phaser; they use the routines exit phaser_enter and 33 | * phaser_exit. These functions are non-waiting, non-blocking, fully 34 | * reentrant, infallible, and async-signal-safe. 35 | * 36 | * Another thread (at most one at a time) can concurrently call 37 | * phaser_drain. This function blocks until all threads that had 38 | * entered the monitored region before the call to phaser_drain make 39 | * the corresponding call to phaser_exit to exit the region. 40 | * 41 | * phaser_enter and phaser_exit are async-signal-safe and completely 42 | * reentrant, making them ideal for use in signal handlers where other 43 | * synchronization primitives may be cumbersome. 44 | * 45 | * PERFORMANCE 46 | * ----------- 47 | * 48 | * Phaser is heavily read-oriented. phaser_enter and phaser_exit are 49 | * atomic increment and atomic decrement in the common case. On an 50 | * Intel i7-4600U, a phaser_enter and phaser_exit pair completes in 51 | * approximately 28 cycles. That cost approximately doubles if a 52 | * phaser_drain is running concurrently. 53 | * 54 | * While phaser by itself performs adequately, it occupies only a 55 | * single cache-line, potentially hurting performance in the 56 | * heavily-contended case. Callers can improve performance in this 57 | * case by a factor of two by using multiple phasers, each situated on 58 | * different cache line. Threads entering critical sections can choose 59 | * a phaser based on CPU affinity, and a thread wanting synchronize on 60 | * all readers can call phaser_drain on each cacheline's phased in 61 | * turn. 62 | * 63 | * PORTABILITY 64 | * ----------- 65 | * 66 | * Phaser relies on the OS providing some kind of wait-on-address 67 | * functionality. On Linux, we use futex directly. On Windows, it 68 | * would be possible to use WaitOnAddress. On FreeBSD, umtx ought to 69 | * work; on iOS, the kernels' psynch_cvwait psynch_cvsignal should 70 | * suffice. 71 | * 72 | * EXAMPLE 73 | * ------- 74 | * 75 | * A contrived example may help clarify the use case. Note that 76 | * worker_threads() performs no heavyweight synchronization and that 77 | * any number of calls to worker_threads() and 78 | * atomically_increment_array() can run concurrently. 79 | * 80 | * std::vector* g_array; 81 | * pthread_mutex_t array_phaser_lock = PTHREAD_MUTEX_INITIALIZER; 82 | * phaser_t array_phaser; 83 | * 84 | * void init() 85 | * { 86 | * pthread_mutex_init(&array_phaser_lock); 87 | * phaser_init(&array_phaser); 88 | * } 89 | * 90 | * void worker_threads() 91 | * { 92 | * phaser_phase phase; 93 | * for(;;) { 94 | * phase = phaser_enter(array_phaser); 95 | * operate_on_array(array); 96 | * phaser_exit(array_phaser, phase); 97 | * } 98 | * } 99 | * 100 | * void atomically_increment_array() 101 | * { 102 | * std::vector* old_array; 103 | * std::vector* new_array; 104 | * bool success; 105 | * 106 | * do { 107 | * phaser_enter(array_phaser); 108 | * 109 | * old_array = __atomic_load_n(&g_array, __ATOMIC_ACQUIRE); 110 | * // NB: in real code, the std::vector constructor can throw, 111 | * // and calling code should take care to exit the phaser 112 | * // critical section if it does. 113 | * new_array = new std::vector(*old_array); 114 | * for(auto it = new_array->begin(); it != new_array->end(); ++it) { 115 | * *it += 1; 116 | * } 117 | * 118 | * // Important to use __ATOMIC_RELEASE on the success path: 119 | * // other threads must see only fully-constructed vector. 120 | * success = 121 | * __atomic_compare_exchange_n( 122 | * &g_array, 123 | * &old_array, 124 | * new_array, 125 | * true // weak: we loop anyway, 126 | * __ATOMIC_RELEASE, 127 | * __ATOMIC_RELAXED); 128 | * 129 | * phaser_exit(array_phaser); 130 | * 131 | * if(!success) { 132 | * * Someone else beat us to the increment, so try again. 133 | * delete new_array; 134 | * } 135 | * } while(!success); 136 | * 137 | * // We exclusively own the old array. Wait for pending readers 138 | * // to finish. 139 | * 140 | * pthread_mutex_lock(&array_phaser_lock); 141 | * phaser_drain(array_phaser); 142 | * pthread_mutex_unlock(&array_phaser_lock); 143 | * 144 | * // Now we know that nobody is using old_array: all 145 | * // references to array occur inside a phaser critical 146 | * // section, and the call to phaser_drain ensured that 147 | * // all critical sections that began while g_array still 148 | * // pointed at old_array have now terminated. 149 | * 150 | * delete old_array; 151 | * } 152 | */ 153 | 154 | #ifdef __cplusplus 155 | extern "C" { 156 | #endif 157 | 158 | #define STRATEGY_FUTEX 1 159 | #define STRATEGY_PIPE 2 160 | 161 | #ifndef PHASER_STRATEGY 162 | #ifdef __linux__ 163 | #define PHASER_STRATEGY STRATEGY_FUTEX 164 | #else 165 | #define PHASER_STRATEGY STRATEGY_PIPE 166 | #endif 167 | #endif 168 | 169 | #if PHASER_STRATEGY == STRATEGY_FUTEX 170 | #define PHASER_UNINITIALIZED { { 0, 0 } } 171 | #elif PHASER_STRATEGY == STRATEGY_PIPE 172 | #define PHASER_UNINITIALIZED { { 0, 0 }, { -1, -1 } } 173 | #else 174 | #error "Unknown phaser strategy" 175 | #endif 176 | 177 | #define NUM_PHASES 2 /* must be power of two! */ 178 | 179 | typedef struct phaser { 180 | size_t counter[NUM_PHASES]; 181 | #if PHASER_STRATEGY == STRATEGY_PIPE 182 | /* We don't have futex on non-Linux, so we fall back to using a pipe 183 | as a semaphore: down = read a byte, up = write a byte. We only 184 | need one pipe because we only drain one counter at a time. */ 185 | int counter_pipe[2]; 186 | #endif 187 | } phaser_t; 188 | 189 | typedef uint32_t phaser_phase; 190 | 191 | /** 192 | * Initialize a phaser object. This function must be paired with 193 | * phaser_destroy. 194 | * Returns 0 on success, nonzero on failure. On failure, errno is set. 195 | */ 196 | int phaser_init(phaser_t* ph); 197 | 198 | /** 199 | * De-initialize a phaser object. ph must previously have been 200 | * successfully initialized with phaser_init. 201 | */ 202 | void phaser_destroy(phaser_t* ph); 203 | 204 | /** 205 | * Enter a phaser critical region. This function must be paired with 206 | * phaser_exit. Return a token to pass to phaser_exit. This function 207 | * is reentrant and async-signal-safe: it may be called even on a 208 | * thread currently executing phaser_drain (e.g., from a signal 209 | * handler). This function cannot fail and does not block or wait. 210 | * 211 | * phaser_enter is a full memory barrier. 212 | */ 213 | __attribute__ ((warn_unused_result)) 214 | phaser_phase phaser_enter(phaser_t* ph); 215 | 216 | /** 217 | * Exit a phaser critical region. This function is reentrant and 218 | * async-signal-safe. PHASE is the return value of the matching 219 | * phaser_enter call. 220 | * 221 | * phaser_exit is a full memory barrier. 222 | */ 223 | void phaser_exit(phaser_t* ph, phaser_phase phase); 224 | 225 | /** 226 | * Block and wait for all active critical regions on PH to exit. 227 | * (That is, wait for the phaser_exit calls corresponding to any 228 | * unpaired phaser_enter calls on PH.) This routine is *not* 229 | * async-signal-safe. Do not call it while the current thread is PH's 230 | * critical region. phaser_drain may or may not wait for the end of 231 | * some critical regions that begin while a phaser_drain call is in 232 | * active, but phaser_drain is guaranteed to make forward progress and 233 | * complete in finite time, assuming critical sections do the same. 234 | * 235 | * Callers must serialize phaser_drain calls on a given phaser object. 236 | * phaser_drain is a full memory barrier and a pthread cancellation 237 | * point. 238 | */ 239 | void phaser_drain(phaser_t* ph); 240 | 241 | #ifdef __cplusplus 242 | } // extern "C" 243 | #endif 244 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sharedlibs.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "sharedlibs.h" 18 | #include "locks.h" 19 | 20 | #include "Build.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table. */ 29 | 30 | namespace facebook { 31 | namespace linker { 32 | 33 | namespace { 34 | 35 | static pthread_rwlock_t sharedLibsMutex_ = PTHREAD_RWLOCK_INITIALIZER; 36 | static std::unordered_map& sharedLibData() { 37 | static std::unordered_map sharedLibData_; 38 | return sharedLibData_; 39 | } 40 | 41 | template 42 | bool addSharedLib(char const* libname, Arg&& arg) { 43 | #if !defined(__aarch64__) 44 | if (sharedLibData().find(basename(libname)) == sharedLibData().end()) { 45 | try { 46 | elfSharedLibData data(std::forward(arg)); 47 | WriterLock wl(&sharedLibsMutex_); 48 | return sharedLibData().insert(std::make_pair(basename(libname), std::move(data))).second; 49 | } catch (input_parse_error&) { 50 | // elfSharedLibData ctor will throw if it is unable to parse input 51 | // just ignore it and don't add the library, we'll return false 52 | } 53 | } 54 | #endif 55 | return false; 56 | } 57 | 58 | bool ends_with(const char* str, const char* ending) { 59 | size_t str_len = strlen(str); 60 | size_t ending_len = strlen(ending); 61 | 62 | if (ending_len > str_len) { 63 | return false; 64 | } 65 | return strcmp(str + (str_len - ending_len), ending) == 0; 66 | } 67 | 68 | } // namespace (anonymous) 69 | 70 | elfSharedLibData sharedLib(char const* libname) { 71 | ReaderLock rl(&sharedLibsMutex_); 72 | auto lib = sharedLibData().at(basename(libname)); 73 | if (!lib) { 74 | throw std::out_of_range(libname); 75 | } 76 | return lib; 77 | } 78 | 79 | std::vector> allSharedLibs() { 80 | ReaderLock rl(&sharedLibsMutex_); 81 | std::vector> libs; 82 | libs.reserve(sharedLibData().size()); 83 | std::copy(sharedLibData().begin(), sharedLibData().end(), std::back_inserter(libs)); 84 | return libs; 85 | } 86 | 87 | // for testing. not exposed via header. 88 | void clearSharedLibs() { 89 | WriterLock wl(&sharedLibsMutex_); 90 | sharedLibData().clear(); 91 | } 92 | 93 | } } // namespace facebook::linker 94 | 95 | extern "C" { 96 | 97 | using namespace facebook::linker; 98 | #define ANDROID_L 21 99 | 100 | int 101 | refresh_shared_libs() { 102 | if (facebook::build::Build::getAndroidSdk() >= ANDROID_L) { 103 | static auto dl_iterate_phdr = 104 | reinterpret_cast( 105 | dlsym(RTLD_DEFAULT, "dl_iterate_phdr")); 106 | 107 | if (dl_iterate_phdr == nullptr) { 108 | // Undefined symbol. 109 | return 1; 110 | } 111 | 112 | dl_iterate_phdr(+[](dl_phdr_info* info, size_t, void*) { 113 | if (info->dlpi_name && ends_with(info->dlpi_name, ".so")) { 114 | addSharedLib(info->dlpi_name, info); 115 | } 116 | return 0; 117 | }, nullptr); 118 | } else { 119 | #ifndef __LP64__ 120 | soinfo* si = reinterpret_cast(dlopen(nullptr, RTLD_LOCAL)); 121 | 122 | if (si == nullptr) { 123 | return 1; 124 | } 125 | 126 | for (; si != nullptr; si = si->next) { 127 | if (si->link_map.l_name && ends_with(si->link_map.l_name, ".so")) { 128 | addSharedLib(si->link_map.l_name, si); 129 | } 130 | } 131 | #endif 132 | } 133 | return 0; 134 | } 135 | 136 | } // extern C 137 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sharedlibs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include "elfSharedLibData.h" 20 | 21 | #include 22 | #include 23 | 24 | namespace facebook { 25 | namespace linker { 26 | 27 | /** 28 | * Looks up an elfSharedLibData by name. 29 | */ 30 | elfSharedLibData sharedLib(char const*); 31 | 32 | /** 33 | * Returns a vector of copies of all known elfSharedLibData objects at this moment. 34 | */ 35 | std::vector> allSharedLibs(); 36 | 37 | } } // namespace facebook::linker 38 | 39 | extern "C" { 40 | 41 | /** 42 | * Learns about all shared libraries in the process and creates elfSharedLibData 43 | * entries for any we don't already know of. 44 | */ 45 | int refresh_shared_libs(); 46 | 47 | } // extern "C" 48 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sig_safe_write.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "sig_safe_write.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "sigmux.h" 23 | 24 | struct fault_handler_data { 25 | int tid; 26 | int active; 27 | int check_sigill; 28 | jmp_buf jump_buffer; 29 | }; 30 | 31 | /** 32 | * Returns an integer uniquely identifying current thread. 33 | * This function is async-signal-safe. 34 | */ 35 | static int 36 | as_safe_gettid() { 37 | return syscall(__NR_gettid); 38 | } 39 | 40 | /** 41 | * Signal handler that jumps out of the handler after determining the signal is 42 | * caused by executing predetermined code on predetermined thread. 43 | */ 44 | static enum sigmux_action 45 | fault_handler( 46 | struct sigmux_siginfo *siginfo, 47 | void *handler_data) { 48 | struct fault_handler_data *data = (struct fault_handler_data *) handler_data; 49 | siginfo_t *info = siginfo->info; 50 | 51 | if (__atomic_load_n(&data->tid, __ATOMIC_SEQ_CST) != as_safe_gettid()) { 52 | return SIGMUX_CONTINUE_SEARCH; 53 | } 54 | 55 | if (!__atomic_load_n(&data->active, __ATOMIC_SEQ_CST)) { 56 | return SIGMUX_CONTINUE_SEARCH; 57 | } 58 | 59 | if (__atomic_load_n(&data->check_sigill, __ATOMIC_SEQ_CST)) { 60 | /* Expect SIGILL signal */ 61 | if (info->si_signo != SIGILL) { 62 | return SIGMUX_CONTINUE_SEARCH; 63 | } 64 | } else { 65 | /* Expect SIGSEGV or SIGBUS signal */ 66 | if (info->si_signo != SIGSEGV && info->si_signo != SIGBUS) { 67 | return SIGMUX_CONTINUE_SEARCH; 68 | } 69 | } 70 | 71 | sigmux_longjmp(siginfo, data->jump_buffer, 1); 72 | 73 | // not reached 74 | return SIGMUX_CONTINUE_EXECUTION; 75 | } 76 | 77 | int 78 | sig_safe_op(void (*op)(void *), void *data) { 79 | struct fault_handler_data handler_data = {}; 80 | struct sigmux_registration *registration = NULL; 81 | int result = 1; 82 | 83 | __atomic_store_n(&handler_data.tid, as_safe_gettid(), __ATOMIC_SEQ_CST); 84 | __atomic_store_n(&handler_data.check_sigill, 0, __ATOMIC_SEQ_CST); 85 | 86 | if (sigsetjmp(handler_data.jump_buffer, 1)) { 87 | errno = EFAULT; 88 | goto out; 89 | } 90 | 91 | sigset_t sigset; 92 | if (sigemptyset(&sigset) || 93 | sigaddset(&sigset, SIGSEGV) || 94 | sigaddset(&sigset, SIGBUS)) { 95 | goto out; 96 | } 97 | 98 | registration = sigmux_register(&sigset, &fault_handler, &handler_data, 0); 99 | if (!registration) { 100 | goto out; 101 | } 102 | 103 | __atomic_store_n(&handler_data.active, 1, __ATOMIC_SEQ_CST); 104 | op(data); 105 | __atomic_store_n(&handler_data.active, 0, __ATOMIC_SEQ_CST); 106 | 107 | result = 0; 108 | 109 | out: 110 | if (registration) { 111 | int old_errno = errno; 112 | sigmux_unregister(registration); 113 | errno = old_errno; 114 | } 115 | 116 | return result; 117 | } 118 | 119 | int 120 | sig_safe_exec(void (*op)(void *), void *data) { 121 | struct fault_handler_data handler_data = {}; 122 | struct sigmux_registration *registration = NULL; 123 | int result = 1; 124 | 125 | __atomic_store_n(&handler_data.tid, as_safe_gettid(), __ATOMIC_SEQ_CST); 126 | __atomic_store_n(&handler_data.check_sigill, 1, __ATOMIC_SEQ_CST); 127 | 128 | if (sigsetjmp(handler_data.jump_buffer, 1)) { 129 | errno = EFAULT; 130 | goto out; 131 | } 132 | 133 | sigset_t sigset; 134 | if (sigemptyset(&sigset) || 135 | sigaddset(&sigset, SIGILL)) { 136 | goto out; 137 | } 138 | 139 | registration = sigmux_register(&sigset, &fault_handler, &handler_data, 0); 140 | if (!registration) { 141 | goto out; 142 | } 143 | 144 | __atomic_store_n(&handler_data.active, 1, __ATOMIC_SEQ_CST); 145 | op(data); 146 | __atomic_store_n(&handler_data.active, 0, __ATOMIC_SEQ_CST); 147 | 148 | result = 0; 149 | 150 | out: 151 | if (registration) { 152 | int old_errno = errno; 153 | sigmux_unregister(registration); 154 | errno = old_errno; 155 | } 156 | 157 | return result; 158 | } 159 | 160 | struct write_params { 161 | void *destination; 162 | intptr_t value; 163 | }; 164 | 165 | static void 166 | sig_safe_write_op(void *data) { 167 | struct write_params *params = (struct write_params *) data; 168 | 169 | __atomic_store_n( 170 | (intptr_t *) params->destination, 171 | params->value, 172 | __ATOMIC_SEQ_CST); 173 | } 174 | 175 | int 176 | sig_safe_write(void *destination, intptr_t value) { 177 | struct write_params params = { 178 | .destination = destination, 179 | .value = value, 180 | }; 181 | 182 | return sig_safe_op(&sig_safe_write_op, ¶ms); 183 | } 184 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sig_safe_write.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | /** 26 | * SIGSEGV and SIGBUS safe op. Performs the specified 27 | * op, but first registers sigmux based SIGSEGV and SIGBUS 28 | * handler that bails out in case of failure. 29 | * 30 | * The operation will receive the value of data as a parameter. 31 | * 32 | * If successful, returns 0. In case of error returns non-zero 33 | * value and sets errno appropriately. 34 | */ 35 | int 36 | sig_safe_op(void (*op)(void* data), void* data); 37 | 38 | /** 39 | * SIGILL safe op. Performs the specified op, but first registers sigmux based 40 | * SIGILL handler that bails out in case of failure. 41 | * 42 | * The operation will receive the value of data as a parameter. 43 | * 44 | * If successful, returns 0. In case of error returns non-zero 45 | * value and sets errno appropriately. 46 | */ 47 | int 48 | sig_safe_exec(void (*op)(void* data), void* data); 49 | 50 | /** 51 | * Like sig_safe_op but specifically for memory writes. 52 | * Writes the specified value to the target address with all the protections 53 | * of sig_safe_op. 54 | * 55 | * If successful, returns 0. In case of error returns non-zero 56 | * value and sets errno appropriately. 57 | */ 58 | int 59 | sig_safe_write(void* destination, intptr_t value); 60 | 61 | #ifdef __cplusplus 62 | } 63 | #endif 64 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sigmux.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-present, Facebook, Inc. 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 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "sigmux.h" 25 | #include "phaser.h" 26 | 27 | #ifndef USE_RT_SIGPROCMASK 28 | # if defined(__linux__) && defined(__ANDROID__) 29 | # define USE_RT_SIGPROCMASK 1 30 | # else 31 | # define USE_RT_SIGPROCMASK 0 32 | # endif 33 | #endif 34 | 35 | #if USE_RT_SIGPROCMASK 36 | 37 | # include "linux_syscall_support.h" 38 | 39 | #endif 40 | 41 | #define ARRAY_SIZE(a) ((sizeof ((a)) / (sizeof ((a)[0])))) 42 | 43 | static inline bool verify_dummy(bool b) { return b; } 44 | 45 | #ifdef NDEBUG 46 | #define VERIFY(e) verify_dummy((e)) 47 | #else 48 | #define VERIFY(e) (assert((e)), verify_dummy(true)) 49 | #endif 50 | 51 | // Cast from part of a structure to the whole thing. Identical to the 52 | // Linux kernel container_of macro. 53 | #define container_of(ptr, type, member) ({ \ 54 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 55 | (type *)( (char *)__mptr - offsetof(type,member) ); \ 56 | }) 57 | 58 | struct sigmux_siginfo_internal { 59 | struct sigmux_siginfo public; 60 | phaser_phase phase; 61 | }; 62 | 63 | // N.B. concurrent readers may only iterate over the list in the 64 | // forward direction. Access to prev pointers requires callers to 65 | // hold the modification mutex. 66 | 67 | struct sigmux_registration_link { 68 | struct sigmux_registration_link *prev; 69 | struct sigmux_registration_link *next; 70 | }; 71 | 72 | struct sigmux_registration { 73 | struct sigmux_registration_link link; 74 | sigset_t signals; 75 | sigmux_handler handler; 76 | void *handler_data; 77 | unsigned flags; 78 | }; 79 | 80 | // Use uint8_t and own bit-set structure instead of sigset_t for 81 | // sigmux_global.initsig. This way, the debugger doesn't have to 82 | // understand the implementation of sigset_t. 83 | struct sigmux_sigset { 84 | uint8_t s[(NSIG + 7) / 8]; 85 | }; 86 | 87 | // N.B. Not static --- we want to be able to find this symbol using 88 | // gdb.lookup_global_symbol. We use -fvisibility=hidden, so this 89 | // symbol still isn't exposed to other DSOs. 90 | struct sigmux_global { 91 | pthread_mutex_t lock; 92 | phaser_t phaser; 93 | int phaser_needs_init; 94 | struct sigaction *orig_sigact[NSIG]; 95 | struct sigaction *alt_sigact[NSIG]; 96 | struct sigmux_registration_link handlers; 97 | sigmux_sigaction_function real_sigaction; 98 | struct sigmux_sigset initsig; 99 | } sigmux_global = { 100 | .lock = PTHREAD_MUTEX_INITIALIZER, 101 | .phaser = PHASER_UNINITIALIZED, 102 | .phaser_needs_init = 1, 103 | .handlers = {&sigmux_global.handlers, &sigmux_global.handlers}, 104 | }; 105 | 106 | #ifdef __ANDROID__ 107 | static const sigset_t no_signals; 108 | #endif 109 | 110 | static int 111 | invoke_real_sigaction(int signum, 112 | const struct sigaction *act, 113 | struct sigaction *oldact) { 114 | return (sigmux_global.real_sigaction ?: sigaction)(signum, act, oldact); 115 | } 116 | 117 | static int 118 | sigmux_sigismember(const struct sigmux_sigset *ss, int signum) { 119 | if (!(0 < signum && signum < NSIG)) { 120 | errno = EINVAL; 121 | return -1; 122 | } 123 | 124 | return !!(ss->s[signum / 8] & (1 << (signum % 8))); 125 | } 126 | 127 | static void 128 | sigmux_sigaddset(struct sigmux_sigset *ss, int signum) { 129 | ss->s[signum / 8] |= (1 << (signum % 8)); 130 | } 131 | 132 | /** 133 | * Is this signal fatal when configured for SIG_DFL? 134 | */ 135 | static bool 136 | signal_default_fatal_p(int signum) { 137 | switch (signum) { 138 | case SIGCHLD: 139 | case SIGWINCH: 140 | return false; 141 | default: 142 | return true; 143 | } 144 | } 145 | 146 | /** 147 | * Is this signal fatal when configured for SIG_IGN? 148 | */ 149 | static bool 150 | signal_always_fatal_p(int signum) { 151 | switch (signum) { 152 | case SIGSEGV: 153 | case SIGBUS: 154 | case SIGABRT: 155 | case SIGILL: 156 | return true; 157 | default: 158 | return false; 159 | } 160 | } 161 | 162 | /* Clang does not have noclone. */ 163 | #ifdef __clang__ 164 | #pragma clang diagnostic push 165 | #pragma clang diagnostic ignored "-Wunknown-attributes" 166 | #endif 167 | 168 | /** 169 | * The debugger sets a breakpoint here to run code when sigmux has run 170 | * out of signal-handling options and is about to die horribly. 171 | */ 172 | __attribute__((noinline, noclone)) 173 | void // Not static! 174 | sigmux_gdbhook_on_fatal_signal(siginfo_t *info, void *context) { 175 | /* Noop: debugger sets breakpoint here */ 176 | (void) info; 177 | (void) context; 178 | asm volatile (""); 179 | } 180 | 181 | /** 182 | * The debugger sets a breakpoint here to run code after sigmux has 183 | * taken over responsibility for a signal. 184 | */ 185 | __attribute__((noinline, noclone)) 186 | void // Not static! 187 | sigmux_gdbhook_on_signal_seized(void) { 188 | /* Noop: debugger sets breakpoint here */ 189 | asm volatile (""); 190 | } 191 | 192 | #ifdef __clang__ 193 | #pragma clang diagnostic pop 194 | #endif 195 | 196 | static void 197 | set_signal_handler_to_default(int signum) { 198 | #if USE_RT_SIGPROCMASK 199 | // sigchain has a bug in Android 5.0.x where it ignores attempts to 200 | // reset to SIG_DFL; just use the system call directly in this case. 201 | struct kernel_sigaction sa; 202 | memset(&sa, 0, sizeof(sa)); 203 | sys_sigemptyset(&sa.sa_mask); 204 | sa.sa_handler_ = SIG_DFL; 205 | sa.sa_flags = SA_RESTART; 206 | sys_rt_sigaction(signum, &sa, NULL, sizeof(struct kernel_sigset_t)); 207 | #else 208 | struct sigaction sa; 209 | memset(&sa, 0, sizeof (sa)); 210 | sa.sa_handler = SIG_DFL; 211 | sa.sa_flags = SA_RESTART; 212 | invoke_real_sigaction(signum, &sa, NULL); 213 | #endif 214 | } 215 | 216 | static void 217 | set_sigmask_for_handler(const struct sigaction *action, int signum) { 218 | sigset_t new_mask; 219 | memcpy(&new_mask, &action->sa_mask, sizeof(sigset_t)); 220 | if ((action->sa_flags & SA_NODEFER) == 0) { 221 | sigaddset(&new_mask, signum); 222 | } 223 | sigprocmask(SIG_SETMASK, &new_mask, NULL); 224 | } 225 | 226 | static void 227 | invoke_sighandler(const struct sigaction *action, 228 | int signum, 229 | siginfo_t *info, 230 | void *context) { 231 | if (signal_always_fatal_p(signum)) { 232 | sigmux_gdbhook_on_fatal_signal(info, context); 233 | } 234 | 235 | // N.B. We don't need to restore the signal mask, since returning 236 | // normally from the signal handler will do it for us. If the 237 | // signal handler returns non-locally, it has the burden of 238 | // resetting the signal mask whether it's being called by the kernel 239 | // directly or by us. 240 | // 241 | // Also note that the default action of any signal is to either do 242 | // nothing, bring down the process, or stop the process. 243 | 244 | bool is_siginfo = (action->sa_flags & SA_SIGINFO); 245 | bool is_default = is_siginfo 246 | ? action->sa_sigaction == NULL 247 | : action->sa_handler == SIG_DFL; 248 | bool is_ignore = !is_siginfo && action->sa_handler == SIG_IGN; 249 | 250 | if (is_siginfo && !is_default && !is_ignore) { 251 | set_sigmask_for_handler(action, signum); 252 | action->sa_sigaction(signum, info, context); 253 | } else if (!is_siginfo && !is_default && !is_ignore) { 254 | set_sigmask_for_handler(action, signum); 255 | action->sa_handler(signum); 256 | } else if (signal_always_fatal_p(signum) || 257 | (is_default && signal_default_fatal_p(signum))) { 258 | sigset_t to_unblock; 259 | set_signal_handler_to_default(signum); 260 | sigemptyset(&to_unblock); 261 | sigaddset(&to_unblock, signum); 262 | sigprocmask(SIG_UNBLOCK, &to_unblock, NULL); 263 | raise(signum); 264 | abort(); 265 | } else if (is_default && (signum == SIGTSTP || 266 | signum == SIGTTIN || 267 | signum == SIGTTOU)) { 268 | raise(SIGSTOP); 269 | } 270 | } 271 | 272 | void 273 | sigmux_longjmp( 274 | struct sigmux_siginfo *public_siginfo, 275 | sigjmp_buf buf, 276 | int val) { 277 | struct sigmux_siginfo_internal *siginfo = 278 | container_of(public_siginfo, 279 | struct sigmux_siginfo_internal, 280 | public); 281 | 282 | phaser_exit(&sigmux_global.phaser, siginfo->phase); 283 | siglongjmp(buf, val); 284 | } 285 | 286 | enum sigmux_action 287 | sigmux_handle_signal( 288 | int signum, 289 | siginfo_t *info, 290 | void *context, 291 | int flags) { 292 | struct sigmux_registration *reg; 293 | struct sigmux_registration_link *it; 294 | struct sigmux_registration_link *reglist; 295 | 296 | enum sigmux_action action; 297 | struct sigmux_siginfo_internal siginfo = { 298 | .public = { 299 | .signum = signum, 300 | .info = info, 301 | .context = context, 302 | }, 303 | 304 | .phase = phaser_enter(&sigmux_global.phaser), 305 | }; 306 | 307 | // __ATOMIC_RELAXED is fine: phaser_enter is a memory barrier. 308 | reglist = __atomic_load_n(&sigmux_global.handlers.next, __ATOMIC_RELAXED); 309 | action = SIGMUX_CONTINUE_SEARCH; 310 | 311 | if ((flags & SIGMUX_HANDLE_SIGNAL_NORMAL_PRIORITY)) { 312 | for (it = reglist; 313 | it != &sigmux_global.handlers && action == SIGMUX_CONTINUE_SEARCH; 314 | it = it->next) { 315 | reg = container_of(it, struct sigmux_registration, link); 316 | if ((reg->flags & SIGMUX_LOW_PRIORITY) == 0 && 317 | sigismember(®->signals, signum)) { 318 | action = reg->handler(&siginfo.public, reg->handler_data); 319 | } 320 | } 321 | } 322 | 323 | if ((flags & SIGMUX_HANDLE_SIGNAL_LOW_PRIORITY)) { 324 | for (it = reglist; 325 | it != &sigmux_global.handlers && action == SIGMUX_CONTINUE_SEARCH; 326 | it = it->next) { 327 | reg = container_of(it, struct sigmux_registration, link); 328 | if ((reg->flags & SIGMUX_LOW_PRIORITY) != 0 && 329 | sigismember(®->signals, signum)) { 330 | action = reg->handler(&siginfo.public, reg->handler_data); 331 | } 332 | } 333 | } 334 | 335 | // We need to copy the next handler to local storage _before_ we end 336 | // the phase, then use this local storage in invoke_sighandler. 337 | // If we just used the default sighandler directly, we'd race with 338 | // concurrent callers to sigmux_sigaction. We can't just end the 339 | // phase after we call invoke_sighandler, because invoke_sighandler 340 | // may return non-locally. For the same reason, we can't just 341 | // protect orig_sigact with a lock. 342 | 343 | struct sigaction next_handler; 344 | 345 | if ((flags & SIGMUX_HANDLE_SIGNAL_INVOKE_DEFAULT) && 346 | action == SIGMUX_CONTINUE_SEARCH) { 347 | struct sigaction *next_handler_snapshot = 348 | __atomic_load_n(&sigmux_global.orig_sigact[signum], __ATOMIC_CONSUME); 349 | next_handler = *next_handler_snapshot; 350 | // For one-shot signal handlers, we execute the action only once, 351 | // so let threads compete to see who can set sa_handler (or 352 | // sa_sigaction) to NULL first. If we win, the CAS succeeds and 353 | // sa_handler (or sa_sigaction) ends up with the handler to 354 | // execute. If we lose, it ends up with SIG_DFL (or NULL), which 355 | // tells us to ignore the signal in invoke_sighandler. 356 | if (next_handler.sa_flags & SA_RESETHAND) { 357 | if (next_handler.sa_flags & SA_SIGINFO) { 358 | (void) __atomic_compare_exchange_n(&next_handler_snapshot->sa_handler, 359 | &next_handler.sa_handler, 360 | SIG_DFL, 361 | false /* weak */, 362 | __ATOMIC_RELAXED, 363 | __ATOMIC_RELAXED); 364 | } else { 365 | (void) __atomic_compare_exchange_n(&next_handler_snapshot->sa_sigaction, 366 | &next_handler.sa_sigaction, 367 | NULL, 368 | false /* weak */, 369 | __ATOMIC_RELAXED, 370 | __ATOMIC_RELAXED); 371 | 372 | } 373 | // Don't bother going through this process next time. 374 | next_handler_snapshot->sa_flags &= ~SA_RESETHAND; 375 | } 376 | } 377 | 378 | phaser_exit(&sigmux_global.phaser, siginfo.phase); 379 | 380 | if ((flags & SIGMUX_HANDLE_SIGNAL_INVOKE_DEFAULT) && 381 | action == SIGMUX_CONTINUE_SEARCH) { 382 | invoke_sighandler(&next_handler, signum, info, context); 383 | action = SIGMUX_CONTINUE_EXECUTION; 384 | } 385 | 386 | return action; 387 | } 388 | 389 | static void 390 | sigmux_handle_signal_1( 391 | int signum, 392 | siginfo_t *info, 393 | void *context) { 394 | #ifdef __ANDROID__ 395 | // Depending on Android version, sigchain can call us with any 396 | // random signal mask set despite our asking for no blocked signals 397 | // and our using SA_NODEFER. Reset the signal mask explicitly. 398 | sigprocmask(SIG_SETMASK, &no_signals, NULL); 399 | #endif 400 | (void) sigmux_handle_signal( 401 | signum, 402 | info, 403 | context, 404 | (SIGMUX_HANDLE_SIGNAL_NORMAL_PRIORITY | 405 | SIGMUX_HANDLE_SIGNAL_LOW_PRIORITY | 406 | SIGMUX_HANDLE_SIGNAL_INVOKE_DEFAULT)); 407 | } 408 | 409 | static struct sigaction * 410 | allocate_sigaction(struct sigaction **sap) { 411 | if (*sap == NULL) { 412 | *sap = calloc(1, sizeof(**sap)); 413 | } 414 | return *sap; 415 | } 416 | 417 | int 418 | sigmux_init(int signum) { 419 | int ismem; 420 | struct sigaction newact; 421 | int ret = -1; 422 | 423 | VERIFY(0 == pthread_mutex_lock(&sigmux_global.lock)); 424 | if (sigmux_global.phaser_needs_init) { 425 | if (phaser_init(&sigmux_global.phaser) != 0) { 426 | goto out; 427 | } 428 | sigmux_global.phaser_needs_init = 0; 429 | } 430 | ismem = sigmux_sigismember(&sigmux_global.initsig, signum); 431 | if (ismem == -1) { 432 | goto out; 433 | } 434 | 435 | if (ismem == 0) { 436 | 437 | struct sigaction *orig_sigact = 438 | allocate_sigaction(&sigmux_global.orig_sigact[signum]); 439 | if (orig_sigact == NULL) { 440 | goto out; 441 | } 442 | 443 | // Pre-allocate spare memory for sigmux_sigaction, since it isn't 444 | // allowed to fail with ENOMEM. 445 | if (allocate_sigaction(&sigmux_global.alt_sigact[signum]) == NULL) { 446 | goto out; 447 | } 448 | 449 | memset(&newact, 0, sizeof(newact)); 450 | newact.sa_sigaction = sigmux_handle_signal_1; 451 | newact.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK | SA_RESTART; 452 | if (invoke_real_sigaction(signum, &newact, orig_sigact) != 0) { 453 | goto out; 454 | } 455 | 456 | sigmux_sigaddset(&sigmux_global.initsig, signum); 457 | __atomic_signal_fence(__ATOMIC_SEQ_CST); 458 | sigmux_gdbhook_on_signal_seized(); 459 | } 460 | 461 | ret = 0; 462 | 463 | out: 464 | 465 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 466 | return ret; 467 | } 468 | 469 | struct sigmux_registration * 470 | sigmux_register( 471 | const sigset_t *signals, 472 | sigmux_handler handler, 473 | void *handler_data, 474 | unsigned flags) { 475 | struct sigmux_registration *reg; 476 | 477 | reg = calloc(1, sizeof(*reg)); 478 | if (reg == NULL) { 479 | return NULL; 480 | } 481 | 482 | reg->signals = *signals; 483 | reg->handler = handler; 484 | reg->handler_data = handler_data; 485 | reg->flags = flags; 486 | 487 | VERIFY(0 == pthread_mutex_lock(&sigmux_global.lock)); 488 | 489 | // Atomically prepend our handler to the list. We perform all 490 | // modification to the list under sigmux_global.lock, so we need 491 | // only worry about concurrent readers of 492 | // sigmux_global.handlers.next, who will see either the old 493 | // sigmux_global.handlers.next or our new one. __ATOMIC_RELEASE 494 | // ensures readers see only fully-constructed object. 495 | 496 | reg->link.next = sigmux_global.handlers.next; 497 | reg->link.prev = &sigmux_global.handlers; 498 | reg->link.next->prev = ®->link; 499 | __atomic_store_n(&sigmux_global.handlers.next, ®->link, __ATOMIC_RELEASE); 500 | 501 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 502 | 503 | return reg; 504 | } 505 | 506 | void 507 | sigmux_unregister(struct sigmux_registration *registration_cookie) { 508 | struct sigmux_registration *reg = registration_cookie; 509 | 510 | // Make concurrent readers bypass the handler we're trying to 511 | // unregister. Wait for all active readers to complete. The 512 | // phaser_drain is a memory barrier, so our write to reg->prev will 513 | // be visible. 514 | VERIFY(0 == pthread_mutex_lock(&sigmux_global.lock)); 515 | reg->link.prev->next = reg->link.next; 516 | reg->link.next->prev = reg->link.prev; 517 | phaser_drain(&sigmux_global.phaser); 518 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 519 | free(reg); 520 | } 521 | 522 | int 523 | sigmux_sigaction(int signum, 524 | const struct sigaction *act, 525 | struct sigaction *oldact) { 526 | VERIFY(0 == pthread_mutex_lock(&sigmux_global.lock)); 527 | 528 | if (!sigmux_sigismember(&sigmux_global.initsig, signum) 529 | || signum <= 0 || signum >= NSIG) { 530 | // We're not hooked, so just defer to the original sigaction. 531 | // Make sure to release the lock before we do so that if 532 | // real_sigaction is some kind of weird thing that ends up calling 533 | // back into us, we don't deadlock. 534 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 535 | return invoke_real_sigaction(signum, act, oldact); 536 | } 537 | 538 | // sigaction(2) is technically isn't allowed to crash if ACT or 539 | // OLDACT points to invalid memory (it's supposed to fail with 540 | // EFAULT instead), and we do, but this particular spec violation 541 | // probably doesn't matter. (We could use mincore(2) to test the 542 | // memory, but we could still race with an unmap or mprotect.) 543 | 544 | if (oldact != NULL) { 545 | *oldact = *sigmux_global.orig_sigact[signum]; 546 | 547 | // If the current handler is a one-shot handler, it may be in an 548 | // invalid state as a result of how we use atomic CAS to implement 549 | // SA_RESETHAND. In this case, munge the returned struct 550 | // sigaction to make it look like we atomically reset the 551 | // whole thing. 552 | 553 | bool pretend_default = 554 | ((oldact->sa_flags & SA_SIGINFO) && oldact->sa_sigaction == NULL) || 555 | (!(oldact->sa_flags & SA_SIGINFO) && oldact->sa_handler == SIG_DFL); 556 | 557 | if (pretend_default) { 558 | oldact->sa_flags &= ~(SA_RESETHAND | SA_SIGINFO); 559 | oldact->sa_handler = SIG_DFL; 560 | } 561 | } 562 | 563 | if (act != NULL) { 564 | *sigmux_global.alt_sigact[signum] = *act; 565 | __atomic_exchange(&sigmux_global.orig_sigact[signum], 566 | &sigmux_global.alt_sigact[signum], 567 | &sigmux_global.alt_sigact[signum], 568 | __ATOMIC_RELEASE); 569 | phaser_drain(&sigmux_global.phaser); 570 | } 571 | 572 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 573 | return 0; 574 | } 575 | 576 | 577 | sigmux_sigaction_function 578 | sigmux_set_real_sigaction(sigmux_sigaction_function real_sigaction) { 579 | sigmux_sigaction_function old_sigaction; 580 | VERIFY(0 == pthread_mutex_lock(&sigmux_global.lock)); 581 | old_sigaction = sigmux_global.real_sigaction; 582 | sigmux_global.real_sigaction = real_sigaction; 583 | VERIFY(0 == pthread_mutex_unlock(&sigmux_global.lock)); 584 | return old_sigaction; 585 | } 586 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/sigmux.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018-present, Facebook, Inc. 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 | /* 18 | * The sigmux library provides a central point for cooperative 19 | * signal handling in a single process on POSIX systems. 20 | * 21 | * The POSIX signal API associates signals with handlers for an entire 22 | * process, and a given signal can have only one handler. This 23 | * property makes it difficult for distinct components running in a 24 | * single process to cooperate. While a newly-registered signal 25 | * handler can forward to a previously-installer handler (many 26 | * existing libraries using this approach), this technique requires a 27 | * strict nesting of handling installation and uninstallation, making 28 | * it impossible in practice to safely remove handlers and unload 29 | * components. 30 | * 31 | * libsigmux provides a central dispatch point for signal handling. 32 | * Components can register for signal handler processing and elect to 33 | * either handle a given signal or delegate to other handlers. 34 | * Components can register and unregister their handlers in arbitrary 35 | * order. 36 | * 37 | */ 38 | 39 | #pragma once 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #ifdef SIGMUX_BUILD_DSO 46 | #define SIGMUX_EXPORT __attribute__((visibility("default"))) 47 | #else 48 | #define SIGMUX_EXPORT 49 | #endif 50 | 51 | #ifdef __cplusplus 52 | extern "C" { 53 | #endif 54 | 55 | struct sigmux_registration; 56 | 57 | enum sigmux_action { 58 | SIGMUX_CONTINUE_SEARCH, 59 | SIGMUX_CONTINUE_EXECUTION 60 | }; 61 | 62 | struct sigmux_siginfo { 63 | int signum; 64 | siginfo_t* info; 65 | void* context; 66 | }; 67 | 68 | /** 69 | * Handle callback for a signal. See sigmux_add. 70 | */ 71 | typedef enum sigmux_action (*sigmux_handler)( 72 | struct sigmux_siginfo* siginfo, 73 | void* handler_data 74 | ); 75 | 76 | /** 77 | * Initialize the library for use with signal signum. On success, 78 | * return 0. On error, return -1 with errno set. 79 | * 80 | * sigmux_init installs a signal handler with sigaction. If a signal 81 | * handler is installed when sigmux_init is called, sigmux will invoke 82 | * that handler when no sigmux-registered handler opts to handle a 83 | * signal. Do not manually install signal handlers for signum after 84 | * calling sigmux_init on signum. The signal handler is installed 85 | * with SA_NODEFER | SA_ONSTACK | SA_RESTART. 86 | * 87 | * Once initialized, the sigmux library cannot be uninitialized or 88 | * removed from memory. 89 | * 90 | * sigmux_init is idempotent with respect to a given signal number. 91 | */ 92 | SIGMUX_EXPORT extern int sigmux_init( 93 | int signum 94 | ); 95 | 96 | /** 97 | * Exit non-locally from a sigmux handler. Use this routine instead 98 | * of longjmp or siglongjmp in order to restore internal sigmux 99 | * bookkeeping information. SIGINFO is the signal information passed 100 | * to the current handler; BUF and VAL are as for regular siglongjmp. 101 | */ 102 | SIGMUX_EXPORT __attribute__((noreturn)) void sigmux_longjmp( 103 | struct sigmux_siginfo* siginfo, 104 | sigjmp_buf buf, 105 | int val 106 | ); 107 | 108 | #define SIGMUX_LOW_PRIORITY (1<<0) 109 | 110 | /** 111 | * Register a signal handler with sigmux. Return a registration 112 | * cookie on success or NULL with errno set on error. 113 | * 114 | * SIGNALS is a set of signals for which to invoke HANDLER. 115 | * Registration occurs atomically for all signals. HANDLER is a 116 | * function to call when a signal in SIGNALS arrives. HANDLER_DATA is 117 | * passed to HANDLER along with signal-specific information. 118 | * 119 | * Handlers for a given signal are invoked in reverse order of 120 | * registration. 121 | * 122 | * When a signal arrives, sigmux invokes all registered handlers for 123 | * that signal until one returns SIGMUX_CONTINUE_EXECUTION. If a 124 | * handler returns SIGMUX_CONTINUE_EXECUTION, sigmux returns normally 125 | * from its POSIX signal handler. If no sigmux handler returns 126 | * SIGMUX_CONTINUE_EXECUTION, sigmux invokes the signal handler that 127 | * was in effect when sigmux_init was called on that signal. 128 | * 129 | * FLAGS contains zero or more of the SIGMUX_ flag constants above. 130 | * 131 | * SIGMUX_LOW_PRIORITY --- try the corresponding handler only after 132 | * all handlers that did not specify SIGMUX_LOW_PRIORITY returned 133 | * SIGMUX_CONTINUE_SEARCH. 134 | * 135 | * In rare cases, sigmux may re-run handlers that returned 136 | * SIGMUX_CONTINUE_SEARCH. 137 | * 138 | * The caller must have called sigmux_init on each signal number for 139 | * which a handler is being registered. 140 | */ 141 | SIGMUX_EXPORT struct sigmux_registration* sigmux_register( 142 | const sigset_t* signals, 143 | sigmux_handler handler, 144 | void* handler_data, 145 | unsigned flags 146 | ); 147 | 148 | /** 149 | * Unregister a signal handler registered with sigmux_register. 150 | * REGISTRATION_COOKIE is the value sigmux_register returned. 151 | */ 152 | SIGMUX_EXPORT void sigmux_unregister( 153 | struct sigmux_registration* registration_cookie 154 | ); 155 | 156 | /** 157 | * Like sigaction(2), but operates on the next-signal handlers instead 158 | * of the current one. This routine is useful either as a drop-in 159 | * replacement for sigaction(2) or as a forced replacement for 160 | * sigaction emplaced with LD_PRELOAD or a function hooking library 161 | * like distract. Use sigmux_set_real_sigaction to tell sigmux the 162 | * function to call instead of sigaction. 163 | */ 164 | SIGMUX_EXPORT int sigmux_sigaction(int signum, 165 | const struct sigaction* act, 166 | struct sigaction* oldact); 167 | 168 | /** 169 | * Sigmux sigaction implementation. 170 | */ 171 | typedef int (*sigmux_sigaction_function)( 172 | int signum, 173 | const struct sigaction* act, 174 | struct sigaction* oldact); 175 | 176 | #define SIGMUX_SIGACTION_DEFAULT NULL 177 | 178 | /** 179 | * Tell sigmux to call REAL_SIGACTION intead of the default 180 | * sigaction(2). This routine is useful then using a hooking facility 181 | * to redirect the default operating system sigaction(2) to 182 | * sigmux_sigaction. If SIGMUX_SIGACTION_DEFAULT is specified, call 183 | * the default function. Return the previous sigaction handler. 184 | * 185 | * N.B. We provide this method instead of just using the rt_sigaction 186 | * system call so that we properly integrate with someone who's hooked 187 | * sigaction before we did, e.g., libsigchain in Android's 188 | * ART runtime. 189 | */ 190 | SIGMUX_EXPORT sigmux_sigaction_function sigmux_set_real_sigaction( 191 | sigmux_sigaction_function real_sigaction); 192 | 193 | /** 194 | * Consider handlers registered with normal priority. 195 | */ 196 | #define SIGMUX_HANDLE_SIGNAL_NORMAL_PRIORITY (1<<0) 197 | 198 | /** 199 | * Consider handlers registered at low priority 200 | */ 201 | #define SIGMUX_HANDLE_SIGNAL_LOW_PRIORITY (1<<1) 202 | 203 | /** 204 | * Invoke the original signal handler 205 | */ 206 | #define SIGMUX_HANDLE_SIGNAL_INVOKE_DEFAULT (1<<2) 207 | 208 | /** 209 | * Signal handler exposed directly; useful for callers that hook other 210 | * signal handler wrappers. SIGNUM, INFO, and CONTEXT are as for 211 | * siginfo(2). FLAGS tells us how to handle the signal and is zero or 212 | * more of the SIGMUX_HANDLE_SIGNAL_* constants. Return what we did 213 | * with the signal. 214 | */ 215 | SIGMUX_EXPORT enum sigmux_action sigmux_handle_signal( 216 | int signum, 217 | siginfo_t* info, 218 | void* context, 219 | int flags); 220 | 221 | 222 | #ifdef __cplusplus 223 | } // extern "C" 224 | #endif 225 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/trampoline.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #include "trampoline.h" 18 | #include "hooks.h" 19 | #include "locks.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "abort_with_reason.h" 33 | 34 | #ifdef ANDROID 35 | #include 36 | #ifndef PR_SET_VMA 37 | #define PR_SET_VMA 0x53564d41 38 | #define PR_SET_VMA_ANON_NAME 0 39 | #endif // PR_SET_VMA 40 | #endif // ANDROID 41 | 42 | namespace facebook { 43 | namespace linker { 44 | 45 | namespace { 46 | 47 | class allocator_block { 48 | public: 49 | allocator_block() 50 | : map_(mmap( 51 | nullptr, 52 | kSize, 53 | PROT_READ | PROT_WRITE | PROT_EXEC, 54 | MAP_PRIVATE | MAP_ANONYMOUS, 55 | -1, /* invalid fd */ 56 | 0)) { 57 | if (map_ == MAP_FAILED) { 58 | throw std::system_error(errno, std::system_category()); 59 | } 60 | 61 | #ifdef ANDROID 62 | if (prctl( 63 | PR_SET_VMA, 64 | PR_SET_VMA_ANON_NAME, 65 | map_, 66 | kSize, 67 | "linker plt trampolines")) { 68 | throw std::system_error( 69 | errno, std::system_category(), "could not set VMA name"); 70 | } 71 | #endif // ANDROID 72 | top_ = reinterpret_cast(map_); 73 | } 74 | 75 | allocator_block(allocator_block const&) = delete; 76 | allocator_block(allocator_block&&) = delete; 77 | 78 | allocator_block& operator=(allocator_block const&) = delete; 79 | allocator_block& operator=(allocator_block&&) = delete; 80 | 81 | size_t remaining() const { 82 | return kSize - (top_ - reinterpret_cast(map_)); 83 | } 84 | 85 | void* allocate(size_t sz) { 86 | if (remaining() < sz) { 87 | throw std::bad_alloc(); 88 | } 89 | void* old = top_; 90 | top_ += sz; 91 | return old; 92 | } 93 | 94 | private: 95 | static constexpr size_t kPageSize = 4096; 96 | static constexpr size_t kPagesPerBlock = 1; 97 | static constexpr size_t kSize = kPageSize * kPagesPerBlock; 98 | 99 | void* const map_; 100 | uint8_t* top_; 101 | }; 102 | 103 | static void* allocate(size_t sz) { 104 | static constexpr size_t kAlignment = 4; 105 | static_assert( 106 | (kAlignment & (kAlignment - 1)) == 0, "kAlignment must be power of 2"); 107 | static std::list blocks_; 108 | static pthread_rwlock_t lock_ = PTHREAD_RWLOCK_INITIALIZER; 109 | 110 | // round sz up to nearest kAlignment-bytes boundary 111 | sz = (sz + (kAlignment - 1)) & ~(kAlignment - 1); 112 | 113 | WriterLock wl(&lock_); 114 | if (blocks_.empty() || blocks_.back().remaining() < sz) { 115 | blocks_.emplace_back(); 116 | } 117 | return blocks_.back().allocate(sz); 118 | } 119 | 120 | struct trampoline_hook_info { 121 | HookId id; 122 | void* const return_address; 123 | trampoline_hook_info* previous; 124 | 125 | std::vector run_list; 126 | }; 127 | 128 | static pthread_key_t get_hook_info_key() { 129 | static pthread_key_t tls_key_ = ({ 130 | pthread_key_t key; 131 | if (pthread_key_create(&key, +[](void* obj) { 132 | auto info = reinterpret_cast(obj); 133 | delete info; 134 | }) != 0) { 135 | log_assert("failed to create trampoline TLS key"); 136 | } 137 | key; 138 | }); 139 | return tls_key_; 140 | } 141 | 142 | static trampoline_hook_info* get_hook_info() { 143 | auto key = get_hook_info_key(); 144 | return reinterpret_cast(pthread_getspecific(key)); 145 | } 146 | 147 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 148 | void* push_hook_stack(HookId hook, void* return_address) { 149 | auto run_list = hooks::get_run_list(hook); 150 | if (run_list.size() == 0) { 151 | // No run list 152 | abortWithReason("Run list for trampoline is empty"); 153 | } 154 | auto* info = new trampoline_hook_info{.id = hook, 155 | .return_address = return_address, 156 | .previous = get_hook_info()}; 157 | info->run_list = std::move(run_list); 158 | 159 | pthread_setspecific(get_hook_info_key(), info); 160 | 161 | // return the last entry 162 | return info->run_list.back(); 163 | } 164 | 165 | void* pop_hook_stack() { 166 | auto info = get_hook_info(); 167 | void* ret = info->return_address; 168 | pthread_setspecific(get_hook_info_key(), info->previous); 169 | delete info; 170 | return ret; 171 | } 172 | #endif 173 | 174 | } // namespace 175 | 176 | namespace trampoline { 177 | 178 | class trampoline { 179 | public: 180 | trampoline(HookId id) 181 | : code_size_( 182 | reinterpret_cast(trampoline_data_pointer()) - 183 | reinterpret_cast(trampoline_template_pointer())), 184 | code_(allocate(code_size_ + trampoline_data_size())) { 185 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 186 | std::memcpy(code_, trampoline_template_pointer(), code_size_); 187 | 188 | auto* data = reinterpret_cast( 189 | reinterpret_cast(code_) + code_size_); 190 | 191 | *data++ = reinterpret_cast(push_hook_stack); 192 | *data++ = reinterpret_cast(pop_hook_stack); 193 | *data++ = reinterpret_cast(id); 194 | #endif 195 | } 196 | 197 | trampoline(trampoline const&) = delete; 198 | trampoline(trampoline&&) = delete; 199 | 200 | trampoline& operator=(trampoline const&) = delete; 201 | trampoline& operator=(trampoline&&) = delete; 202 | 203 | trampoline(void* existing_trampoline) 204 | : code_size_(0), code_(existing_trampoline) {} 205 | 206 | void* code() const { 207 | return reinterpret_cast(code_); 208 | } 209 | 210 | private: 211 | size_t const code_size_; // does NOT include data 212 | void* const code_; 213 | }; 214 | 215 | } // namespace trampoline 216 | 217 | void* create_trampoline(HookId id) { 218 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 219 | static std::list trampolines_; 220 | static pthread_rwlock_t lock_ = PTHREAD_RWLOCK_INITIALIZER; 221 | 222 | WriterLock wl(&lock_); 223 | trampolines_.emplace_back(id); 224 | return trampolines_.back().code(); 225 | #else 226 | throw std::runtime_error("unsupported architecture"); 227 | #endif 228 | } 229 | 230 | } // namespace linker 231 | } // namespace facebook 232 | 233 | extern "C" { 234 | 235 | void* get_previous_from_hook(void* hook) { 236 | auto info = facebook::linker::get_hook_info(); 237 | if (info == nullptr) { 238 | // Not in a hook! 239 | abortWithReason("CALL_PREV call outside of an active hook"); 240 | } 241 | auto iter = std::find(info->run_list.begin(), info->run_list.end(), hook); 242 | if (iter == info->run_list.begin()) { 243 | abortWithReason("CALL_PREV call by original function?!"); 244 | } 245 | if (iter == info->run_list.end()) { 246 | abortWithReason("CALL_PREV call by an unknown hook? How did we get here?"); 247 | } 248 | // Decrement means go towards the original function. 249 | iter--; 250 | return *iter; 251 | } 252 | 253 | } // extern "C" 254 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/trampoline.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | #pragma once 18 | 19 | #include "hooks.h" 20 | #include 21 | 22 | namespace facebook { namespace linker { 23 | 24 | // Testing only 25 | namespace trampoline { 26 | 27 | #if defined(__arm__) || defined(__i386__) 28 | #define LINKER_TRAMPOLINE_SUPPORTED_ARCH 29 | #endif 30 | 31 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 32 | extern "C" { 33 | extern void (*trampoline_template)(); 34 | extern void* trampoline_data; 35 | } 36 | #endif 37 | 38 | inline void* trampoline_template_pointer() { 39 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 40 | return reinterpret_cast(&trampoline_template); 41 | #else 42 | return 0; 43 | #endif 44 | } 45 | 46 | inline void* trampoline_data_pointer() { 47 | #ifdef LINKER_TRAMPOLINE_SUPPORTED_ARCH 48 | return &trampoline_data; 49 | #else 50 | return 0; 51 | #endif 52 | } 53 | 54 | constexpr inline size_t trampoline_data_size() { 55 | return sizeof(void*) * 2 + sizeof(HookId); 56 | } 57 | 58 | } // namespace trampoline 59 | 60 | void* create_trampoline(HookId hook); 61 | 62 | } } // namespace facebook::linker 63 | 64 | #ifdef __cplusplus 65 | extern "C" { 66 | #endif 67 | 68 | void* 69 | get_previous_from_hook(void* hook); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/trampoline_arm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | __attribute__((naked)) 18 | void trampoline_template() { 19 | // save registers we clobber (lr) and this particular hook's chained function 20 | // onto a TLS stack so that we can easily look up who CALL_PREV should jump to, and 21 | // clean up after ourselves register-wise, all while ensuring that we don't alter 22 | // the actual thread stack at all in order to make sure the hook function sees exactly 23 | // the parameters it's supposed to. 24 | // We intentionally clobber ip, it's an inter-procedure scratch register anyway. 25 | asm( 26 | "push { r0 - r3 };" // AAPCS doesn't require preservation of r0 - r3 across calls, so save em temporarily 27 | "ldr r0, .L_hook_id;" // store hook id 28 | "mov r1, lr;" // save lr so we know where to go back to once this is all done 29 | "ldr ip, .L_push_hook_stack;" 30 | "blx ip;" 31 | "mov ip, r0;" // return value saved, that's the hook we'll call 32 | "pop { r0 - r3 };" // bring the hook's original parameters back 33 | 34 | "blx ip;" // switches to ARM or Thumb mode appropriately since target is a register 35 | 36 | // now restore what we saved above 37 | "push { r0 - r3 };" 38 | "ldr ip, .L_pop_hook_stack;" 39 | "blx ip;" 40 | "mov lr, r0;" // return value from pop_hook_stack 41 | "pop { r0 - r3 };" 42 | 43 | "bx lr;" // finally, return to our caller 44 | 45 | "trampoline_data:" 46 | ".global trampoline_data;" 47 | ".L_push_hook_stack:" 48 | ".word 0;" 49 | ".L_pop_hook_stack:" 50 | ".word 0;" 51 | ".L_hook_id:" 52 | ".word 0;" 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/linker/trampoline_x86.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2004-present, Facebook, Inc. 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 | __attribute__((naked)) 18 | void trampoline_template() { 19 | asm( 20 | // 21 | // This code implements the linker PLT hooking trampoline contract. 22 | // Namely: 23 | // hook = push_hook(.L_hook_id, ); 24 | // hook(); 25 | // ret = pop_hook(); 26 | // longjump(ret); // with return from hook() 27 | // 28 | // Further, the trampoline must be entirely position-independent, with 29 | // the relevant function pointers in trampoline_data. 30 | // 31 | // x86 concerns: 32 | // There's no eip-relative addressing, so loading the values from our 33 | // trampoline_data area involves the following pattern: 34 | // 35 | // call pic_trampoline 36 | // pic_trampoline: 37 | // // the above emits a relative `call +0` 38 | // popl %eax 39 | // // %eax is now address of pic_trampoline 40 | // addl (address_of_thing_we_need - pic_trampoline), %eax 41 | // // the calculation above is known at link time and gets substituted 42 | // // for an immediate value. %eax is now address_of_thing_we_need 43 | // 44 | // `call` and `ret` implicitly use the stack. `call` pushes the return 45 | // address and `ret` pops the address to return to. 46 | // Therefore, in order to retain control after we call hook(), 47 | // we must modify the value on the stack. We preserve this value in the 48 | // initial push_hook() call. 49 | // 50 | // Floating-point returns happen via the FPU stack, in particular the 51 | // top register, st0. We perform full 80-bit copies from the stack after 52 | // calling hook() via the relevant FPU instructions. 53 | // 54 | // Linux toolchain specific (gcc expects this, maybe clang too): 55 | // The stack must be 16-byte aligned *at the `call` instruction*. 56 | // This is counter-intuitive because it means that `%esp mod 16 == 12` 57 | // on the callee end due to the implicit push of a return address 58 | // as part of `call`. 59 | // 60 | 61 | // Stack alignment mod 16: 12 bytes 62 | 63 | // Set up a new frame because we'll be using some stack space 64 | // for parameter passing. 65 | "pushl %ebp\n" 66 | "movl %esp, %ebp\n" 67 | // Stack alignment mod 16: 8 bytes 68 | 69 | // PIC code to access push_hook_stack and L_chained. 70 | "call .L_pic_trampoline_1\n" 71 | ".L_pic_trampoline_1:\n" 72 | "popl %ecx\n" // ecx = address of this exact instruction 73 | // Stack alignment mod 16: 8 bytes (call + pop cancel each other) 74 | 75 | // second param for push_hook_stack == return address == the value at the 76 | // top of the stack when we entered the trampoline. 77 | "pushl 4(%ebp)\n" 78 | // Stack alignment mod 16: 4 bytes 79 | 80 | "addl $(.L_hook_id - .L_pic_trampoline_1), %ecx\n" 81 | // ecx = address of .L_hook_id 82 | 83 | "movl (%ecx), %eax\n" // eax = .L_hook_id 84 | "pushl %eax\n " // first argument 85 | // Stack alignment mod 16: 0 bytes 86 | 87 | // Convert the address of .L_hook_id into the address of .L_push_hook_stack 88 | "addl $(.L_push_hook_stack - .L_hook_id), %ecx\n" 89 | "movl (%ecx), %eax\n" 90 | 91 | // Stack alignment mod 16: 0 bytes 92 | "call *%eax\n" 93 | // %eax now contains the hook we need to call 94 | 95 | // We're done with our frame, restore old frame before calling the hook. 96 | "movl %ebp, %esp\n" 97 | "popl %ebp\n" 98 | // Stack alignment mod 16: 12 bytes 99 | 100 | // Remove the return address that's already on the stack, we saved it in 101 | // our `push_hook` call. 102 | "addl $4, %esp\n" 103 | // Stack alignment mod 16: 0 bytes 104 | 105 | // Call hook. 106 | "call *%eax\n" 107 | // Stack alignment mod 16: 0 bytes (call + ret cancel each other) 108 | 109 | // save eax & edx, the return values from the hook func 110 | "pushl %eax\n" 111 | // Stack alignment mod 16: 12 bytes 112 | "pushl %edx\n" 113 | // Stack alignment mod 16: 8 bytes 114 | // save st0 which is used for floating point returns 115 | "subl $10, %esp\n" 116 | // Stack alignment mod 16: 14 bytes 117 | "fstpt (%esp)\n" 118 | 119 | // Set up temporary frame 120 | "pushl %ebp\n" 121 | "movl %esp, %ebp\n" 122 | // Stack alignment mod 16: 10 bytes 123 | 124 | // align stack on a 16-byte boundary 125 | "andl $0xfffffff0, %esp\n" 126 | // Stack alignment mod 16: 0 bytes 127 | 128 | // Another PIC trampoline, this time for pop_hook_stack 129 | "call .L_pic_trampoline_3\n" 130 | ".L_pic_trampoline_3:\n" 131 | "popl %ecx\n" 132 | // Stack alignment mod 16: 0 bytes 133 | 134 | "addl $(.L_pop_hook_stack - .L_pic_trampoline_3), %ecx\n" 135 | "movl (%ecx), %eax\n" 136 | 137 | // Stack alignment mod 16: 0 bytes 138 | // Call pop function. 139 | "call *%eax\n" 140 | // eax is now the address we need to return to, move it somewhere else 141 | "movl %eax, %ecx\n" 142 | 143 | // Tear down temporary frame 144 | "movl %ebp, %esp\n" 145 | "popl %ebp\n" 146 | // Stack alignment mod 16: 14 bytes 147 | 148 | // Restore return result from hook as the result of the trampoline 149 | "fldt (%esp)\n" 150 | "addl $10, %esp\n" 151 | // Stack alignment mod 16: 8 bytes 152 | "popl %edx\n" 153 | // Stack alignment mod 16: 12 bytes 154 | "popl %eax\n" 155 | // Stack alignment mod 16: 0 bytes 156 | 157 | "pushl %ecx\n" // restore return address, it got removed by the hook's ret 158 | // Stack alignment mod 16: 12 bytes 159 | "ret\n" 160 | 161 | "trampoline_data:" 162 | ".global trampoline_data;" 163 | ".L_push_hook_stack:" 164 | ".word 0; .word 0;" 165 | ".L_pop_hook_stack:" 166 | ".word 0; .word 0;" 167 | ".L_hook_id:" 168 | ".word 0; .word 0;" 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/cpp/threadHook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "linker.h" 17 | #include "hooks.h" 18 | #include 19 | 20 | #define LOG_TAG "HOOOOOOOOK" 21 | #define ALOG(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) 22 | 23 | std::atomic thread_hooked; 24 | 25 | static jclass kJavaClass; 26 | static jmethodID kMethodGetStack; 27 | static JavaVM *kJvm; 28 | 29 | 30 | char *jstringToChars(JNIEnv *env, jstring jstr) { 31 | if (jstr == nullptr) { 32 | return nullptr; 33 | } 34 | 35 | jboolean isCopy = JNI_FALSE; 36 | const char *str = env->GetStringUTFChars(jstr, &isCopy); 37 | char *ret = strdup(str); 38 | env->ReleaseStringUTFChars(jstr, str); 39 | return ret; 40 | } 41 | 42 | void printJavaStack() { 43 | JNIEnv* jniEnv = NULL; 44 | // JNIEnv 是绑定线程的,所以这里要重新取 45 | kJvm->GetEnv((void**)&jniEnv, JNI_VERSION_1_6); 46 | jstring java_stack = static_cast(jniEnv->CallStaticObjectMethod(kJavaClass, kMethodGetStack)); 47 | if (NULL == java_stack) { 48 | return; 49 | } 50 | char* stack = jstringToChars(jniEnv, java_stack); 51 | ALOG("stack:%s", stack); 52 | free(stack); 53 | 54 | jniEnv->DeleteLocalRef(java_stack); 55 | } 56 | 57 | 58 | int pthread_create_hook(pthread_t* thread, const pthread_attr_t* attr, 59 | void* (*start_routine) (void *), void* arg) { 60 | printJavaStack(); 61 | return CALL_PREV(pthread_create_hook, thread, attr, *start_routine, arg); 62 | } 63 | 64 | 65 | /** 66 | * plt hook libc 的 pthread_create 方法,第一个参数的含义为排除掉 libc.so 67 | */ 68 | void hookLoadedLibs() { 69 | ALOG("hook_plt_method"); 70 | hook_plt_method("libart.so", "pthread_create", (hook_func) &pthread_create_hook); 71 | } 72 | 73 | 74 | void enableThreadHook() { 75 | if (thread_hooked) { 76 | return; 77 | } 78 | ALOG("enableThreadHook"); 79 | 80 | thread_hooked = true; 81 | if (linker_initialize()) { 82 | throw std::runtime_error("Could not initialize linker library"); 83 | } 84 | hookLoadedLibs(); 85 | } 86 | 87 | extern "C" 88 | JNIEXPORT void JNICALL 89 | Java_com_dodola_thread_ThreadHook_enableThreadHookNative(JNIEnv *env, jclass type) { 90 | 91 | enableThreadHook(); 92 | } 93 | 94 | static bool InitJniEnv(JavaVM *vm) { 95 | kJvm = vm; 96 | JNIEnv* env = NULL; 97 | if (kJvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK){ 98 | ALOG("InitJniEnv GetEnv !JNI_OK"); 99 | return false; 100 | } 101 | kJavaClass = reinterpret_cast(env->NewGlobalRef(env->FindClass("com/dodola/thread/ThreadHook"))); 102 | if (kJavaClass == NULL) { 103 | ALOG("InitJniEnv kJavaClass NULL"); 104 | return false; 105 | } 106 | 107 | kMethodGetStack = env->GetStaticMethodID(kJavaClass, "getStack", "()Ljava/lang/String;"); 108 | if (kMethodGetStack == NULL) { 109 | ALOG("InitJniEnv kMethodGetStack NULL"); 110 | return false; 111 | } 112 | return true; 113 | } 114 | 115 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved){ 116 | ALOG("JNI_OnLoad"); 117 | 118 | 119 | if (!InitJniEnv(vm)) { 120 | return -1; 121 | } 122 | 123 | return JNI_VERSION_1_6; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/java/com/dodola/thread/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.dodola.thread; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | public class MainActivity extends Activity { 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { 15 | @Override 16 | public void onClick(View v) { 17 | ThreadHook.enableThreadHook(); 18 | Toast.makeText(MainActivity.this, "开启成功", Toast.LENGTH_SHORT).show(); 19 | } 20 | }); 21 | 22 | findViewById(R.id.newthread).setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | 26 | new Thread(new Runnable() { 27 | @Override 28 | public void run() { 29 | Log.e("HOOOOOOOOK", "thread name:" + Thread.currentThread().getName()); 30 | Log.e("HOOOOOOOOK", "thread id:" + Thread.currentThread().getId()); 31 | new Thread(new Runnable() { 32 | @Override 33 | public void run() { 34 | Log.e("HOOOOOOOOK", "inner thread name:" + Thread.currentThread().getName()); 35 | Log.e("HOOOOOOOOK", "inner thread id:" + Thread.currentThread().getId()); 36 | 37 | } 38 | }).start(); 39 | } 40 | }).start(); 41 | } 42 | 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/java/com/dodola/thread/MyApplication.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dodola.thread; 3 | 4 | import android.app.Application; 5 | 6 | /** 7 | * Created by dodola on 2018/12/11. 8 | */ 9 | public class MyApplication extends Application { 10 | 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/java/com/dodola/thread/ThreadHook.java: -------------------------------------------------------------------------------- 1 | package com.dodola.thread; 2 | 3 | import java.util.ArrayList; 4 | import java.util.ListIterator; 5 | 6 | public final class ThreadHook { 7 | static { 8 | System.loadLibrary("threadhook"); 9 | } 10 | 11 | private static boolean sHasHook = false; 12 | private static boolean sHookFailed = false; 13 | 14 | 15 | public static String getStack() { 16 | return stackTraceToString(new Throwable().getStackTrace()); 17 | } 18 | 19 | private static String stackTraceToString(final StackTraceElement[] arr) { 20 | if (arr == null) { 21 | return ""; 22 | } 23 | 24 | StringBuffer sb = new StringBuffer(); 25 | 26 | for (StackTraceElement stackTraceElement : arr) { 27 | String className = stackTraceElement.getClassName(); 28 | // remove unused stacks 29 | if (className.contains("java.lang.Thread")) { 30 | continue; 31 | } 32 | 33 | sb.append(stackTraceElement).append('\n'); 34 | } 35 | return sb.toString(); 36 | } 37 | 38 | public static void enableThreadHook() { 39 | if (sHasHook) { 40 | return; 41 | } 42 | sHasHook = true; 43 | enableThreadHookNative(); 44 | 45 | } 46 | 47 | 48 | 49 | 50 | private static native void enableThreadHookNative(); 51 | 52 | 53 | } -------------------------------------------------------------------------------- /ThreadHookSample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /ThreadHookSample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |