├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── main ├── main.c └── pic ├── handler.png ├── how-it-works.gif ├── main_method.png ├── mprotect_access.png └── register_handler_and_set_memory_access.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(mprotect C) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | 6 | add_executable(mprotect main.c) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Richard.Winters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JVM 挂起工作线程的 机制与原理 2 | 3 | ## 首选使用Clang编译 GCC编译会报错 目前未研究清楚 4 | * 内嵌的汇编代码 依赖编译后的栈上临时变量跟RBP指针的相对位置 不保证编译器优化后 内嵌的汇编代码能正常运行 5 | 6 | ## HOW IT WORKS 7 | ![gif](./pic/how-it-works.gif) 8 | 9 | ## 由于编译可能产生问题 可以直接在linux上运行 10 | ```bash 11 | ./main 12 | ``` 13 | 14 | ## 通过mprotect设置内存访问权限 15 | 16 | * mprotect 提供下图几种访问权限设定 17 | 18 | ![mprotect definition](./pic/mprotect_access.png) 19 | 20 | * 使用mprotect 对特定内存区域设置 只读保护 并注册信号量处理器 21 | * 当线程访问下图 buffer指针指向的内存,会产生一个信号量,线程会跳转到handler方法 22 | * handler方法暂时按下不表 23 | 24 | ![register handler and set access](./pic/register_handler_and_set_memory_access.png) 25 | 26 | ## main函数流程 27 | 28 | * 保存几个寄存器 rsp rbp rip(pc寄存器) 并将其写入全局变量,以便handler恢复上下文 29 | * 第一次访问p指针(也是buffer指针) 由于权限的问题,会跳转到handler方法 30 | ![main method](./pic/main_method.png) 31 | 32 | 33 | ## handler流程 34 | 35 | * 处理信号量,重新设置buffer指向的区域 为可读可写 36 | * 恢复上下文 jmp到之前 保护现场的汇编代码区域 37 | ![handler](./pic/handler.png) 38 | 39 | 40 | # 技术总结 41 | 42 | * mprotect调用 通常可以用来控制线程的运行流程,以便我们做一些额外的工作,且工作线程并不会感知到我们做的工作,例如JVM垃圾回收 43 | * 在汇编层面上要了解 寄存器跟上下文以及各种调用约定,以便我们改变业务线程原本的工作流程后恢复现状 44 | * golang 的协程 其实也是采用类似的手法 来实现的 45 | -------------------------------------------------------------------------------- /main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/main -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define handle_error(msg) \ 10 | do { perror(msg); exit(EXIT_FAILURE); } while (0) 11 | 12 | char *buffer; 13 | int pagesize; 14 | 15 | static void 16 | handler(int sig, siginfo_t *si, void *unused); 17 | 18 | void allocate_memory(); 19 | 20 | uint64 pc; 21 | uint64 rsp; 22 | uint64 rbp; 23 | 24 | 25 | /** 26 | * jvm safe-point working mechanism, 27 | * this code show how jvm suspend working-thread. 28 | * @param argc 29 | * @param argv 30 | * @return 31 | */ 32 | int main(int argc, char *argv[]) { 33 | /* 34 | * allocate memory and set memory access READ 35 | */ 36 | allocate_memory(); 37 | char *p = buffer; 38 | 39 | /* 40 | * save context like register pc rsp rbp, 41 | * we manually save those registers to c global variables, 42 | * jvm save those registers for recovery they, we simulate JVM's behavior too 43 | */ 44 | uint64 local_pc = 0; 45 | uint64 local_rsp = 0; 46 | uint64 local_rbp = 0; 47 | __asm__(".intel_syntax;" 48 | "lea rax, [rip];" 49 | "mov [rbp-0x20], rax;" 50 | "mov [rbp-0x28], rsp;" 51 | "mov [rbp-0x30], rbp;" 52 | ); 53 | pc = local_pc; 54 | rsp = local_rsp; 55 | rbp = local_rbp; 56 | 57 | 58 | for (int i = 0; i < 4; i++) { 59 | /* 60 | * we access the pointer p, current thread will handle signal 61 | * jvm will do 62 | */ 63 | *(p) = 'a'; 64 | p++; 65 | } 66 | *(p) = '\0'; 67 | printf("p = %s\n", buffer); 68 | //printf("%x",local_pc); 69 | 70 | 71 | /* 72 | * if we didn't restore those registers, 73 | * it should not happen. 74 | */ 75 | printf("Loop completed\n"); 76 | exit(EXIT_SUCCESS); 77 | } 78 | 79 | 80 | void allocate_memory() { 81 | 82 | /* 83 | * register signal handle 84 | */ 85 | struct sigaction sa; 86 | 87 | sa.sa_flags = SA_SIGINFO; 88 | sigemptyset(&sa.sa_mask); 89 | sa.sa_sigaction = handler; 90 | if (sigaction(SIGSEGV, &sa, NULL) == -1) 91 | handle_error("sigaction"); 92 | 93 | pagesize = sysconf(_SC_PAGE_SIZE); 94 | if (pagesize == -1) 95 | handle_error("sysconf"); 96 | 97 | /* 98 | * allocate a buffer aligned on a page boundary, 99 | * initial protection is PROT_READ | PROT_WRITE 100 | */ 101 | 102 | buffer = memalign(pagesize, 4 * pagesize); 103 | if (buffer == NULL) 104 | handle_error("memalign"); 105 | 106 | printf("Start of region: 0x%lx\n", (long) buffer); 107 | 108 | //set allocated memory access as readonly 109 | if (mprotect(buffer, pagesize * 4, 110 | PROT_READ) == -1) 111 | handle_error("mprotect"); 112 | } 113 | 114 | static void handler(int sig, siginfo_t *si, void *unused) { 115 | /* 116 | * if memory access signal happen, this handler will be called, 117 | */ 118 | printf("Got SIGSEGV at address: 0x%lx\n", 119 | (long) si->si_addr); 120 | 121 | //allow to write , otherwise operation system send signal again. 122 | if (mprotect(buffer, pagesize * 4, 123 | PROT_READ | PROT_WRITE) == -1) 124 | handle_error("mprotect"); 125 | 126 | /* 127 | * jvm will suspend current thread, wait STW end. 128 | * other GC thread do GC() 129 | */ 130 | 131 | //recovery those register 132 | //jmp to previous code , make current thread work 133 | uint64 local_rsp = rsp; 134 | uint64 local_pc = pc; 135 | uint64 local_rbp = rbp; 136 | __asm__(".intel_syntax;" 137 | "mov rsp,[rbp-0x20];" 138 | "mov rax,[rbp-0x28];" 139 | "mov rbp,[rbp-0x30];" 140 | "jmp rax;" 141 | ); 142 | 143 | //never happen 144 | printf("rsp:%x", local_rsp); 145 | printf("pc:%x", local_pc); 146 | printf("rbp:%x", local_rbp); 147 | exit(EXIT_FAILURE); 148 | } -------------------------------------------------------------------------------- /pic/handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/pic/handler.png -------------------------------------------------------------------------------- /pic/how-it-works.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/pic/how-it-works.gif -------------------------------------------------------------------------------- /pic/main_method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/pic/main_method.png -------------------------------------------------------------------------------- /pic/mprotect_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/pic/mprotect_access.png -------------------------------------------------------------------------------- /pic/register_handler_and_set_memory_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fqdeng/jvm-suspend-working-thread-mechanism/f5bd44c239b90d083b20f5b8bff2ac7386eb3bd7/pic/register_handler_and_set_memory_access.png --------------------------------------------------------------------------------