├── .gitignore ├── LICENSE ├── README.md ├── java ├── LambdaTest.java ├── Simple.java ├── Switch.java ├── Test.java ├── Varying.java └── agent.conf ├── jvmti ├── Cargo.toml ├── README.md ├── src │ ├── agent.rs │ ├── bytecode │ │ ├── classfile.rs │ │ ├── io │ │ │ ├── mod.rs │ │ │ ├── reader.rs │ │ │ └── writer.rs │ │ ├── mod.rs │ │ └── printer.rs │ ├── capabilities.rs │ ├── class.rs │ ├── config.rs │ ├── context.rs │ ├── emulator.rs │ ├── environment │ │ ├── jni.rs │ │ ├── jvm.rs │ │ ├── jvmti.rs │ │ └── mod.rs │ ├── error.rs │ ├── event.rs │ ├── event_handler.rs │ ├── instrumentation │ │ ├── asm │ │ │ ├── mod.rs │ │ │ └── transformer.rs │ │ └── mod.rs │ ├── lib.rs │ ├── main.rs │ ├── mem.rs │ ├── method.rs │ ├── native │ │ └── mod.rs │ ├── options.rs │ ├── runtime.rs │ ├── thread.rs │ ├── util.rs │ └── version.rs └── tests │ ├── agent.rs │ ├── bc │ ├── collections.rs │ ├── constant.rs │ ├── mod.rs │ └── stream.rs │ ├── bytecode │ └── mod.rs │ ├── capabilities.rs │ ├── class.rs │ ├── context.rs │ ├── emulator.rs │ ├── environment │ ├── jvm.rs │ ├── jvmti.rs │ └── mod.rs │ ├── event.rs │ ├── instrumentation │ └── mod.rs │ ├── lib.rs │ ├── options.rs │ ├── util.rs │ └── version.rs ├── regression-test.sh ├── sample ├── Cargo.toml ├── README.md └── src │ ├── agent.rs │ └── lib.rs └── testwatch.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.log 4 | *.dylib 5 | *.a 6 | *.swp 7 | *.class 8 | Makefile 9 | CMake* 10 | old 11 | .vscode 12 | .idea 13 | *.iml 14 | *.out 15 | test-data* 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jvmti-rust 2 | ========== 3 | 4 | An extensible, safe native JVM agent implemented in pure Rust. 5 | 6 | ## A word of warning 7 | 8 | This project is far from being complete or usable to say the least and contains 9 | a healthy dose of proof-of-concept code that is guaranteed to either work or not. 10 | 11 | ## Abstract 12 | 13 | Rust JVMTI is intended to become a slim JVM 14 | application performance management (APM) tool leveraging both safe access to native 15 | JVM functionality via Rust and byte code instrumentation using Java code. 16 | 17 | ## Already implemented (probably poorly) 18 | 19 | * Ability to connect to a JVM as a native agent library 20 | * Read and parse loaded class files 21 | * Generate byte code from loaded or created class files 22 | * Gathering and displaying statistics about method class, class loading and synchronization times 23 | * Read basic command line configuration 24 | * Basic JVM emulator for implementing unit tests without the need for an actual JVM 25 | 26 | ## Usage 27 | 28 | Please see [the example](./sample/README.md). 29 | -------------------------------------------------------------------------------- /java/LambdaTest.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.ArrayList; 3 | 4 | 5 | public class LambdaTest { 6 | 7 | public static void main(final String[] args) { 8 | final LambdaTest test = new LambdaTest(); 9 | 10 | test.doStreamProcessing(); 11 | } 12 | 13 | public void doStreamProcessing() { 14 | final List list = new ArrayList(); 15 | 16 | 17 | list.add("Apple"); 18 | list.add("Pear"); 19 | list.add("Pineapple"); 20 | list.add("Blueberry"); 21 | 22 | list.stream().filter(x -> x.toLowerCase().startsWith("A")).forEach(System.out::println); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java/Simple.java: -------------------------------------------------------------------------------- 1 | public class Simple { 2 | 3 | private static long staticVariable = 13; 4 | 5 | public static void main(final String[] args) throws Exception { 6 | int a = 394; 7 | long b = 14l; 8 | System.out.println(a); 9 | System.out.println(b); 10 | System.out.println(staticVariable); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/Switch.java: -------------------------------------------------------------------------------- 1 | public class Switch { 2 | 3 | 4 | public static void main(final String[] args) { 5 | 6 | int a = 0; 7 | 8 | switch (a) { 9 | case 0: 10 | a = 1; 11 | break; 12 | case 100: 13 | a = 2; 14 | break; 15 | case 2: 16 | a = 3; 17 | break; 18 | case 300: 19 | a = 4; 20 | break; 21 | default: 22 | a = 5; 23 | break; 24 | } 25 | 26 | int b = 1; 27 | 28 | switch (a) { 29 | case 0: 30 | b = 3; 31 | break; 32 | case 100: 33 | b = 4; 34 | break; 35 | case 200: 36 | b = 5; 37 | break; 38 | case 3: 39 | b = 6; 40 | break; 41 | default: 42 | b = 7; 43 | break; 44 | } 45 | 46 | 47 | int c = 2; 48 | 49 | switch (b) { 50 | case 0: 51 | c = 3; 52 | break; 53 | case 100: 54 | c = 4; 55 | break; 56 | case 200: 57 | c = 5; 58 | break; 59 | case 3: 60 | c = 6; 61 | break; 62 | default: 63 | c = 7; 64 | break; 65 | } 66 | 67 | 68 | 69 | int d = 3; 70 | 71 | switch (c) { 72 | case 0: 73 | d = 3; 74 | break; 75 | case 1: 76 | d = 4; 77 | break; 78 | case 200: 79 | d = 5; 80 | break; 81 | case 300: 82 | d = 6; 83 | break; 84 | default: 85 | d = 7; 86 | break; 87 | } 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /java/Test.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.ArrayList; 3 | import java.util.function.Consumer; 4 | 5 | public class Test extends Simple implements Consumer { 6 | 7 | public static void main(final String[] args) throws Exception { 8 | final Test test = new Test(); 9 | 10 | // test.printSomething(); 11 | // test.sleepALittle(); 12 | test.spawnThreads(); 13 | // test.throwChecked(); 14 | // test.throwUnchecked(); 15 | } 16 | 17 | 18 | @Deprecated 19 | public void printSomething() { 20 | System.out.println("Lofasz"); 21 | } 22 | 23 | public void throwChecked() { 24 | try { 25 | throw new ClassNotFoundException(); 26 | } catch (final ClassNotFoundException e) { 27 | System.out.println("Caught"); 28 | } 29 | } 30 | 31 | public void throwUnchecked() throws Exception { 32 | throw new NullPointerException("Application error"); 33 | } 34 | 35 | public void sleepALittle() throws Exception { 36 | Thread.sleep(500); 37 | System.out.println("Slept enough"); 38 | } 39 | 40 | public void spawnThreads() throws Exception { 41 | System.out.println("---- Spawn Threads"); 42 | final Object object = new Object(); 43 | 44 | final Runnable run = new Runnable() { 45 | 46 | public void run() { 47 | // System.out.println(" [" + Thread.currentThread().getName() + "] Waiting for the monitor"); 48 | 49 | synchronized(object) { 50 | System.out.println(" [" + Thread.currentThread().getName() + "] Owning the monitor, before sleep"); 51 | try { 52 | Thread.sleep(2000); 53 | } catch (Exception e) { 54 | } 55 | // System.out.println(" [" + Thread.currentThread().getName() + "] Owning the monitor, after sleep"); 56 | } 57 | 58 | try { 59 | Thread.sleep(2300); 60 | } catch (Exception e) { 61 | } 62 | // System.out.println(" [" + Thread.currentThread().getName() + "] After acquiring the monitor"); 63 | } 64 | }; 65 | 66 | final List threads = new ArrayList(); 67 | 68 | for (int i = 0; i < 4; i++) { 69 | final Thread thread = new Thread(run); 70 | threads.add(thread); 71 | System.out.println("Starting thread: " + thread.getName()); 72 | } 73 | 74 | for (final Thread thread : threads) { 75 | thread.start(); 76 | } 77 | 78 | for (final Thread thread : threads) { 79 | thread.join(); 80 | } 81 | } 82 | 83 | public void accept(String string) { 84 | System.out.println(string); 85 | } 86 | 87 | public void testSwitch() { 88 | int i = 3; 89 | 90 | switch (i) { 91 | case 1: 92 | case 2: 93 | System.out.println(i); 94 | break; 95 | case 3: 96 | System.out.println("Found it"); 97 | break; 98 | default: 99 | break; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /java/Varying.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | 3 | public class Varying { 4 | 5 | public int function() { 6 | try { 7 | int a = 2; 8 | a++; 9 | 10 | } finally { 11 | int a = 4; 12 | 13 | a ++; 14 | } 15 | 16 | return 0; 17 | } 18 | 19 | public int catchAll() { 20 | try { 21 | return mayThrow(); 22 | } catch (IOException ex) { 23 | int a = 0; 24 | a++; 25 | } 26 | return 0; 27 | } 28 | 29 | public int mayThrow() throws IOException { 30 | 31 | if (getRandom() > 3) { 32 | throw new IOException("Hello"); 33 | } 34 | 35 | 36 | return 0; 37 | } 38 | 39 | public int getRandom() { 40 | return 4; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/agent.conf: -------------------------------------------------------------------------------- 1 | agent_name = "Test" 2 | entry_points = [ 3 | ".Hello.main", 4 | "java.io.OutputStream.flush" 5 | ] 6 | active_classes = [ 7 | "Hello", 8 | "java/util/Queue", 9 | "java/net/URLConnection" 10 | ] 11 | -------------------------------------------------------------------------------- /jvmti/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jvmti" 3 | version = "0.5.0" 4 | authors = [ 5 | "Alex Pecsi ", 6 | "Robert Lu " 7 | ] 8 | license = "Apache-2.0" 9 | description = "JVM TI Rust binding" 10 | keywords = [ "java", "jvm", "jvmti", "debugger" ] 11 | documentation = "https://github.com/robberphex/jvmti-rust" 12 | repository = "https://github.com/robberphex/jvmti-rust.git" 13 | 14 | [lib] 15 | crate_type = [ "cdylib", "rlib" ] 16 | 17 | [dependencies] 18 | libc = "0.2.*" 19 | time = "0.1.*" 20 | lazy_static = "0.2.*" 21 | toml = "0.4.*" 22 | serde = "1.0.*" 23 | serde_derive = "1.0.*" 24 | -------------------------------------------------------------------------------- /jvmti/README.md: -------------------------------------------------------------------------------- 1 | jvmti-rust 2 | ========== 3 | 4 | An extensible, safe native JVM agent implemented in pure Rust. 5 | 6 | ## A word of warning 7 | 8 | This project is far from being complete or usable to say the least and contains 9 | a healthy dose of proof-of-concept code that is guaranteed to either work or not. 10 | 11 | ## Abstract 12 | 13 | Rust JVMTI is intended to become a slim JVM 14 | application performance management (APM) tool leveraging both safe access to native 15 | JVM functionality via Rust and byte code instrumentation using Java code. 16 | 17 | ## Already implemented (probably poorly) 18 | 19 | * Ability to connect to a JVM as a native agent library 20 | * Read and parse loaded class files 21 | * Generate byte code from loaded or created class files 22 | * Gathering and displaying statistics about method class, class loading and synchronization times 23 | * Read basic command line configuration 24 | * Basic JVM emulator for implementing unit tests without the need for an actual JVM 25 | 26 | ## Usage 27 | 28 | Please see [the example](../sample/README.md). 29 | -------------------------------------------------------------------------------- /jvmti/src/agent.rs: -------------------------------------------------------------------------------- 1 | use super::capabilities::Capabilities; 2 | use super::config::Config; 3 | use super::environment::jvm::{JVMF, JVMAgent}; 4 | use super::environment::jvmti::JVMTI; 5 | use super::event::*; 6 | use super::error::*; 7 | use super::native::JavaVMPtr; 8 | use super::options::Options; 9 | use super::version::VersionNumber; 10 | 11 | pub struct Agent { 12 | jvm: Box, 13 | pub capabilities: Capabilities, 14 | callbacks: EventCallbacks, 15 | environment: Box 16 | } 17 | 18 | impl Agent { 19 | 20 | /// Create a newly initialised but blank JVM `Agent` instance using the provided Java VM pointer. 21 | pub fn new(vm: JavaVMPtr) -> Agent { 22 | let jvm_agent = JVMAgent::new(vm); 23 | 24 | match jvm_agent.get_environment() { 25 | Ok(environment) => Agent { 26 | jvm: Box::new(jvm_agent), 27 | capabilities: Capabilities::new(), 28 | callbacks: EventCallbacks::new(), 29 | environment: environment 30 | }, 31 | Err(err) => panic!("FATAL: Could not get JVMTI environment: {}", translate_error(&err)) 32 | } 33 | 34 | } 35 | 36 | /// Create a newly initialised but blank JVM `Agent` instance using the provided JVM agent. 37 | pub fn new_from(jvm: Box) -> Agent { 38 | match jvm.get_environment() { 39 | Ok(environment) => Agent { 40 | jvm: jvm, 41 | capabilities: Capabilities::new(), 42 | callbacks: EventCallbacks::new(), 43 | environment: environment 44 | }, 45 | Err(err) => panic!("FATAL: Could not get JVMTI environment: {}", translate_error(&err)) 46 | } 47 | } 48 | 49 | /// Return JVMTI version being used 50 | pub fn get_version(&self) -> VersionNumber { 51 | self.environment.get_version_number() 52 | } 53 | 54 | pub fn shutdown(&self) { 55 | // TODO implement this method 56 | } 57 | 58 | pub fn destroy(&self) -> Result<(), NativeError> { 59 | self.jvm.destroy() 60 | } 61 | 62 | pub fn update(&mut self) { 63 | match self.environment.add_capabilities(&self.capabilities) { 64 | Ok(caps) => { 65 | println!("Current capabilities: {}", caps); 66 | self.capabilities = caps; 67 | 68 | match self.environment.set_event_callbacks(self.callbacks.clone()) { 69 | None => { 70 | self.environment.set_event_notification_mode(VMEvent::VMObjectAlloc, self.callbacks.vm_object_alloc.is_some()); 71 | self.environment.set_event_notification_mode(VMEvent::VMObjectFree, self.callbacks.vm_object_free.is_some()); 72 | self.environment.set_event_notification_mode(VMEvent::VMStart, self.callbacks.vm_start.is_some()); 73 | self.environment.set_event_notification_mode(VMEvent::VMInit, self.callbacks.vm_init.is_some()); 74 | self.environment.set_event_notification_mode(VMEvent::VMDeath, self.callbacks.vm_death.is_some()); 75 | self.environment.set_event_notification_mode(VMEvent::MethodEntry, self.callbacks.method_entry.is_some()); 76 | self.environment.set_event_notification_mode(VMEvent::MethodExit, self.callbacks.method_exit.is_some()); 77 | self.environment.set_event_notification_mode(VMEvent::ThreadStart, self.callbacks.thread_start.is_some()); 78 | self.environment.set_event_notification_mode(VMEvent::ThreadEnd, self.callbacks.thread_end.is_some()); 79 | self.environment.set_event_notification_mode(VMEvent::Exception, self.callbacks.exception.is_some()); 80 | self.environment.set_event_notification_mode(VMEvent::ExceptionCatch, self.callbacks.exception_catch.is_some()); 81 | self.environment.set_event_notification_mode(VMEvent::MonitorWait, self.callbacks.monitor_wait.is_some()); 82 | self.environment.set_event_notification_mode(VMEvent::MonitorWaited, self.callbacks.monitor_waited.is_some()); 83 | self.environment.set_event_notification_mode(VMEvent::MonitorContendedEnter, self.callbacks.monitor_contended_enter.is_some()); 84 | self.environment.set_event_notification_mode(VMEvent::MonitorContendedEntered, self.callbacks.monitor_contended_entered.is_some()); 85 | self.environment.set_event_notification_mode(VMEvent::FieldAccess, self.callbacks.field_access.is_some()); 86 | self.environment.set_event_notification_mode(VMEvent::FieldModification, self.callbacks.field_modification.is_some()); 87 | self.environment.set_event_notification_mode(VMEvent::GarbageCollectionStart, self.callbacks.garbage_collection_start.is_some()); 88 | self.environment.set_event_notification_mode(VMEvent::GarbageCollectionFinish, self.callbacks.garbage_collection_finish.is_some()); 89 | self.environment.set_event_notification_mode(VMEvent::ClassFileLoadHook, self.callbacks.class_file_load_hook.is_some()); 90 | }, 91 | Some(error) => println!("Couldn't register callbacks: {}", translate_error(&error)) 92 | } 93 | }, 94 | Err(error) => println!("Couldn't update capabilities: {}", translate_error(&error)) 95 | } 96 | } 97 | 98 | pub fn on_method_entry(&mut self, handler: Option) { 99 | self.callbacks.method_entry = handler; 100 | self.capabilities.can_generate_method_entry_events = handler.is_some(); 101 | } 102 | 103 | pub fn on_method_exit(&mut self, handler: Option) { 104 | self.callbacks.method_exit = handler; 105 | self.capabilities.can_generate_method_exit_events = handler.is_some(); 106 | } 107 | 108 | pub fn on_vm_init(&mut self, handler: Option) { 109 | self.callbacks.vm_init = handler; 110 | } 111 | 112 | pub fn on_vm_death(&mut self, handler: Option) { 113 | self.callbacks.vm_death = handler; 114 | } 115 | 116 | pub fn on_vm_start(&mut self, handler: Option) { 117 | self.callbacks.vm_start = handler; 118 | } 119 | 120 | pub fn on_vm_object_alloc(&mut self, handler: Option) { 121 | self.callbacks.vm_object_alloc = handler; 122 | self.capabilities.can_generate_vm_object_alloc_events = handler.is_some(); 123 | } 124 | 125 | pub fn on_vm_object_free(&mut self, handler: Option) { 126 | self.callbacks.vm_object_free = handler; 127 | self.capabilities.can_generate_object_free_events = handler.is_some(); 128 | } 129 | 130 | pub fn on_thread_start(&mut self, handler: Option) { 131 | self.callbacks.thread_start = handler; 132 | } 133 | 134 | pub fn on_thread_end(&mut self, handler: Option) { 135 | self.callbacks.thread_end = handler; 136 | } 137 | 138 | pub fn on_exception(&mut self, handler: Option) { 139 | self.callbacks.exception = handler; 140 | self.capabilities.can_generate_exception_events = handler.or(self.callbacks.exception_catch).is_some(); 141 | } 142 | 143 | pub fn on_exception_catch(&mut self, handler: Option) { 144 | self.callbacks.exception_catch = handler; 145 | self.capabilities.can_generate_exception_events = handler.or(self.callbacks.exception).is_some(); 146 | } 147 | 148 | pub fn on_monitor_wait(&mut self, handler: Option) { 149 | self.callbacks.monitor_wait = handler; 150 | 151 | let has_some = handler 152 | .or(self.callbacks.monitor_waited) 153 | .or(self.callbacks.monitor_contended_enter) 154 | .or(self.callbacks.monitor_contended_entered).is_some(); 155 | 156 | self.capabilities.can_generate_monitor_events = has_some; 157 | } 158 | 159 | pub fn on_monitor_waited(&mut self, handler: Option) { 160 | self.callbacks.monitor_waited = handler; 161 | 162 | let has_some = handler 163 | .or(self.callbacks.monitor_wait) 164 | .or(self.callbacks.monitor_contended_enter) 165 | .or(self.callbacks.monitor_contended_entered).is_some(); 166 | 167 | self.capabilities.can_generate_monitor_events = has_some; 168 | } 169 | 170 | pub fn on_monitor_contended_enter(&mut self, handler: Option) { 171 | self.callbacks.monitor_contended_enter = handler; 172 | 173 | let has_some = handler 174 | .or(self.callbacks.monitor_wait) 175 | .or(self.callbacks.monitor_waited) 176 | .or(self.callbacks.monitor_contended_entered).is_some(); 177 | 178 | self.capabilities.can_generate_monitor_events = has_some; 179 | } 180 | 181 | pub fn on_monitor_contended_entered(&mut self, handler: Option) { 182 | self.callbacks.monitor_contended_entered = handler; 183 | 184 | let has_some = handler 185 | .or(self.callbacks.monitor_wait) 186 | .or(self.callbacks.monitor_waited) 187 | .or(self.callbacks.monitor_contended_enter).is_some(); 188 | 189 | self.capabilities.can_generate_monitor_events = has_some; 190 | } 191 | 192 | pub fn on_field_access(&mut self, handler: Option) { 193 | self.callbacks.field_access = handler; 194 | self.capabilities.can_generate_field_access_events = handler.is_some(); 195 | } 196 | 197 | pub fn on_field_modification(&mut self, handler: Option) { 198 | self.callbacks.field_modification = handler; 199 | self.capabilities.can_generate_field_modification_events = handler.is_some(); 200 | } 201 | 202 | pub fn on_garbage_collection_start(&mut self, handler: Option) { 203 | self.callbacks.garbage_collection_start = handler; 204 | self.capabilities.can_generate_garbage_collection_events = handler.or(self.callbacks.garbage_collection_finish).is_some(); 205 | } 206 | 207 | pub fn on_garbage_collection_finish(&mut self, handler: Option) { 208 | self.callbacks.garbage_collection_finish = handler; 209 | self.capabilities.can_generate_garbage_collection_events = handler.or(self.callbacks.garbage_collection_start).is_some(); 210 | } 211 | 212 | pub fn on_class_file_load(&mut self, handler: Option) { 213 | self.callbacks.class_file_load_hook = handler; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /jvmti/src/bytecode/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::reader::*; 2 | pub use self::writer::*; 3 | 4 | pub mod reader; 5 | pub mod writer; 6 | -------------------------------------------------------------------------------- /jvmti/src/bytecode/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::classfile::*; 2 | pub use self::io::*; 3 | 4 | pub mod classfile; 5 | pub mod io; 6 | pub mod printer; 7 | 8 | /* 9 | 10 | // -- 11 | 12 | pub enum BytecodeError { 13 | NotImplemented 14 | } 15 | 16 | pub type BytecodeResult = Result; 17 | 18 | // -- Stream 19 | 20 | pub struct BytecodeReader<'a> { 21 | source: &'a Read, 22 | } 23 | 24 | impl <'a> BytecodeReader<'a> { 25 | pub fn new(source: &'a Read) -> BytecodeReader { 26 | BytecodeReader { source: source } 27 | } 28 | 29 | pub fn read_u16(&self) -> Option { 30 | None 31 | } 32 | } 33 | 34 | pub struct BytecodeWriter<'a> { 35 | target: &'a mut Write 36 | } 37 | 38 | impl<'a> BytecodeWriter<'a> { 39 | /// Create a new bytecode writer that outputs the generated bytecode to the specified target 40 | pub fn new(target: &'a mut Write) -> BytecodeWriter { 41 | BytecodeWriter { target: target } 42 | } 43 | 44 | pub fn write_bytecode(&mut self, bytecode: &T) -> Result where T: Bytecode { 45 | bytecode.write_bytecode(self) 46 | } 47 | 48 | pub fn write_u64(&mut self, value: u64) -> Result { 49 | self.target.write(&*vec![ 50 | ((value & 0xFF << 56) >> 56) as u8, 51 | ((value & 0xFF << 48) >> 48) as u8, 52 | ((value & 0xFF << 40) >> 40) as u8, 53 | ((value & 0xFF << 32) >> 32) as u8, 54 | ((value & 0xFF << 24) >> 24) as u8, 55 | ((value & 0xFF << 16) >> 16) as u8, 56 | ((value & 0xFF << 8) >> 8) as u8, 57 | (value & 0xFF) as u8 58 | ]) 59 | } 60 | 61 | pub fn write_u32(&mut self, value: u32) -> Result { 62 | self.target.write(&*vec![ 63 | ((value & 0xFF << 24) >> 24) as u8, 64 | ((value & 0xFF << 16) >> 16) as u8, 65 | ((value & 0xFF << 8) >> 8) as u8, 66 | (value & 0xFF) as u8 67 | ]) 68 | } 69 | 70 | pub fn write_u16(&mut self, value: u16) -> Result { 71 | self.target.write(&*vec![ ((value & 0xFF00) >> 8) as u8, (value & 0xFF) as u8 ]) 72 | } 73 | 74 | pub fn write_u8(&mut self, value: u8) -> Result { 75 | self.target.write(&*vec![value]) 76 | } 77 | 78 | pub fn write_n(&mut self, value: Vec) -> Result { 79 | value.iter().map(|v| self.write_u8(*v)).fold(Ok(0), |acc, x| { 80 | match (acc, x) { 81 | (Ok(i), Ok(s)) => Ok(i + s), 82 | (e@Err(_), _) => e, 83 | (_, Err(err)) => Err(err) 84 | } 85 | }) 86 | } 87 | } 88 | 89 | pub trait Bytecode: Sized { 90 | fn read_bytecode(reader: &BytecodeReader) -> Result; 91 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result; 92 | } 93 | 94 | // -- Constants -- 95 | 96 | pub struct ConstantPool { 97 | } 98 | 99 | impl ConstantPool { 100 | } 101 | 102 | pub struct ConstantPoolIndex { 103 | pub index: u16 104 | } 105 | 106 | impl Bytecode for ConstantPoolIndex { 107 | 108 | fn read_bytecode(reader: &BytecodeReader) -> Result { 109 | match reader.read_u16() { 110 | Some(index) => Ok(ConstantPoolIndex { index: index }), 111 | None => Err(BytecodeError::NotImplemented) 112 | } 113 | } 114 | 115 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 116 | writer.write_u16(self.index) 117 | } 118 | } 119 | 120 | // -- Attributes -- 121 | 122 | pub enum Attribute { 123 | ConstantValue(ConstantValue), 124 | Code(Code) 125 | } 126 | 127 | impl Bytecode for Attribute { 128 | 129 | fn read_bytecode(reader: &BytecodeReader) -> Result { 130 | Err(BytecodeError::NotImplemented) 131 | } 132 | 133 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 134 | match self { 135 | &Attribute::ConstantValue(ref val) => val.write_bytecode(writer), 136 | &Attribute::Code(ref val) => val.write_bytecode(writer) 137 | } 138 | } 139 | } 140 | 141 | pub struct ConstantValue { 142 | pub index: ConstantPoolIndex 143 | } 144 | 145 | impl Bytecode for ConstantValue { 146 | fn read_bytecode(bytes: &BytecodeReader) -> Result { 147 | Err(BytecodeError::NotImplemented) 148 | } 149 | 150 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 151 | Ok(0) 152 | } 153 | } 154 | 155 | pub struct Code { 156 | pub max_stack: u16, 157 | pub max_locals: u16, 158 | pub code: Vec, 159 | pub exception_table: Vec, 160 | pub attributes: Vec 161 | } 162 | 163 | impl Bytecode for Code { 164 | fn read_bytecode(bytes: &BytecodeReader) -> Result { 165 | Err(BytecodeError::NotImplemented) 166 | } 167 | 168 | fn write_bytecode(&self, writer: &mut BytecodeWriter) -> Result { 169 | Ok(0) 170 | } 171 | } 172 | 173 | pub struct ExceptionHandler { 174 | pub start_pc: u16, 175 | pub end_pc: u16, 176 | pub handler_pc: u16, 177 | pub catch_type: u16 178 | } 179 | 180 | pub struct StackMapTable { 181 | pub entries: Vec 182 | } 183 | 184 | pub enum StackMapFrame { 185 | // TODO incomplete 186 | } 187 | 188 | pub struct Exceptions { 189 | pub exception_index_table: Vec 190 | } 191 | 192 | */ 193 | -------------------------------------------------------------------------------- /jvmti/src/bytecode/printer.rs: -------------------------------------------------------------------------------- 1 | use super::classfile::*; 2 | 3 | pub struct ClassfilePrinter; 4 | 5 | impl ClassfilePrinter { 6 | pub fn render_lines(classfile: &Classfile) -> Vec { 7 | let mut lines = vec![]; 8 | 9 | lines.push(format!("class {}", ClassfilePrinter::resolve_class(&classfile.this_class, &classfile.constant_pool))); 10 | lines.push(format!("Minor version: {}", classfile.version.minor_version)); 11 | lines.push(format!("Major version: {}", classfile.version.major_version)); 12 | lines.push(format!("Flags: {}", ClassfilePrinter::render_flags(&classfile.access_flags))); 13 | lines.push(format!("Constant pool:")); 14 | 15 | let mut i: i32 = -1; 16 | 17 | let _: Vec<()> = ClassfilePrinter::render_constant_pool(&classfile.constant_pool).iter() 18 | .map(|constant| { 19 | i = i + 1; 20 | format!("{:>5} = {}", format!("#{}", i), constant) 21 | }) 22 | .map(|line| lines.push(line)) 23 | .collect(); 24 | 25 | let _: Vec<()> = ClassfilePrinter::render_methods(&classfile).iter() 26 | .map(|method| { 27 | format!("{}", method) 28 | }) 29 | .map(|line| lines.push(line)) 30 | .collect(); 31 | 32 | lines 33 | } 34 | 35 | pub fn render_flags(flags: &AccessFlags) -> String { 36 | let mut flag_vec = vec![]; 37 | 38 | if flags.has_flag(ClassAccessFlags::Public as u16) { 39 | flag_vec.push("ACC_PUBLIC "); 40 | } 41 | 42 | if flags.has_flag(ClassAccessFlags::Super as u16) { 43 | flag_vec.push("ACC_SUPER "); 44 | } 45 | 46 | // TODO implement other access flags 47 | 48 | flag_vec.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }) 49 | } 50 | 51 | pub fn render_constant_pool(constant_pool: &ConstantPool) -> Vec { 52 | constant_pool.constants.iter().map(|constant| { 53 | ClassfilePrinter::render_constant(&constant, constant_pool) 54 | }).collect() 55 | } 56 | 57 | pub fn render_constant(constant: &Constant, pool: &ConstantPool) -> String { 58 | match constant { 59 | &Constant::Utf8(ref content) => format!("Utf8 {}", String::from_utf8_lossy(content.as_slice())), 60 | &Constant::Integer(value) => format!("Integer {}", value), 61 | &Constant::Float(value) => format!("Float {}", value), 62 | &Constant::Long(value) => format!("Long {}", value), 63 | &Constant::Double(value) => format!("Double {}", value), 64 | &Constant::Class(ref index) => format!("Class #{:<14}// {}", index.idx, ClassfilePrinter::resolve_utf8(index, pool)), 65 | &Constant::FieldRef { class_index: ref ci, name_and_type_index: ref ni } => format!("FieldRef {:<14} // {}.{}", format!("#{}.#{}", ci.idx, ni.idx), ClassfilePrinter::resolve_class(ci, pool), ClassfilePrinter::resolve_name_and_type(ni, &pool)), 66 | &Constant::MethodRef { class_index: ref ci, name_and_type_index: ref ni } => format!("MethodRef {:<14} // {}.{}", format!("#{}.#{}", ci.idx, ni.idx), ClassfilePrinter::resolve_class(ci, pool), ClassfilePrinter::resolve_name_and_type(ni, pool)), 67 | &Constant::InterfaceMethodRef { class_index: ref ci, name_and_type_index: ref ni } => format!("InterfaceMethodRef {:<14} // {}.{}", format!("#{}.#{}", ci.idx, ni.idx), ClassfilePrinter::resolve_class(ci, pool), ClassfilePrinter::resolve_name_and_type(ni, pool)), 68 | &Constant::String(ref cpi) => format!("String #{:<14}// {}", cpi.idx, ClassfilePrinter::resolve_utf8(cpi, pool)), 69 | &Constant::NameAndType { name_index: ref ni, descriptor_index: ref dp } => format!("NameAndType {:<14} // {}:{}", format!("#{}:#{}", ni.idx, dp.idx), ClassfilePrinter::resolve_utf8(ni, pool), ClassfilePrinter::resolve_utf8(dp, pool)), 70 | &Constant::MethodHandle { reference_kind: ref kind, reference_index: ref ri } => format!("MethodHandle {} #{}", ClassfilePrinter::resolve_reference_kind(kind), ri.idx), 71 | &Constant::MethodType(ref cpi) => format!("MethodType #{}", cpi.idx), 72 | &Constant::InvokeDynamic { bootstrap_method_attr_index: ref bi, name_and_type_index: ref ni } => format!("InvokeDynamic #{}.{}", bi.idx, ClassfilePrinter::resolve_name_and_type(ni, pool)), 73 | &Constant::Unknown(value) => format!("Unknown constant {}", value), 74 | &Constant::Placeholder => format!("Placeholder") 75 | } 76 | } 77 | 78 | pub fn render_utf8(index: &ConstantPoolIndex, cp: &ConstantPool) -> Option { 79 | cp.get_utf8_string(index.idx as u16) 80 | } 81 | 82 | pub fn resolve_utf8(index: &ConstantPoolIndex, cp: &ConstantPool) -> String { 83 | ClassfilePrinter::render_utf8(index, cp).unwrap_or(String::from("")) 84 | } 85 | 86 | pub fn resolve_class(index: &ConstantPoolIndex, cp: &ConstantPool) -> String { 87 | cp.resolve_index(index).map(|constant| match constant { 88 | &Constant::Class(ref idx) => ClassfilePrinter::resolve_utf8(idx, cp), 89 | _ => String::from("") 90 | }).unwrap_or(String::from("")) 91 | } 92 | 93 | pub fn resolve_name_and_type(nandt: &ConstantPoolIndex, cp: &ConstantPool) -> String { 94 | cp.resolve_index(nandt).map(|constant| match constant { 95 | &Constant::NameAndType { name_index: ref ni, descriptor_index: ref di } => format!("{}:{}", ClassfilePrinter::resolve_utf8(ni, cp), ClassfilePrinter::resolve_utf8(di, cp)), 96 | _ => String::from("") 97 | }).unwrap_or(String::from("")) 98 | } 99 | 100 | pub fn resolve_method_reference(method_reference: &ConstantPoolIndex, cp: &ConstantPool) -> String { 101 | cp.resolve_index(method_reference).map(|constant| match constant { 102 | &Constant::MethodRef { class_index: ref ci, name_and_type_index: ref ni } => format!("{}:{}", ClassfilePrinter::resolve_class(ci, cp), ClassfilePrinter::resolve_name_and_type(ni, cp)), 103 | _ => String::from("Not a method reference>") 104 | }).unwrap_or(String::from("")) 105 | } 106 | 107 | pub fn resolve_reference_kind(kind: &ReferenceKind) -> String { 108 | String::from(match kind { 109 | &ReferenceKind::GetField => "GetField", 110 | &ReferenceKind::GetStatic => "GetStatic", 111 | &ReferenceKind::InvokeInterface => "InvokeInterface", 112 | &ReferenceKind::InvokeSpecial => "InvokeSpecial", 113 | &ReferenceKind::InvokeStatic => "InvokeStatic", 114 | &ReferenceKind::InvokeVirtual => "InvokeVirtual", 115 | &ReferenceKind::NewInvokeSpecial => "NewInvokeSpecial", 116 | &ReferenceKind::PutField => "PutField", 117 | &ReferenceKind::PutStatic => "PutStatic", 118 | _ => "Unknown" 119 | }) 120 | } 121 | 122 | pub fn render_methods(classfile: &Classfile) -> Vec { 123 | classfile.methods.iter().flat_map(|method| ClassfilePrinter::render_method(method, &classfile.constant_pool)).collect() 124 | } 125 | 126 | pub fn render_method(method: &Method, cp: &ConstantPool) -> Vec { 127 | let mut lines = vec![]; 128 | 129 | lines.push(format!(" {}()", ClassfilePrinter::resolve_utf8(&method.name_index, cp))); 130 | lines.push(format!(" Descriptor: {}", ClassfilePrinter::resolve_utf8(&method.descriptor_index, cp))); 131 | // TODO display access flags 132 | let _: Vec<()> = method.attributes.iter().flat_map(|code_attr| ClassfilePrinter::render_attribute(code_attr, cp)).map(|line| lines.push(line)).collect(); 133 | 134 | lines.push(String::from("")); 135 | 136 | lines 137 | } 138 | 139 | pub fn render_attribute(code: &Attribute, cp: &ConstantPool) -> Vec { 140 | let mut lines = vec![]; 141 | 142 | match code { 143 | &Attribute::Code { max_stack: ref ms, max_locals: ref ml, code: ref c, exception_table: ref et, attributes: ref attributes } => { 144 | let mut instr_pointer: usize = 0; 145 | 146 | lines.push(String::from(" Code: ")); 147 | lines.push(format!(" stack={} locals={} args={}", ms, ml, "???")); 148 | let _: Vec<()> = c.iter().map(|instr| (instr.len(), match instr { 149 | &Instruction::AALOAD => format!("aaload"), 150 | &Instruction::AASTORE => format!("aastore"), 151 | &Instruction::ACONST_NULL => format!("aconst_null"), 152 | &Instruction::ALOAD(value) => format!("aload {}", value), 153 | &Instruction::ALOAD_0 => format!("aload_0"), 154 | &Instruction::ALOAD_1 => format!("aload_1"), 155 | &Instruction::ALOAD_2 => format!("aload_2"), 156 | &Instruction::ALOAD_3 => format!("aload_3"), 157 | &Instruction::ASTORE(value) => format!("astore {}", value), 158 | &Instruction::ATHROW => format!("athrow"), 159 | &Instruction::BALOAD => format!("baload"), 160 | &Instruction::BASTORE => format!("bastore"), 161 | &Instruction::BIPUSH(value) => format!("bipush {}", value), 162 | &Instruction::CALOAD => format!("caload"), 163 | &Instruction::CASTORE => format!("castore"), 164 | &Instruction::CHECKCAST(value) => format!("checkcast {}", value), 165 | &Instruction::D2F => format!("d2f"), 166 | &Instruction::D2I => format!("d2i"), 167 | &Instruction::D2L => format!("d2l"), 168 | &Instruction::DADD => format!("dadd"), 169 | &Instruction::DALOAD => format!("daload"), 170 | &Instruction::DASTORE => format!("dastore"), 171 | &Instruction::DCMPL => format!("dcmpl"), 172 | &Instruction::DCMPG => format!("dcmpg"), 173 | &Instruction::DCONST_0 => format!("dconst_0"), 174 | &Instruction::DCONST_1 => format!("dconst_1"), 175 | &Instruction::DDIV => format!("ddiv"), 176 | &Instruction::DLOAD(value) => format!("dload {}", value), 177 | &Instruction::DLOAD_0 => format!("dload_0"), 178 | &Instruction::DLOAD_1 => format!("dload_1"), 179 | &Instruction::DLOAD_2 => format!("dload_2"), 180 | &Instruction::DLOAD_3 => format!("dload_3"), 181 | &Instruction::DMUL => format!("dmul"), 182 | &Instruction::DNEG => format!("dneg"), 183 | &Instruction::DREM => format!("drem"), 184 | &Instruction::DRETURN => format!("dreturn"), 185 | &Instruction::DSTORE(value) => format!("dstore {}", value), 186 | &Instruction::DSTORE_0 => format!("dstore_0"), 187 | &Instruction::DSTORE_1 => format!("dstore_1"), 188 | &Instruction::DSTORE_2 => format!("dstore_2"), 189 | &Instruction::DSTORE_3 => format!("dstore_3"), 190 | &Instruction::DSUB => format!("dsub"), 191 | &Instruction::DUP => format!("dup"), 192 | &Instruction::DUP_X1 => format!("dup_x1"), 193 | &Instruction::DUP_X2 => format!("dup_x2"), 194 | &Instruction::DUP2 => format!("dup2"), 195 | &Instruction::DUP2_X1 => format!("dup2_x1"), 196 | &Instruction::DUP2_X2 => format!("dup2_x2"), 197 | &Instruction::F2D => format!("f2d"), 198 | &Instruction::F2I => format!("f2i"), 199 | &Instruction::F2L => format!("f2l"), 200 | &Instruction::FADD => format!("fadd"), 201 | &Instruction::FALOAD => format!("faload"), 202 | &Instruction::FASTORE => format!("fastore"), 203 | &Instruction::FCMPL => format!("fcmpl"), 204 | &Instruction::FCMPG => format!("fcmpg"), 205 | &Instruction::FCONST_0 => format!("fconst_0"), 206 | &Instruction::FCONST_1 => format!("fconst_1"), 207 | &Instruction::FCONST_2 => format!("fconst_2"), 208 | &Instruction::FDIV => format!("fdiv"), 209 | &Instruction::FLOAD(value) => format!("fload {}", value), 210 | &Instruction::FLOAD_0 => format!("fload_0"), 211 | &Instruction::FLOAD_1 => format!("fload_1"), 212 | &Instruction::FLOAD_2 => format!("fload_2"), 213 | &Instruction::FLOAD_3 => format!("fload_3"), 214 | &Instruction::FMUL => format!("fmul"), 215 | &Instruction::FNEG => format!("fneg"), 216 | &Instruction::FREM => format!("frem"), 217 | &Instruction::FRETURN => format!("freturn"), 218 | &Instruction::FSTORE(value) => format!("fstore {}", value), 219 | &Instruction::FSTORE_0 => format!("fstore_0"), 220 | &Instruction::FSTORE_1 => format!("fstore_1"), 221 | &Instruction::FSTORE_2 => format!("fstore_2"), 222 | &Instruction::FSTORE_3 => format!("fstore_3"), 223 | &Instruction::FSUB => format!("fsub"), 224 | &Instruction::GETFIELD(value) => format!("getfield {}", value), 225 | &Instruction::GETSTATIC(value) => format!("getstatic {}", value), 226 | &Instruction::GOTO(value) => format!("goto {}", value), 227 | &Instruction::GOTO_W(value) => format!("goto_w {}", value), 228 | &Instruction::I2B => format!("i2b"), 229 | &Instruction::I2C => format!("i2c"), 230 | &Instruction::I2D => format!("i2d"), 231 | &Instruction::I2F => format!("i2f"), 232 | &Instruction::I2L => format!("i2l"), 233 | &Instruction::I2S => format!("i2s"), 234 | &Instruction::IADD => format!("iadd"), 235 | &Instruction::IALOAD => format!("iaload"), 236 | &Instruction::IAND => format!("iand"), 237 | &Instruction::IASTORE => format!("iastore"), 238 | &Instruction::ICONST_M1 => format!("iconst_m1"), 239 | &Instruction::ICONST_0 => format!("iconst_0"), 240 | &Instruction::ICONST_1 => format!("iconst_1"), 241 | &Instruction::ICONST_2 => format!("iconst_2"), 242 | &Instruction::ICONST_3 => format!("iconst_3"), 243 | &Instruction::ICONST_4 => format!("iconst_4"), 244 | &Instruction::ICONST_5 => format!("iconst_5"), 245 | &Instruction::IDIV => format!("idiv"), 246 | &Instruction::IF_ACMPEQ(value) => format!("if_acmpeq"), 247 | &Instruction::IF_ACMPNE(value) => format!("if_acmpne"), 248 | &Instruction::IF_ICMPEQ(value) => format!("if_icmpeq"), 249 | &Instruction::IF_ICMPNE(value) => format!("if_icmpne"), 250 | &Instruction::IF_ICMPLT(value) => format!("if_icmplt"), 251 | &Instruction::IF_ICMPGE(value) => format!("if_icmpge"), 252 | &Instruction::IF_ICMPGT(value) => format!("if_icmpgt"), 253 | &Instruction::IF_ICMPLE(value) => format!("if_icmple"), 254 | &Instruction::IFEQ(value) => format!("ifeq"), 255 | &Instruction::IFNE(value) => format!("ifne"), 256 | &Instruction::IFLT(value) => format!("iflt"), 257 | &Instruction::IFGE(value) => format!("ifge"), 258 | &Instruction::IFGT(value) => format!("ifgt"), 259 | &Instruction::IFLE(value) => format!("ifle"), 260 | &Instruction::IFNONNULL(value) => format!("ifnonnull"), 261 | &Instruction::IFNULL(value) => format!("ifnull"), 262 | &Instruction::IINC(value, increment) => format!("iinc"), 263 | &Instruction::ILOAD(value) => format!("iload"), 264 | &Instruction::ILOAD_0 => format!("iload_0"), 265 | &Instruction::ILOAD_1 => format!("iload_1"), 266 | &Instruction::ILOAD_2 => format!("iload_2"), 267 | &Instruction::ILOAD_3 => format!("iload_3"), 268 | &Instruction::IMUL => format!("imul"), 269 | &Instruction::INEG => format!("ineg"), 270 | &Instruction::INSTANCEOF(value) => format!("instanceof"), 271 | &Instruction::INVOKEDYNAMIC(value) => format!("invokedynamic #{}", value), 272 | &Instruction::INVOKEINTERFACE(value, index) => format!("invokeinterface #{}", value), 273 | &Instruction::INVOKESPECIAL(value) => format!("invokespecial {}", ClassfilePrinter::resolve_method_reference(&ConstantPoolIndex::new(value as usize), cp)), 274 | &Instruction::INVOKESTATIC(value) => format!("invokestatic {}", ClassfilePrinter::resolve_method_reference(&ConstantPoolIndex::new(value as usize), cp)), 275 | &Instruction::INVOKEVIRTUAL(value) => format!("invokevirtual {}", ClassfilePrinter::resolve_method_reference(&ConstantPoolIndex::new(value as usize), cp)), 276 | &Instruction::IOR => format!("ior"), 277 | &Instruction::IREM => format!("irem"), 278 | &Instruction::IRETURN => format!("ireturn"), 279 | &Instruction::ISHL => format!("ishl"), 280 | &Instruction::ISHR => format!("ishr"), 281 | &Instruction::ISTORE(value) => format!("istore {}", value), 282 | &Instruction::ISTORE_0 => format!("istore_0"), 283 | &Instruction::ISTORE_1 => format!("istore_1"), 284 | &Instruction::ISTORE_2 => format!("istore_2"), 285 | &Instruction::ISTORE_3 => format!("istore_3"), 286 | &Instruction::ISUB => format!("isub"), 287 | &Instruction::IUSHR => format!("iushr"), 288 | &Instruction::IXOR => format!("ixor"), 289 | &Instruction::JSR(value) => format!("jsr"), 290 | &Instruction::JSR_W(value) => format!("jsr_w"), 291 | &Instruction::L2D => format!("l2d"), 292 | &Instruction::L2F => format!("l2f"), 293 | &Instruction::L2I => format!("l2i"), 294 | &Instruction::LADD => format!("ladd"), 295 | &Instruction::LALOAD => format!("laload"), 296 | &Instruction::LAND => format!("land"), 297 | &Instruction::LASTORE => format!("lastore"), 298 | &Instruction::LCMP => format!("lcmp"), 299 | &Instruction::LCONST_0 => format!("lconst_0"), 300 | &Instruction::LCONST_1 => format!("lconst_1"), 301 | &Instruction::LDC(value) => format!("ldc"), 302 | &Instruction::LDC_W(value) => format!("ldc_w"), 303 | &Instruction::LDC2_W(value) => format!("ldc2_w"), 304 | &Instruction::LDIV => format!("ldiv"), 305 | &Instruction::LLOAD(value) => format!("lload"), 306 | &Instruction::LLOAD_0 => format!("lload_0"), 307 | &Instruction::LLOAD_1 => format!("lload_1"), 308 | &Instruction::LLOAD_2 => format!("lload_2"), 309 | &Instruction::LLOAD_3 => format!("lload_3"), 310 | &Instruction::LMUL => format!("lmul"), 311 | &Instruction::LNEG => format!("lneg"), 312 | &Instruction::LOOKUPSWITCH(value, ref table) => format!("lookupswitch"), 313 | &Instruction::LOR => format!("lor"), 314 | &Instruction::LREM => format!("lrem"), 315 | &Instruction::LRETURN => format!("lreturn"), 316 | &Instruction::LSHL => format!("lshl"), 317 | &Instruction::LSHR => format!("lshr"), 318 | &Instruction::LSTORE(value) => format!("lstore {}", value), 319 | &Instruction::LSTORE_0 => format!("lstore_0"), 320 | &Instruction::LSTORE_1 => format!("lstore_1"), 321 | &Instruction::LSTORE_2 => format!("lstore_2"), 322 | &Instruction::LSTORE_3 => format!("lstore_3"), 323 | &Instruction::LSUB => format!("lsub"), 324 | &Instruction::LUSHR => format!("lushr"), 325 | &Instruction::LXOR => format!("lxor"), 326 | &Instruction::MONITORENTER => format!("monitorenter"), 327 | &Instruction::MONITOREXIT => format!("monitorexit"), 328 | &Instruction::MULTIANEWARRAY(value, size) => format!("multianewarray"), 329 | &Instruction::NEW(value) => format!("new"), 330 | &Instruction::NEWARRAY(value) => format!("newarray"), 331 | &Instruction::NOP => format!("nop"), 332 | &Instruction::POP => format!("pop"), 333 | &Instruction::POP2 => format!("pop2"), 334 | &Instruction::PUTFIELD(value) => format!("putfield"), 335 | &Instruction::PUTSTATIC(value) => format!("putstatic"), 336 | &Instruction::RET(value) => format!("ret"), 337 | &Instruction::RETURN => format!("return"), 338 | &Instruction::SALOAD => format!("saload"), 339 | &Instruction::SASTORE => format!("sastore"), 340 | &Instruction::SIPUSH(value) => format!("sipush {}", value), 341 | &Instruction::SWAP => format!("swap"), 342 | &Instruction::TABLESWITCH(value, _, _, _) => format!("tableswitch"), 343 | &Instruction::IINC_W(value, increment) => format!("iinc_w"), 344 | &Instruction::ILOAD_W(value) => format!("iload_w {}", value), 345 | &Instruction::FLOAD_W(value) => format!("fload_w {}", value), 346 | &Instruction::ALOAD_W(value) => format!("aload_w {}", value), 347 | &Instruction::LLOAD_W(value) => format!("lload_w {}", value), 348 | &Instruction::DLOAD_W(value) => format!("dload_w {}", value), 349 | &Instruction::ISTORE_W(value) => format!("istore_w {}", value), 350 | &Instruction::FSTORE_W(value) => format!("fstore_w {}", value), 351 | &Instruction::ASTORE_W(value) => format!("astore_w {}", value), 352 | &Instruction::LSTORE_W(value) => format!("lstore_w {}", value), 353 | &Instruction::DSTORE_W(value) => format!("dstore_w {}", value), 354 | &Instruction::RET_W(value) => format!("ret_w {}", value), 355 | &Instruction::PADDED_INSTRUCTION(value) => format!("padded_instruction {}", value), 356 | &Instruction::WTF(value) => format!("wtf {}", value), 357 | _ => format!("instr") 358 | })).map(|line| { 359 | lines.push(format!(" {:>4}: {}", instr_pointer, line.1)); 360 | instr_pointer = instr_pointer + line.0 361 | }).collect(); 362 | 363 | let _: Vec<()> = attributes.iter().flat_map(|att| ClassfilePrinter::render_attribute(att, cp)).map(|line| format!(" {}", line)).map(|line| lines.push(line)).collect(); 364 | }, 365 | &Attribute::LineNumberTable(ref table) => { 366 | lines.push(String::from(" LineNumberTable")); 367 | 368 | let _: Vec<()> = ClassfilePrinter::render_line_number_table(table).iter().map(|line_number| lines.push(format!(" {}", line_number))).collect(); 369 | }, 370 | &Attribute::ConstantValue(ref cpi) => { lines.push(format!(" ConstantValue #{}", cpi.idx)); }, 371 | &Attribute::StackMapTable(ref table) => { 372 | lines.push(format!(" StackMapTable")); 373 | let _: Vec<()> = table.iter().map(|frame| ClassfilePrinter::render_stack_map_frame(frame)).map(|line| lines.push(format!(" {}", line))).collect(); 374 | }, 375 | &Attribute::AnnotationDefault(_) => { lines.push(format!(" AnnotationDefault")); }, 376 | &Attribute::BootstrapMethods(_) => { lines.push(format!(" BootstrapMethods")); }, 377 | &Attribute::LocalVariableTable(ref table) => { 378 | lines.push(String::from(" LocalVariableTable")); 379 | let _: Vec<()> = table.iter().map(|local_var| ClassfilePrinter::render_local_variable(local_var)).map(|line| lines.push(format!(" {}", line))).collect(); 380 | }, 381 | &Attribute::LocalVariableTypeTable(ref table) => { 382 | lines.push(String::from(" LocalVariableTypeTable")); 383 | let _: Vec<()> = table.iter().map(|var_type| ClassfilePrinter::render_local_variable_type(var_type)).map(|line| lines.push(format!(" {}", line))).collect(); 384 | }, 385 | &Attribute::Deprecated => { lines.push(format!(" Deprecated")); }, 386 | _ => { 387 | lines.push(format!("RandomAttribute")); 388 | () 389 | }, 390 | //Code { max_stack: u16, max_locals: u16, code: Vec, exception_table: Vec, attributes: Vec }, 391 | } 392 | 393 | lines 394 | } 395 | 396 | pub fn render_stack_map_frame(frame: &StackMapFrame) -> String { 397 | match frame { 398 | &StackMapFrame::SameFrame { tag: tag } => format!("SameFrame {}", tag), 399 | &StackMapFrame::SameLocals1StackItemFrame { tag: tag, stack: _ /*VerificationType*/ } => format!("SameLocals1StackItemFrame {}", tag), 400 | &StackMapFrame::SameLocals1StackItemFrameExtended { offset_delta: offset, stack: _ /*VerificationType*/ } => format!("SameLocals1StackItemFrameExtended {}", offset), 401 | &StackMapFrame::ChopFrame { tag: tag, offset_delta: offset } => format!("ChopFrame {} {}", tag, offset), 402 | &StackMapFrame::SameFrameExtended { offset_delta: offset } => format!("SameFrameExtended {}", offset), 403 | &StackMapFrame::AppendFrame { tag: tag, offset_delta: offset, locals: _ /*Vec*/ } => format!("AppendFrame {} {}", tag, offset), 404 | &StackMapFrame::FullFrame { offset_delta: offset, locals: _ /*Vec*/, stack: _ /*Vec*/ } => format!("FullFrame {}", offset), 405 | &StackMapFrame::FutureUse { tag: tag } => format!("FutureUse") 406 | } 407 | } 408 | 409 | pub fn render_local_variable(variable: &LocalVariableTable) -> String { 410 | format!("{}", variable.index) 411 | } 412 | 413 | pub fn render_local_variable_type(variable_type: &LocalVariableTypeTable) -> String { 414 | format!("{}", variable_type.index) 415 | } 416 | 417 | pub fn render_line_number_table(table: &Vec) -> Vec { 418 | table.iter().map(|line| format!("line {}: {}", line.line_number, line.start_pc)).collect() 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /jvmti/src/capabilities.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::{Display, Error}; 3 | use std::fmt::Formatter; 4 | use super::native::jvmti_native::*; 5 | 6 | #[derive(Default, Clone)] 7 | pub struct Capabilities { 8 | /// Can set and get tags 9 | pub can_tag_objects: bool, 10 | /// Can set watchpoints on field modification 11 | pub can_generate_field_modification_events: bool, 12 | /// Can set watchpoints on field access 13 | pub can_generate_field_access_events: bool, 14 | /// Can get bytecodes of a method 15 | pub can_get_bytecodes: bool, 16 | /// Can test if a field or method is synthetic 17 | pub can_get_synthetic_attribute: bool, 18 | /// Can get information about ownership of monitors 19 | pub can_get_owned_monitor_info: bool, 20 | /// Can GetCurrentContendedMonitor 21 | pub can_get_current_contended_monitor: bool, 22 | /// Can GetObjectMonitorUsage 23 | pub can_get_monitor_info: bool, 24 | /// Can pop frames off the stack 25 | pub can_pop_frame: bool, 26 | /// Can redefine classes with RedefineClasses 27 | pub can_redefine_classes: bool, 28 | /// Can send stop or interrupt to threads 29 | pub can_signal_thread: bool, 30 | /// Can get the source file name of a class 31 | pub can_get_source_file_name: bool, 32 | /// Can get the line number table of a method 33 | pub can_get_line_numbers: bool, 34 | /// Can get the source debug extension of a class 35 | pub can_get_source_debug_extension: bool, 36 | /// Can set and get local variables 37 | pub can_access_local_variables: bool, 38 | /// Can return methods in the order they occur in the class file 39 | pub can_maintain_original_method_order: bool, 40 | /// Can get single step events 41 | pub can_generate_single_step_events: bool, 42 | /// Can get exception thrown and exception catch events 43 | pub can_generate_exception_events: bool, 44 | /// Can set and thus get FramePop events 45 | pub can_generate_frame_pop_events: bool, 46 | /// Can set and thus get Breakpoint events 47 | pub can_generate_breakpoint_events: bool, 48 | /// Can suspend and resume threads 49 | pub can_suspend: bool, 50 | /// Can modify (retransform or redefine) any non-primitive non-array class. 51 | pub can_redefine_any_class: bool, 52 | /// Can get current thread CPU time 53 | pub can_get_current_thread_cpu_time: bool, 54 | /// Can get thread CPU time 55 | pub can_get_thread_cpu_time: bool, 56 | /// Can generate method entry events on entering a method 57 | pub can_generate_method_entry_events: bool, 58 | /// Can generate method exit events on leaving a method 59 | pub can_generate_method_exit_events: bool, 60 | /// Can generate ClassFileLoadHook events for every loaded class. 61 | pub can_generate_all_class_hook_events: bool, 62 | /// Can generate events when a method is compiled or unloaded 63 | pub can_generate_compiled_method_load_events: bool, 64 | /// Can generate events on monitor activity 65 | pub can_generate_monitor_events: bool, 66 | /// Can generate events on VM allocation of an object 67 | pub can_generate_vm_object_alloc_events: bool, 68 | /// Can generate events when a native method is bound to its implementation 69 | pub can_generate_native_method_bind_events: bool, 70 | /// Can generate events when garbage collection begins or ends 71 | pub can_generate_garbage_collection_events: bool, 72 | /// Can generate events when the garbage collector frees an object 73 | pub can_generate_object_free_events: bool, 74 | /// Can return early from a method 75 | pub can_force_early_return: bool, 76 | /// Can get information about owned monitors with stack depth 77 | pub can_get_owned_monitor_stack_depth_info: bool, 78 | /// Can get the constant pool of a class 79 | pub can_get_constant_pool: bool, 80 | /// Can set prefix to be applied when native method cannot be resolved 81 | pub can_set_native_method_prefix: bool, 82 | /// Can retransform classes with RetransformClasses. In addition to the restrictions imposed by the specific 83 | /// implementation on this capability (see the Capability section), this capability must be set before the 84 | /// ClassFileLoadHook event is enabled for the first time in this environment. An environment that possesses 85 | /// this capability at the time that ClassFileLoadHook is enabled for the first time is said to be 86 | /// retransformation capable. An environment that does not possess this capability at the time that 87 | /// ClassFileLoadHook is enabled for the first time is said to be retransformation incapable 88 | pub can_retransform_classes: bool, 89 | /// RetransformClasses can be called on any class (can_retransform_classes must also be set) 90 | pub can_retransform_any_class: bool, 91 | /// Can generate events when the VM is unable to allocate memory from the JavaTM platform heap. 92 | pub can_generate_resource_exhaustion_heap_events: bool, 93 | /// Can generate events when the VM is unable to create a thread. 94 | pub can_generate_resource_exhaustion_threads_events: bool 95 | } 96 | 97 | impl Capabilities { 98 | 99 | pub fn new() -> Capabilities { 100 | Capabilities { 101 | ..Default::default() 102 | } 103 | } 104 | 105 | pub fn from_native(native_caps: &jvmtiCapabilities) -> Capabilities { 106 | Capabilities { 107 | can_tag_objects: native_caps._bindgen_bitfield_1_ & 0x00000001 > 0, 108 | can_generate_field_modification_events: native_caps._bindgen_bitfield_1_ & 0x00000002 > 0, 109 | can_generate_field_access_events: native_caps._bindgen_bitfield_1_ & 0x00000004 > 0, 110 | can_get_bytecodes: native_caps._bindgen_bitfield_1_ & 0x00000008 > 0, 111 | can_get_synthetic_attribute: native_caps._bindgen_bitfield_1_ & 0x00000010 > 0, 112 | can_get_owned_monitor_info: native_caps._bindgen_bitfield_1_ & 0x00000020 > 0, 113 | can_get_current_contended_monitor: native_caps._bindgen_bitfield_1_ & 0x00000040 > 0, 114 | can_get_monitor_info: native_caps._bindgen_bitfield_1_ & 0x00000080 > 0, 115 | can_pop_frame: native_caps._bindgen_bitfield_1_ & 0x00000100 > 0, 116 | can_redefine_classes: native_caps._bindgen_bitfield_1_ & 0x00000200 > 0, 117 | can_signal_thread: native_caps._bindgen_bitfield_1_ & 0x00000400 > 0, 118 | can_get_source_file_name: native_caps._bindgen_bitfield_1_ & 0x00000800 > 0, 119 | can_get_line_numbers: native_caps._bindgen_bitfield_1_ & 0x00001000 > 0, 120 | can_get_source_debug_extension: native_caps._bindgen_bitfield_1_ & 0x00002000 > 0, 121 | can_access_local_variables: native_caps._bindgen_bitfield_1_ & 0x00004000 > 0, 122 | can_maintain_original_method_order: native_caps._bindgen_bitfield_1_ & 0x00008000 > 0, 123 | can_generate_single_step_events: native_caps._bindgen_bitfield_1_ & 0x00010000 > 0, 124 | can_generate_exception_events: native_caps._bindgen_bitfield_1_ & 0x00020000 > 0, 125 | can_generate_frame_pop_events: native_caps._bindgen_bitfield_1_ & 0x00040000 > 0, 126 | can_generate_breakpoint_events: native_caps._bindgen_bitfield_1_ & 0x00080000 > 0, 127 | can_suspend: native_caps._bindgen_bitfield_1_ & 0x00100000 > 0, 128 | can_redefine_any_class: native_caps._bindgen_bitfield_1_ & 0x00200000 > 0, 129 | can_get_current_thread_cpu_time: native_caps._bindgen_bitfield_1_ & 0x00400000 > 0, 130 | can_get_thread_cpu_time: native_caps._bindgen_bitfield_1_ & 0x00800000 > 0, 131 | can_generate_method_entry_events: native_caps._bindgen_bitfield_1_ & 0x01000000 > 0, 132 | can_generate_method_exit_events: native_caps._bindgen_bitfield_1_ & 0x02000000 > 0, 133 | can_generate_all_class_hook_events: native_caps._bindgen_bitfield_1_ & 0x04000000 > 0, 134 | can_generate_compiled_method_load_events: native_caps._bindgen_bitfield_1_ & 0x08000000 > 0, 135 | can_generate_monitor_events: native_caps._bindgen_bitfield_1_ & 0x10000000 > 0, 136 | can_generate_vm_object_alloc_events: native_caps._bindgen_bitfield_1_ & 0x20000000 > 0, 137 | can_generate_native_method_bind_events: native_caps._bindgen_bitfield_1_ & 0x40000000 > 0, 138 | can_generate_garbage_collection_events: native_caps._bindgen_bitfield_1_ & 0x80000000 > 0, 139 | 140 | can_generate_object_free_events: native_caps._bindgen_bitfield_2_ & 0x00000001 > 0, 141 | can_force_early_return: native_caps._bindgen_bitfield_2_ & 0x00000002 > 0, 142 | can_get_owned_monitor_stack_depth_info: native_caps._bindgen_bitfield_2_ & 0x00000004 > 0, 143 | can_get_constant_pool: native_caps._bindgen_bitfield_2_ & 0x00000008 > 0, 144 | can_set_native_method_prefix: native_caps._bindgen_bitfield_2_ & 0x00000010 > 0, 145 | can_retransform_classes: native_caps._bindgen_bitfield_2_ & 0x00000020 > 0, 146 | can_retransform_any_class: native_caps._bindgen_bitfield_2_ & 0x00000040 > 0, 147 | can_generate_resource_exhaustion_heap_events: native_caps._bindgen_bitfield_2_ & 0x00000080 > 0, 148 | can_generate_resource_exhaustion_threads_events: native_caps._bindgen_bitfield_2_ & 0x00000100 > 0, 149 | } 150 | } 151 | 152 | /// Convert this instance into a native jvmtiCapabilities instance that can be passwd to the 153 | /// native JVMTI interface 154 | pub fn to_native(&self) -> jvmtiCapabilities { 155 | let mut field_map1 = HashMap::new(); 156 | let mut field_map2 = HashMap::new(); 157 | let field_map3 = HashMap::new(); 158 | let field_map4 = HashMap::new(); 159 | 160 | // TODO this is probably not idiomatic Rust but this is the best I could come up with at them moment 161 | field_map1.insert(0x00000001, self.can_tag_objects); 162 | field_map1.insert(0x00000002, self.can_generate_field_modification_events); 163 | field_map1.insert(0x00000004, self.can_generate_field_access_events); 164 | field_map1.insert(0x00000008, self.can_get_bytecodes); 165 | field_map1.insert(0x00000010, self.can_get_synthetic_attribute); 166 | field_map1.insert(0x00000020, self.can_get_owned_monitor_info); 167 | field_map1.insert(0x00000040, self.can_get_current_contended_monitor); 168 | field_map1.insert(0x00000080, self.can_get_monitor_info); 169 | field_map1.insert(0x00000100, self.can_pop_frame); 170 | field_map1.insert(0x00000200, self.can_redefine_classes); 171 | field_map1.insert(0x00000400, self.can_signal_thread); 172 | field_map1.insert(0x00000800, self.can_get_source_file_name); 173 | field_map1.insert(0x00001000, self.can_get_line_numbers); 174 | field_map1.insert(0x00002000, self.can_get_source_debug_extension); 175 | field_map1.insert(0x00004000, self.can_access_local_variables); 176 | field_map1.insert(0x00008000, self.can_maintain_original_method_order); 177 | field_map1.insert(0x00010000, self.can_generate_single_step_events); 178 | field_map1.insert(0x00020000, self.can_generate_exception_events); 179 | field_map1.insert(0x00040000, self.can_generate_frame_pop_events); 180 | field_map1.insert(0x00080000, self.can_generate_breakpoint_events); 181 | field_map1.insert(0x00100000, self.can_suspend); 182 | field_map1.insert(0x00200000, self.can_redefine_any_class); 183 | field_map1.insert(0x00400000, self.can_get_current_thread_cpu_time); 184 | field_map1.insert(0x00800000, self.can_get_thread_cpu_time); 185 | field_map1.insert(0x01000000, self.can_generate_method_entry_events); 186 | field_map1.insert(0x02000000, self.can_generate_method_exit_events); 187 | field_map1.insert(0x04000000, self.can_generate_all_class_hook_events); 188 | field_map1.insert(0x08000000, self.can_generate_compiled_method_load_events); 189 | field_map1.insert(0x10000000, self.can_generate_monitor_events); 190 | field_map1.insert(0x20000000, self.can_generate_vm_object_alloc_events); 191 | field_map1.insert(0x40000000, self.can_generate_native_method_bind_events); 192 | field_map1.insert(0x80000000, self.can_generate_garbage_collection_events); 193 | 194 | field_map2.insert(0x00000001, self.can_generate_object_free_events); 195 | field_map2.insert(0x00000002, self.can_force_early_return); 196 | field_map2.insert(0x00000004, self.can_get_owned_monitor_stack_depth_info); 197 | field_map2.insert(0x00000008, self.can_get_constant_pool); 198 | field_map2.insert(0x00000010, self.can_set_native_method_prefix); 199 | field_map2.insert(0x00000020, self.can_retransform_classes); 200 | field_map2.insert(0x00000040, self.can_retransform_any_class); 201 | field_map2.insert(0x00000080, self.can_generate_resource_exhaustion_heap_events); 202 | field_map2.insert(0x00000100, self.can_generate_resource_exhaustion_threads_events); 203 | 204 | let fields = vec![ field_map1, field_map2, field_map3, field_map4 ]; 205 | let result:Vec = fields.iter().map(|f| f.iter().map(|(&value, &switch)| if switch { value } else { 0 }).fold(0, |acc, item| acc | item) ).collect(); 206 | 207 | let native_struct = jvmtiCapabilities { 208 | _bindgen_bitfield_1_: result[0], 209 | _bindgen_bitfield_2_: result[1], 210 | _bindgen_bitfield_3_: result[2], 211 | _bindgen_bitfield_4_: result[3] 212 | }; 213 | 214 | return native_struct; 215 | } 216 | 217 | pub fn merge(&self, other: &Capabilities) -> Capabilities { 218 | let native1 = self.to_native(); 219 | let native2 = other.to_native(); 220 | 221 | let native_merged = jvmtiCapabilities { 222 | _bindgen_bitfield_1_: native1._bindgen_bitfield_1_ | native2._bindgen_bitfield_1_, 223 | _bindgen_bitfield_2_: native1._bindgen_bitfield_1_ | native2._bindgen_bitfield_2_, 224 | _bindgen_bitfield_3_: native1._bindgen_bitfield_1_ | native2._bindgen_bitfield_3_, 225 | _bindgen_bitfield_4_: native1._bindgen_bitfield_1_ | native2._bindgen_bitfield_4_ 226 | }; 227 | 228 | Capabilities::from_native(&native_merged) 229 | } 230 | } 231 | 232 | impl Display for Capabilities { 233 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 234 | write!(f, "(\ 235 | can_tag_objects: {},\ 236 | can_generate_field_modification_events: {},\ 237 | can_generate_field_access_events: {},\ 238 | can_get_bytecodes: {},\ 239 | can_get_synthetic_attribute: {},\ 240 | can_get_owned_monitor_info: {},\ 241 | can_get_current_contended_monitor: {},\ 242 | can_get_monitor_info: {},\ 243 | can_pop_frame: {},\ 244 | can_redefine_classes: {},\ 245 | can_signal_thread: {},\ 246 | can_get_source_file_name: {},\ 247 | can_get_line_numbers: {},\ 248 | can_get_source_debug_extension: {},\ 249 | can_access_local_variables: {},\ 250 | can_maintain_original_method_order: {},\ 251 | can_generate_single_step_events: {},\ 252 | can_generate_exception_events: {},\ 253 | can_generate_frame_pop_events: {},\ 254 | can_generate_breakpoint_events: {},\ 255 | can_suspend: {},\ 256 | can_redefine_any_class: {},\ 257 | can_get_current_thread_cpu_time: {},\ 258 | can_get_thread_cpu_time: {},\ 259 | can_generate_method_entry_events: {},\ 260 | can_generate_method_exit_events: {},\ 261 | can_generate_all_class_hook_events: {},\ 262 | can_generate_compiled_method_load_events: {},\ 263 | can_generate_monitor_events: {},\ 264 | can_generate_vm_object_alloc_events: {},\ 265 | can_generate_native_method_bind_events: {},\ 266 | can_generate_garbage_collection_events: {},\ 267 | can_generate_object_free_events: {},\ 268 | can_force_early_return: {},\ 269 | can_get_owned_monitor_stack_depth_info: {},\ 270 | can_get_constant_pool: {},\ 271 | can_set_native_method_prefix: {},\ 272 | can_retransform_classes: {},\ 273 | can_retransform_any_class: {},\ 274 | can_generate_resource_exhaustion_heap_events: {},\ 275 | can_generate_resource_exhaustion_threads_events: {})", 276 | 277 | self.can_tag_objects, 278 | self.can_generate_field_modification_events, 279 | self.can_generate_field_access_events, 280 | self.can_get_bytecodes, 281 | self.can_get_synthetic_attribute, 282 | self.can_get_owned_monitor_info, 283 | self.can_get_current_contended_monitor, 284 | self.can_get_monitor_info, 285 | self.can_pop_frame, 286 | self.can_redefine_classes, 287 | self.can_signal_thread, 288 | self.can_get_source_file_name, 289 | self.can_get_line_numbers, 290 | self.can_get_source_debug_extension, 291 | self.can_access_local_variables, 292 | self.can_maintain_original_method_order, 293 | self.can_generate_single_step_events, 294 | self.can_generate_exception_events, 295 | self.can_generate_frame_pop_events, 296 | self.can_generate_breakpoint_events, 297 | self.can_suspend, 298 | self.can_redefine_any_class, 299 | self.can_get_current_thread_cpu_time, 300 | self.can_get_thread_cpu_time, 301 | self.can_generate_method_entry_events, 302 | self.can_generate_method_exit_events, 303 | self.can_generate_all_class_hook_events, 304 | self.can_generate_compiled_method_load_events, 305 | self.can_generate_monitor_events, 306 | self.can_generate_vm_object_alloc_events, 307 | self.can_generate_native_method_bind_events, 308 | self.can_generate_garbage_collection_events, 309 | self.can_generate_object_free_events, 310 | self.can_force_early_return, 311 | self.can_get_owned_monitor_stack_depth_info, 312 | self.can_get_constant_pool, 313 | self.can_set_native_method_prefix, 314 | self.can_retransform_classes, 315 | self.can_retransform_any_class, 316 | self.can_generate_resource_exhaustion_heap_events, 317 | self.can_generate_resource_exhaustion_threads_events) 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /jvmti/src/class.rs: -------------------------------------------------------------------------------- 1 | use super::native::JavaClass; 2 | 3 | /// 4 | /// Enumeration of the possible Java types. 5 | /// 6 | #[derive(Debug, Eq, PartialEq)] 7 | pub enum JavaType<'a> { 8 | Boolean, 9 | Byte, 10 | Char, 11 | Double, 12 | Float, 13 | Int, 14 | Long, 15 | Short, 16 | Void, 17 | Class(&'a str), 18 | Array(Box>) 19 | } 20 | 21 | impl<'a> JavaType<'a> { 22 | 23 | /// Convert a given type signature into a JavaType instance (if possible). None is returned 24 | /// if the conversation was not successful. 25 | pub fn parse(signature: &'a str) -> Option> { 26 | match signature.len() { 27 | 0 => None, 28 | 1 => match &*signature { 29 | "B" => Some(JavaType::Byte), 30 | "C" => Some(JavaType::Char), 31 | "D" => Some(JavaType::Double), 32 | "F" => Some(JavaType::Float), 33 | "I" => Some(JavaType::Int), 34 | "J" => Some(JavaType::Long), 35 | "S" => Some(JavaType::Short), 36 | "V" => Some(JavaType::Void), 37 | "Z" => Some(JavaType::Boolean), 38 | _ => None 39 | }, 40 | _ => { 41 | match signature.chars().nth(0).unwrap() { 42 | '[' => { 43 | let (_, local_type) = signature.split_at(1); 44 | 45 | match JavaType::parse(local_type) { 46 | Some(result) => Some(JavaType::Array(Box::new(result))), 47 | None => None 48 | } 49 | }, 50 | 'L' => Some(JavaType::Class(signature)), 51 | _ => None 52 | } 53 | } 54 | } 55 | } 56 | 57 | /// 58 | /// Converts the given Java type into a conventional human-readable representation 59 | /// 60 | pub fn to_string(java_type: &JavaType) -> String { 61 | match *java_type { 62 | JavaType::Byte => "byte".to_string(), 63 | JavaType::Char => "char".to_string(), 64 | JavaType::Double => "double".to_string(), 65 | JavaType::Float => "float".to_string(), 66 | JavaType::Int => "int".to_string(), 67 | JavaType::Long => "long".to_string(), 68 | JavaType::Short => "short".to_string(), 69 | JavaType::Void => "void".to_string(), 70 | JavaType::Boolean => "boolean".to_string(), 71 | JavaType::Array(ref inner_type) => format!("{}[]", JavaType::to_string(inner_type)), 72 | JavaType::Class(cls) => cls.trim_left_matches("L").trim_right_matches(";").replace(";", "").replace("/", ".").to_string() 73 | } 74 | } 75 | } 76 | 77 | /// 78 | /// Represents a JNI local reference to a Java class 79 | /// 80 | pub struct ClassId { 81 | pub native_id: JavaClass 82 | } 83 | 84 | pub struct ClassSignature { 85 | pub package: String, 86 | pub name: String 87 | } 88 | 89 | impl ClassSignature { 90 | 91 | pub fn new(java_type: &JavaType) -> ClassSignature { 92 | let str = JavaType::to_string(java_type); 93 | match str.rfind('.') { 94 | Some(idx) => { 95 | let (pkg, name) = str.split_at(idx + 1); 96 | 97 | ClassSignature { 98 | package: pkg.trim_right_matches(".").to_string(), 99 | name: name.to_string() 100 | } 101 | }, 102 | None => ClassSignature { package: "".to_string(), name: str.to_string() } 103 | 104 | } 105 | } 106 | 107 | pub fn to_string(&self) -> String { 108 | format!("{}.{}", self.package, self.name) 109 | } 110 | } 111 | 112 | /// 113 | /// Represents a Java class 114 | /// 115 | pub struct Class { 116 | pub id: ClassId, 117 | pub signature: ClassSignature 118 | } 119 | 120 | impl Class { 121 | 122 | /// Constructs a new Class instance. 123 | pub fn new<'a>(id: ClassId, signature: JavaType<'a>) -> Class { 124 | Class { id: id, signature: ClassSignature::new(&signature) } 125 | } 126 | 127 | /// Returns the readable name of this class 128 | pub fn to_string(&self) -> String { 129 | self.signature.to_string() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /jvmti/src/config.rs: -------------------------------------------------------------------------------- 1 | extern crate toml; 2 | 3 | use std::fs::File; 4 | use std::io::{ Read }; 5 | use std::path::Path; 6 | 7 | #[derive(Deserialize)] 8 | pub struct Config { 9 | pub agent_name: String, 10 | pub entry_points: Vec, 11 | pub active_classes: Vec 12 | } 13 | 14 | impl Config { 15 | 16 | pub fn read_config() -> Option { 17 | let default_config: String = String::from("agent.conf"); 18 | 19 | Config::read_from_file(default_config) 20 | } 21 | 22 | pub fn read_from_file>(file_name: T) -> Option { 23 | match File::open(file_name) { 24 | Ok(mut file) => { 25 | let mut contents = String::new(); 26 | let _ = file.read_to_string(&mut contents); 27 | 28 | let config: Config = toml::from_str(contents.as_str()).unwrap(); 29 | 30 | Some(config) 31 | }, 32 | _ => None 33 | } 34 | } 35 | } 36 | 37 | impl Default for Config { 38 | 39 | fn default() -> Self { 40 | Config { 41 | agent_name: String::from("default"), 42 | entry_points: vec![], 43 | active_classes: vec![] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jvmti/src/context.rs: -------------------------------------------------------------------------------- 1 | use super::config::Config; 2 | use super::thread::ThreadId; 3 | use std::collections::HashMap; 4 | use std::sync::{Arc, RwLock}; 5 | use time::Duration; 6 | use time::Tm; 7 | use time::now; 8 | 9 | /// 10 | /// Public static mutable *cough* agent context. This seems necessary as our code is invoked from 11 | /// the JVM and we need a place to store the temporary mutable data. 12 | /// 13 | /// This wouldn't be such a problem if this was a C program, but you know, this is not a C program. 14 | /// 15 | lazy_static! { 16 | static ref STATIC_CONTEXT: AgentContext = AgentContext::new(); 17 | } 18 | 19 | /// 20 | /// Public accessor that provides an abstraction to the global mutable agent state. 21 | /// 22 | pub fn static_context() -> &'static AgentContext { 23 | &STATIC_CONTEXT 24 | } 25 | 26 | pub struct AgentContext { 27 | context: Arc>, 28 | pub config: Arc> 29 | } 30 | 31 | impl AgentContext { 32 | pub fn new() -> AgentContext { 33 | AgentContext { 34 | context: Arc::new(RwLock::new(Context::new())), 35 | config: Arc::new(RwLock::new(Config::default())) 36 | } 37 | } 38 | 39 | pub fn set_config(&self, config: Config) { 40 | match self.config.write() { 41 | Ok(mut cfg) => { 42 | *cfg = config; 43 | }, 44 | Err(_) => { /* TODO */ } 45 | } 46 | } 47 | 48 | pub fn thread_start(&self, thread_id: &ThreadId) { 49 | match self.context.write() { 50 | Ok(mut ctx) => { 51 | (*ctx).thread_lifetime.insert((*thread_id).clone(), now()); 52 | }, 53 | Err(_) => { /* TODO: Ignore for now */ } 54 | } 55 | } 56 | 57 | pub fn thread_end(&self, thread_id: &ThreadId) -> Option { 58 | match self.context.write() { 59 | Ok(mut ctx) => { 60 | let now = now(); 61 | Some((*ctx).thread_lifetime.remove(thread_id).unwrap_or(now) - now) 62 | }, 63 | Err(_) => { None /* TODO: Ignore for now */ } 64 | } 65 | } 66 | 67 | pub fn monitor_enter(&self, thread_id: &ThreadId) { 68 | match self.context.write() { 69 | Ok(mut ctx) => { 70 | (*ctx).monitor_queue.insert((*thread_id).clone(), now()); 71 | }, 72 | Err(_) => { 73 | // TODO: Ignore this 74 | } 75 | } 76 | } 77 | 78 | pub fn monitor_entered(&self, thread_id: &ThreadId) -> Option { 79 | match self.context.write() { 80 | Ok(mut ctx) => { 81 | let now = now(); 82 | Some((*ctx).monitor_queue.remove(thread_id).unwrap_or(now) - now) 83 | }, 84 | Err(_) => { None /* TODO: Ignore for now */ } 85 | } 86 | } 87 | 88 | pub fn wait_start(&self, thread_id: &ThreadId) { 89 | match self.context.write() { 90 | Ok(mut ctx) => { 91 | (*ctx).thread_wait.insert((*thread_id).clone(), now()); 92 | }, 93 | Err(_) => { /* TODO: Ignore for now */ } 94 | } 95 | } 96 | 97 | pub fn wait_end(&self, thread_id: &ThreadId) -> Option { 98 | match self.context.write() { 99 | Ok(mut ctx) => { 100 | let now = now(); 101 | Some((*ctx).thread_wait.remove(thread_id).unwrap_or(now) - now) 102 | }, 103 | Err(_) => { None /* TODO: Ignoring for now */ } 104 | } 105 | } 106 | 107 | pub fn method_enter(&self, thread_id: &ThreadId) { 108 | match self.context.write() { 109 | Ok(mut ctx) => { 110 | let now = now(); 111 | 112 | let new_stack = match (*ctx).method_times.remove(thread_id) { 113 | Some(mut thread_stack) => { 114 | thread_stack.push(now); 115 | thread_stack 116 | }, 117 | None => { 118 | let new_vec = vec![ now ]; 119 | new_vec 120 | } 121 | }; 122 | 123 | (*ctx).method_times.insert((*thread_id).clone(), new_stack); 124 | }, 125 | Err(_) => { /* TODO: Ignoring for now */ } 126 | } 127 | } 128 | 129 | pub fn method_exit(&self, thread_id: &ThreadId) -> Option { 130 | match self.context.write() { 131 | Ok(mut ctx) => { 132 | let now = now(); 133 | 134 | match (*ctx).method_times.get_mut(thread_id) { 135 | Some(ref mut thread_stack) => match thread_stack.pop() { 136 | Some(time) => Some(time - now), 137 | None => None 138 | }, 139 | None => None 140 | } 141 | }, 142 | Err(_) => { None /* TODO Ignoring for now */ } 143 | } 144 | } 145 | } 146 | 147 | pub struct Context { 148 | pub thread_lifetime: HashMap, 149 | pub monitor_queue: HashMap, 150 | pub thread_wait: HashMap, 151 | pub method_times: HashMap>, 152 | pub method_net_times: HashMap> 153 | } 154 | 155 | impl Context { 156 | pub fn new() -> Context { 157 | Context { 158 | thread_lifetime: HashMap::new(), 159 | monitor_queue: HashMap::new(), 160 | thread_wait: HashMap::new(), 161 | method_times: HashMap::new(), 162 | method_net_times: HashMap::new() 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /jvmti/src/emulator.rs: -------------------------------------------------------------------------------- 1 | use super::capabilities::Capabilities; 2 | use super::class::{ClassId, ClassSignature}; 3 | use super::error::NativeError; 4 | use super::environment::jvm::JVMF; 5 | use super::environment::jvmti::{JVMTI}; 6 | use super::event::{EventCallbacks, VMEvent}; 7 | use super::mem::MemoryAllocation; 8 | use super::method::{MethodId, MethodSignature}; 9 | use super::native::JavaThread; 10 | use super::runtime::*; 11 | use super::thread::Thread; 12 | use super::version::VersionNumber; 13 | use std::collections::HashMap; 14 | 15 | /// Allows testing of JVM and JVMTI-related functions by emulating (mocking) a JVM agent. 16 | pub struct JVMEmulator { 17 | pub capabilities: Capabilities, 18 | pub callbacks: EventCallbacks, 19 | pub events: HashMap 20 | } 21 | 22 | impl JVMEmulator { 23 | pub fn new() -> JVMEmulator { 24 | JVMEmulator { 25 | capabilities: Capabilities::new(), 26 | callbacks: EventCallbacks::new(), 27 | events: HashMap::new() 28 | } 29 | } 30 | 31 | pub fn emit_method_entry(&self, event: MethodInvocationEvent) { 32 | match self.callbacks.method_entry { 33 | Some(handler) => { 34 | handler(event); 35 | }, 36 | _ => () 37 | } 38 | } 39 | } 40 | 41 | impl JVMF for JVMEmulator { 42 | fn get_environment(&self) -> Result, NativeError> { 43 | Ok(Box::new(JVMEmulator::new())) 44 | } 45 | 46 | fn destroy(&self) -> Result<(), NativeError> { 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl JVMTI for JVMEmulator { 52 | 53 | fn get_version_number(&self) -> VersionNumber { 54 | VersionNumber::unknown() 55 | } 56 | 57 | fn add_capabilities(&mut self, new_capabilities: &Capabilities) -> Result { 58 | let merged = self.capabilities.merge(&new_capabilities); 59 | self.capabilities = merged; 60 | Ok(self.capabilities.clone()) 61 | } 62 | 63 | fn get_capabilities(&self) -> Capabilities { 64 | self.capabilities.clone() 65 | } 66 | 67 | fn set_event_callbacks(&mut self, callbacks: EventCallbacks) -> Option { 68 | self.callbacks = callbacks; 69 | 70 | None 71 | } 72 | 73 | fn set_event_notification_mode(&mut self, event: VMEvent, mode: bool) -> Option { 74 | self.events.insert(event, mode); 75 | None 76 | } 77 | 78 | fn get_thread_info(&self, thread_id: &JavaThread) -> Result { 79 | match *thread_id as u64 { 80 | _ => Err(NativeError::NotImplemented) 81 | } 82 | } 83 | 84 | fn get_method_declaring_class(&self, method_id: &MethodId) -> Result { 85 | match method_id.native_id as u64 { 86 | _ => Err(NativeError::NotImplemented) 87 | } 88 | } 89 | 90 | fn get_method_name(&self, method_id: &MethodId) -> Result { 91 | match method_id.native_id as u64 { 92 | 0x01 => Ok(MethodSignature::new("".to_string())), 93 | _ => Err(NativeError::NotImplemented) 94 | } 95 | } 96 | 97 | fn get_class_signature(&self, class_id: &ClassId) -> Result { 98 | match class_id.native_id as u64 { 99 | _ => Err(NativeError::NotImplemented) 100 | } 101 | } 102 | 103 | fn allocate(&self, len: usize) -> Result { 104 | Ok(MemoryAllocation { ptr: ::std::ptr::null_mut(), len: len }) 105 | } 106 | 107 | fn deallocate(&self) { 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /jvmti/src/environment/jni.rs: -------------------------------------------------------------------------------- 1 | use super::super::native::{JavaObject, JNIEnvPtr}; 2 | use super::super::class::ClassId; 3 | 4 | /// 5 | /// `JNI` defines a set of operatations the JVM offers through it's JNI interface. 6 | /// 7 | pub trait JNI { 8 | 9 | /// Return an `ClassId` belonging to the given Java object instance. 10 | fn get_object_class(&self, object_id: &JavaObject) -> ClassId; 11 | } 12 | 13 | /// 14 | /// This is the native implementation of the `JNI` trait. Each trait method call is delegated 15 | /// to the represented JNI instance. 16 | pub struct JNIEnvironment { 17 | 18 | jni: JNIEnvPtr 19 | } 20 | 21 | impl JNIEnvironment { 22 | 23 | pub fn new(jni: JNIEnvPtr) -> JNIEnvironment { 24 | JNIEnvironment { jni: jni } 25 | } 26 | } 27 | 28 | impl JNI for JNIEnvironment { 29 | 30 | fn get_object_class(&self, object_id: &JavaObject) -> ClassId { 31 | unsafe { 32 | let class_id = (**self.jni).GetObjectClass.unwrap()(self.jni, *object_id); 33 | 34 | ClassId { native_id: class_id } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jvmti/src/environment/jvm.rs: -------------------------------------------------------------------------------- 1 | use super::super::native::{JavaVMPtr, JVMTIEnvPtr}; 2 | use super::super::native::jvmti_native::JVMTI_VERSION; 3 | use super::super::environment::jvmti::{JVMTI, JVMTIEnvironment}; 4 | use super::super::error::{wrap_error, NativeError}; 5 | use libc::c_void; 6 | use std::ptr; 7 | 8 | pub trait JVMF { 9 | fn get_environment(&self) -> Result, NativeError>; 10 | fn destroy(&self) -> Result<(), NativeError>; 11 | } 12 | /// 13 | /// `JVMAgent` represents a binding to the JVM. 14 | /// 15 | pub struct JVMAgent { 16 | vm: JavaVMPtr 17 | } 18 | 19 | impl JVMAgent { 20 | 21 | /// Create a new `JVMAgent` instance 22 | pub fn new(vm: JavaVMPtr) -> JVMAgent { 23 | JVMAgent { vm: vm } 24 | } 25 | } 26 | 27 | impl JVMF for JVMAgent { 28 | 29 | /// Return the native JVMTI environment if available (ie. the current thread is attached to it) 30 | /// otherwise return an error message. 31 | fn get_environment(&self) -> Result, NativeError> { 32 | unsafe { 33 | let mut void_ptr: *mut c_void = ptr::null_mut() as *mut c_void; 34 | let penv_ptr: *mut *mut c_void = &mut void_ptr as *mut *mut c_void; 35 | let result = wrap_error((**self.vm).GetEnv.unwrap()(self.vm, penv_ptr, JVMTI_VERSION) as u32); 36 | 37 | match result { 38 | NativeError::NoError => { 39 | let env_ptr: JVMTIEnvPtr = *penv_ptr as JVMTIEnvPtr; 40 | let env = JVMTIEnvironment::new(env_ptr); 41 | return Result::Ok(Box::new(env)); 42 | }, 43 | err @ _ => Result::Err(wrap_error(err as u32)) 44 | } 45 | } 46 | } 47 | 48 | fn destroy(&self) -> Result<(), NativeError> { 49 | unsafe { 50 | let error = (**self.vm).DestroyJavaVM.unwrap()(self.vm) as u32; 51 | 52 | if error == 0 { 53 | Ok(()) 54 | } else { 55 | Err(wrap_error(error)) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jvmti/src/environment/jvmti.rs: -------------------------------------------------------------------------------- 1 | use super::super::capabilities::Capabilities; 2 | use super::super::class::{ClassId, ClassSignature, JavaType}; 3 | use super::super::error::{wrap_error, NativeError}; 4 | use super::super::event::{EventCallbacks, VMEvent}; 5 | use super::super::event_handler::*; 6 | use super::super::mem::MemoryAllocation; 7 | use super::super::method::{MethodId, MethodSignature}; 8 | use super::super::thread::{ThreadId, Thread}; 9 | use super::super::util::stringify; 10 | use super::super::version::VersionNumber; 11 | use super::super::native::{MutString, MutByteArray, JavaClass, JavaObject, JavaInstance, JavaLong, JavaThread, JVMTIEnvPtr}; 12 | use super::super::native::jvmti_native::{Struct__jvmtiThreadInfo, jvmtiCapabilities}; 13 | use std::ptr; 14 | 15 | pub trait JVMTI { 16 | 17 | /// 18 | /// Return the JVM TI version number, which includes major, minor and micro version numbers. 19 | /// 20 | fn get_version_number(&self) -> VersionNumber; 21 | /// Set new capabilities by adding the capabilities whose values are set to true in new_caps. 22 | /// All previous capabilities are retained. 23 | /// Some virtual machines may allow a limited set of capabilities to be added in the live phase. 24 | fn add_capabilities(&mut self, new_capabilities: &Capabilities) -> Result; 25 | fn get_capabilities(&self) -> Capabilities; 26 | /// Set the functions to be called for each event. The callbacks are specified by supplying a 27 | /// replacement function table. The function table is copied--changes to the local copy of the 28 | /// table have no effect. This is an atomic action, all callbacks are set at once. No events 29 | /// are sent before this function is called. When an entry is None no event is sent. 30 | /// An event must be enabled and have a callback in order to be sent--the order in which this 31 | /// function and set_event_notification_mode are called does not affect the result. 32 | fn set_event_callbacks(&mut self, callbacks: EventCallbacks) -> Option; 33 | fn set_event_notification_mode(&mut self, event: VMEvent, mode: bool) -> Option; 34 | fn get_thread_info(&self, thread_id: &JavaThread) -> Result; 35 | fn get_method_declaring_class(&self, method_id: &MethodId) -> Result; 36 | fn get_method_name(&self, method_id: &MethodId) -> Result; 37 | fn get_class_signature(&self, class_id: &ClassId) -> Result; 38 | fn allocate(&self, len: usize) -> Result; 39 | fn deallocate(&self); 40 | } 41 | 42 | pub struct JVMTIEnvironment { 43 | 44 | jvmti: JVMTIEnvPtr 45 | } 46 | 47 | impl JVMTIEnvironment { 48 | pub fn new(env_ptr: JVMTIEnvPtr) -> JVMTIEnvironment { 49 | JVMTIEnvironment { jvmti: env_ptr } 50 | } 51 | } 52 | 53 | impl JVMTI for JVMTIEnvironment { 54 | 55 | fn get_version_number(&self) -> VersionNumber { 56 | unsafe { 57 | let mut version: i32 = 0; 58 | let version_ptr = &mut version; 59 | (**self.jvmti).GetVersionNumber.unwrap()(self.jvmti, version_ptr); 60 | let uversion = *version_ptr as u32; 61 | VersionNumber::from_u32(&uversion) 62 | } 63 | } 64 | 65 | fn add_capabilities(&mut self, new_capabilities: &Capabilities) -> Result { 66 | let native_caps = new_capabilities.to_native(); 67 | let caps_ptr:*const jvmtiCapabilities = &native_caps; 68 | 69 | unsafe { 70 | match wrap_error((**self.jvmti).AddCapabilities.unwrap()(self.jvmti, caps_ptr)) { 71 | NativeError::NoError => Ok(self.get_capabilities()), 72 | err @ _ => Err(err) 73 | } 74 | } 75 | } 76 | 77 | fn get_capabilities(&self) -> Capabilities { 78 | unsafe { 79 | let caps = Capabilities::new(); 80 | let mut native_caps = caps.to_native(); 81 | { 82 | let cap_ptr = &mut native_caps; 83 | (**self.jvmti).GetCapabilities.unwrap()(self.jvmti, cap_ptr); 84 | } 85 | Capabilities::from_native(&native_caps) 86 | } 87 | } 88 | 89 | fn set_event_callbacks(&mut self, callbacks: EventCallbacks) -> Option { 90 | register_vm_init_callback(callbacks.vm_init); 91 | register_vm_start_callback(callbacks.vm_start); 92 | register_vm_death_callback(callbacks.vm_death); 93 | register_vm_object_alloc_callback(callbacks.vm_object_alloc); 94 | register_method_entry_callback(callbacks.method_entry); 95 | register_method_exit_callback(callbacks.method_exit); 96 | register_thread_start_callback(callbacks.thread_start); 97 | register_thread_end_callback(callbacks.thread_end); 98 | register_exception_callback(callbacks.exception); 99 | register_exception_catch_callback(callbacks.exception_catch); 100 | register_monitor_wait_callback(callbacks.monitor_wait); 101 | register_monitor_waited_callback(callbacks.monitor_waited); 102 | register_monitor_contended_enter_callback(callbacks.monitor_contended_enter); 103 | register_monitor_contended_endered_callback(callbacks.monitor_contended_entered); 104 | register_field_access_callback(callbacks.field_access); 105 | register_field_modification_callback(callbacks.field_modification); 106 | register_garbage_collection_start(callbacks.garbage_collection_start); 107 | register_garbage_collection_finish(callbacks.garbage_collection_finish); 108 | register_class_file_load_hook(callbacks.class_file_load_hook); 109 | 110 | let (native_callbacks, callbacks_size) = registered_callbacks(); 111 | 112 | unsafe { 113 | match wrap_error((**self.jvmti).SetEventCallbacks.unwrap()(self.jvmti, &native_callbacks, callbacks_size)) { 114 | NativeError::NoError => None, 115 | err @ _ => Some(err) 116 | } 117 | } 118 | } 119 | 120 | fn set_event_notification_mode(&mut self, event: VMEvent, mode: bool) -> Option { 121 | unsafe { 122 | let mode_i = match mode { true => 1, false => 0 }; 123 | let sptr: JavaObject = ptr::null_mut(); 124 | 125 | match wrap_error((**self.jvmti).SetEventNotificationMode.unwrap()(self.jvmti, mode_i, event as u32, sptr)) { 126 | NativeError::NoError => None, 127 | err @ _ => Some(err) 128 | } 129 | } 130 | } 131 | 132 | fn get_thread_info(&self, thread_id: &JavaThread) -> Result { 133 | let mut info = Struct__jvmtiThreadInfo { name: ptr::null_mut(), priority: 0, is_daemon: 0, thread_group: ptr::null_mut(), context_class_loader: ptr::null_mut()}; 134 | let mut info_ptr = &mut info; 135 | 136 | unsafe { 137 | match (**self.jvmti).GetThreadInfo { 138 | Some(func) => { 139 | match wrap_error(func(self.jvmti, *thread_id, info_ptr)) { 140 | NativeError::NoError => Ok(Thread { 141 | id: ThreadId { native_id: *thread_id }, 142 | name: stringify((*info_ptr).name), 143 | priority: (*info_ptr).priority as u32, 144 | is_daemon: if (*info_ptr).is_daemon > 0 { true } else { false } 145 | }), 146 | err@_ => Err(err) 147 | } 148 | }, 149 | None => Err(NativeError::NoError) 150 | } 151 | } 152 | } 153 | 154 | fn get_method_declaring_class(&self, method_id: &MethodId) -> Result { 155 | let mut jstruct: JavaInstance = JavaInstance { _hacky_hack_workaround: 0 }; 156 | let mut jclass_instance: JavaClass = &mut jstruct; 157 | let meta_ptr: *mut JavaClass = &mut jclass_instance; 158 | 159 | unsafe { 160 | match wrap_error((**self.jvmti).GetMethodDeclaringClass.unwrap()(self.jvmti, method_id.native_id, meta_ptr)) { 161 | NativeError::NoError => Ok(ClassId { native_id: *meta_ptr }), 162 | err @ _ => Err(err) 163 | } 164 | } 165 | } 166 | 167 | fn get_method_name(&self, method_id: &MethodId) -> Result { 168 | let mut method_name = ptr::null_mut(); 169 | let mut method_ptr = &mut method_name; 170 | 171 | let mut signature: MutString = ptr::null_mut(); 172 | let mut signature_ptr = &mut signature; 173 | 174 | let mut generic_sig: MutString = ptr::null_mut(); 175 | let mut generic_sig_ptr = &mut generic_sig; 176 | 177 | unsafe { 178 | match wrap_error((**self.jvmti).GetMethodName.unwrap()(self.jvmti, method_id.native_id, method_ptr, signature_ptr, generic_sig_ptr)) { 179 | NativeError::NoError => Ok(MethodSignature::new(stringify(*method_ptr))), 180 | err @ _ => Err(err) 181 | } 182 | } 183 | } 184 | 185 | fn get_class_signature(&self, class_id: &ClassId) -> Result { 186 | unsafe { 187 | let mut native_sig: MutString = ptr::null_mut(); 188 | let mut sig: MutString = ptr::null_mut(); 189 | let p1: *mut MutString = &mut sig; 190 | let p2: *mut MutString = &mut native_sig; 191 | 192 | match wrap_error((**self.jvmti).GetClassSignature.unwrap()(self.jvmti, class_id.native_id, p1, p2)) { 193 | NativeError::NoError => Ok(ClassSignature::new(&JavaType::parse(&stringify(sig)).unwrap())), 194 | err @ _ => Err(err) 195 | } 196 | } 197 | } 198 | 199 | fn allocate(&self, len: usize) -> Result { 200 | let size: JavaLong = len as JavaLong; 201 | let mut ptr: MutByteArray = ptr::null_mut(); 202 | let mem_ptr: *mut MutByteArray = &mut ptr; 203 | 204 | unsafe { 205 | match wrap_error((**self.jvmti).Allocate.unwrap()(self.jvmti, size, mem_ptr)) { 206 | NativeError::NoError => Ok(MemoryAllocation { ptr: ptr, len: len }), 207 | err @ _ => Err(err) 208 | } 209 | } 210 | } 211 | 212 | fn deallocate(&self) { 213 | 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /jvmti/src/environment/mod.rs: -------------------------------------------------------------------------------- 1 | use self::jvmti::{JVMTI, JVMTIEnvironment}; 2 | use self::jni::{JNI, JNIEnvironment}; 3 | use super::capabilities::Capabilities; 4 | use super::class::{ClassId, ClassSignature}; 5 | use super::error::NativeError; 6 | use super::event::{EventCallbacks, VMEvent}; 7 | use super::mem::MemoryAllocation; 8 | use super::method::{MethodId, MethodSignature}; 9 | use super::native::{JavaObject, JavaThread}; 10 | use super::thread::Thread; 11 | use super::version::VersionNumber; 12 | 13 | pub mod jni; 14 | pub mod jvm; 15 | pub mod jvmti; 16 | 17 | /// `Environment` combines the functionality of both `JNI` and `JVMTI` by wrapping an instance of 18 | /// both and delegating the method calls to their corresponding recipients. 19 | pub struct Environment { 20 | jvmti: JVMTIEnvironment, 21 | jni: JNIEnvironment 22 | } 23 | 24 | impl Environment { 25 | 26 | pub fn new(jvmti: JVMTIEnvironment, jni: JNIEnvironment) -> Environment { 27 | Environment { jvmti: jvmti, jni: jni } 28 | } 29 | } 30 | 31 | impl JVMTI for Environment { 32 | 33 | fn get_version_number(&self) -> VersionNumber { 34 | self.jvmti.get_version_number() 35 | } 36 | 37 | fn add_capabilities(&mut self, new_capabilities: &Capabilities) -> Result { 38 | self.jvmti.add_capabilities(new_capabilities) 39 | } 40 | 41 | fn get_capabilities(&self) -> Capabilities { 42 | self.jvmti.get_capabilities() 43 | } 44 | 45 | fn set_event_callbacks(&mut self, callbacks: EventCallbacks) -> Option { 46 | self.jvmti.set_event_callbacks(callbacks) 47 | } 48 | 49 | fn set_event_notification_mode(&mut self, event: VMEvent, mode: bool) -> Option { 50 | self.jvmti.set_event_notification_mode(event, mode) 51 | } 52 | 53 | fn get_thread_info(&self, thread_id: &JavaThread) -> Result { 54 | self.jvmti.get_thread_info(thread_id) 55 | } 56 | 57 | fn get_method_declaring_class(&self, method_id: &MethodId) -> Result { 58 | self.jvmti.get_method_declaring_class(method_id) 59 | } 60 | 61 | fn get_method_name(&self, method_id: &MethodId) -> Result { 62 | self.jvmti.get_method_name(method_id) 63 | } 64 | 65 | fn get_class_signature(&self, class_id: &ClassId) -> Result { 66 | self.jvmti.get_class_signature(class_id) 67 | } 68 | 69 | fn allocate(&self, len: usize) -> Result { 70 | self.jvmti.allocate(len) 71 | } 72 | 73 | fn deallocate(&self) { 74 | self.jvmti.deallocate() 75 | } 76 | } 77 | 78 | impl JNI for Environment { 79 | 80 | fn get_object_class(&self, object_id: &JavaObject) -> ClassId { 81 | self.jni.get_object_class(object_id) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /jvmti/src/error.rs: -------------------------------------------------------------------------------- 1 | 2 | /// A type-safe representation of possible errors 3 | pub enum NativeError { 4 | NoError = 0, 5 | MustPossessCapability = 99, 6 | NullPointer = 100, 7 | OutOfMemory = 110, 8 | NotEnabled = 111, 9 | NotAvailable = 112, 10 | UnexpectedInternalError = 113, 11 | ThreadNotAttached = 115, 12 | Disconnected = 116, 13 | NotImplemented = 999999, // <- now this is a "temporary" hack until the library is under heavy development 14 | UnknownError 15 | } 16 | 17 | /// Turn a native error code into a type-safe error 18 | pub fn wrap_error(code: u32) -> NativeError { 19 | match code { 20 | 0 => NativeError::NoError, 21 | 99 => NativeError::MustPossessCapability, 22 | 100 => NativeError::NullPointer, 23 | 110 => NativeError::OutOfMemory, 24 | 111 => NativeError::NotEnabled, 25 | 112 => NativeError::NotAvailable, 26 | 113 => NativeError::UnexpectedInternalError, 27 | 115 => NativeError::ThreadNotAttached, 28 | 116 => NativeError::Disconnected, 29 | 999999 => NativeError::NotImplemented, 30 | _ => { println!("Unknown error code was detected: {}", code); NativeError::UnknownError } 31 | } 32 | } 33 | 34 | /// Turn native error codes into meaningful and user-readable strings 35 | pub fn translate_error(code: &NativeError) -> String { 36 | match code { 37 | &NativeError::NoError => "No error has occurred.", 38 | &NativeError::MustPossessCapability => "The capability being used is false in this environment.", 39 | &NativeError::NullPointer => "Pointer is unexpectedly NULL.", 40 | &NativeError::OutOfMemory => "The function attempted to allocate memory and no more memory was available for allocation.", 41 | &NativeError::NotEnabled => "The desired functionality has not been enabled in this virtual machine.", 42 | &NativeError::NotAvailable => "The desired functionality is not available in the current phase. Always returned if the virtual machine has completed running.", 43 | &NativeError::UnexpectedInternalError => "An unexpected internal error has occurred.", 44 | &NativeError::ThreadNotAttached => "The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads.", 45 | &NativeError::Disconnected => "The JVM TI environment provided is no longer connected or is not an environment.", 46 | &NativeError::NotImplemented => "This function is not implemented yet", 47 | &NativeError::UnknownError => "Unknown error." 48 | }.to_string() 49 | } 50 | -------------------------------------------------------------------------------- /jvmti/src/event.rs: -------------------------------------------------------------------------------- 1 | use super::native::jvmti_native::*; 2 | use super::runtime::*; 3 | use super::thread::Thread; 4 | 5 | pub type FnMethodEntry = fn(event: MethodInvocationEvent) -> (); 6 | pub type FnMethodExit = fn(event: MethodInvocationEvent) -> (); 7 | pub type FnVMInit = fn() -> (); 8 | pub type FnVMDeath = fn() -> (); 9 | pub type FnVMStart = fn() -> (); 10 | pub type FnVMObjectAlloc = fn(event: ObjectAllocationEvent) -> (); 11 | pub type FnVMObjectFree = fn() -> (); 12 | pub type FnThreadStart = fn(thread: Thread) -> (); 13 | pub type FnThreadEnd = fn(thread: Thread) -> (); 14 | pub type FnException = fn() -> (); 15 | pub type FnExceptionCatch = fn() -> (); 16 | pub type FnMonitorWait = fn(thread: Thread) -> (); 17 | pub type FnMonitorWaited = fn(thread: Thread) -> (); 18 | pub type FnMonitorContendedEnter = fn(thread: Thread) -> (); 19 | pub type FnMonitorContendedEntered = fn(thread: Thread) -> (); 20 | pub type FnFieldAccess = fn() -> (); 21 | pub type FnFieldModification = fn() -> (); 22 | pub type FnGarbageCollectionStart = fn() -> (); 23 | pub type FnGarbageCollectionFinish = fn() -> (); 24 | pub type FnClassFileLoad = fn(event: ClassFileLoadEvent) -> Option>; 25 | pub type FnClassLoad = fn() -> (); 26 | pub type FnClassPrepare = fn() -> (); 27 | pub type FnSingleStep = fn() -> (); 28 | pub type FnFramePop = fn() -> (); 29 | pub type FnBreakpoint = fn() -> (); 30 | pub type FnNativeMethodBind = fn() -> (); 31 | pub type FnCompiledMethodLoad = fn() -> (); 32 | pub type FnCompiledMethodUnload = fn() -> (); 33 | pub type FnDynamicCodeGenerated = fn() -> (); 34 | pub type FnResourceExhausted = fn() -> (); 35 | pub type FnDataDumpRequest = fn() -> (); 36 | 37 | /// 38 | /// `VMEvent` represents events that can occur in JVM applications. These events can be handled 39 | /// using event handlers. For each event a corresponding handler will be called. 40 | /// 41 | #[allow(dead_code)] 42 | #[derive(Hash, Eq, PartialEq)] 43 | pub enum VMEvent { 44 | VMInit = JVMTI_EVENT_VM_INIT as isize, 45 | VMDeath = JVMTI_EVENT_VM_DEATH as isize, 46 | VMObjectAlloc = JVMTI_EVENT_VM_OBJECT_ALLOC as isize, 47 | VMObjectFree = JVMTI_EVENT_OBJECT_FREE as isize, 48 | VMStart = JVMTI_EVENT_VM_START as isize, 49 | MethodEntry = JVMTI_EVENT_METHOD_ENTRY as isize, 50 | MethodExit = JVMTI_EVENT_METHOD_EXIT as isize, 51 | ThreadStart = JVMTI_EVENT_THREAD_START as isize, 52 | ThreadEnd = JVMTI_EVENT_THREAD_END as isize, 53 | Exception = JVMTI_EVENT_EXCEPTION as isize, 54 | ExceptionCatch = JVMTI_EVENT_EXCEPTION_CATCH as isize, 55 | MonitorWait = JVMTI_EVENT_MONITOR_WAIT as isize, 56 | MonitorWaited = JVMTI_EVENT_MONITOR_WAITED as isize, 57 | MonitorContendedEnter = JVMTI_EVENT_MONITOR_CONTENDED_ENTER as isize, 58 | MonitorContendedEntered = JVMTI_EVENT_MONITOR_CONTENDED_ENTERED as isize, 59 | FieldAccess = JVMTI_EVENT_FIELD_ACCESS as isize, 60 | FieldModification = JVMTI_EVENT_FIELD_MODIFICATION as isize, 61 | GarbageCollectionStart = JVMTI_EVENT_GARBAGE_COLLECTION_START as isize, 62 | GarbageCollectionFinish = JVMTI_EVENT_GARBAGE_COLLECTION_FINISH as isize, 63 | ClassFileLoadHook = JVMTI_EVENT_CLASS_FILE_LOAD_HOOK as isize, 64 | ClassLoad = JVMTI_EVENT_CLASS_LOAD as isize, 65 | ClassPrepare = JVMTI_EVENT_CLASS_PREPARE as isize, 66 | SingleStep = JVMTI_EVENT_SINGLE_STEP as isize, 67 | FramePop = JVMTI_EVENT_FRAME_POP as isize, 68 | Breakpoint = JVMTI_EVENT_BREAKPOINT as isize, 69 | NativeMethodBind = JVMTI_EVENT_NATIVE_METHOD_BIND as isize, 70 | CompiledMethodLoad = JVMTI_EVENT_COMPILED_METHOD_LOAD as isize, 71 | CompiledMethodUnload = JVMTI_EVENT_COMPILED_METHOD_UNLOAD as isize, 72 | DynamicCodeGenerated = JVMTI_EVENT_DYNAMIC_CODE_GENERATED as isize, 73 | DataDumpRequest = JVMTI_EVENT_DATA_DUMP_REQUEST as isize, 74 | ResourceExhausted = JVMTI_EVENT_RESOURCE_EXHAUSTED as isize 75 | } 76 | 77 | /// 78 | /// The `EventCallbacks` structure is used to define a set of event handlers that the JVM will call 79 | /// when an event fires. 80 | /// 81 | #[derive(Default, Clone)] 82 | pub struct EventCallbacks { 83 | pub vm_init: Option, 84 | pub vm_death: Option, 85 | pub vm_object_alloc: Option, 86 | pub vm_object_free: Option, 87 | pub vm_start: Option, 88 | pub method_entry: Option, 89 | pub method_exit: Option, 90 | pub thread_start: Option, 91 | pub thread_end: Option, 92 | pub exception: Option, 93 | pub exception_catch: Option, 94 | pub monitor_wait: Option, 95 | pub monitor_waited: Option, 96 | pub monitor_contended_enter: Option, 97 | pub monitor_contended_entered: Option, 98 | pub field_access: Option, 99 | pub field_modification: Option, 100 | pub garbage_collection_start: Option, 101 | pub garbage_collection_finish: Option, 102 | pub class_file_load_hook: Option, 103 | pub class_load: Option, 104 | pub class_prepare: Option, 105 | pub single_step: Option, 106 | pub frame_pop: Option, 107 | pub breakpoint: Option, 108 | pub native_method_bind: Option, 109 | pub compiled_method_load: Option, 110 | pub compiled_method_unload: Option, 111 | pub dynamic_code_generated: Option, 112 | pub data_dump_request: Option, 113 | pub resource_exhausted: Option 114 | } 115 | 116 | impl EventCallbacks { 117 | 118 | pub fn new() -> EventCallbacks { 119 | EventCallbacks { ..Default::default() } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /jvmti/src/event_handler.rs: -------------------------------------------------------------------------------- 1 | use super::environment::Environment; 2 | use super::environment::jni::{JNI, JNIEnvironment}; 3 | use super::environment::jvmti::{JVMTI, JVMTIEnvironment}; 4 | use super::error::{translate_error, NativeError}; 5 | use super::event::*; 6 | use super::method::MethodId; 7 | use super::native::*; 8 | use super::native::jvmti_native::*; 9 | use super::runtime::*; 10 | use libc::{c_char, c_uchar, c_void}; 11 | use std::mem::size_of; 12 | use std::ptr; 13 | use super::util::stringify; 14 | use super::bytecode::*; 15 | use std::io::{ Cursor }; 16 | 17 | pub static mut CALLBACK_TABLE: EventCallbacks = EventCallbacks { 18 | vm_init: None, 19 | vm_death: None, 20 | vm_object_alloc: None, 21 | vm_object_free: None, 22 | vm_start: None, 23 | method_entry: None, 24 | method_exit: None, 25 | exception: None, 26 | exception_catch: None, 27 | monitor_wait: None, 28 | monitor_waited: None, 29 | monitor_contended_enter: None, 30 | monitor_contended_entered: None, 31 | thread_start: None, 32 | thread_end: None, 33 | field_access: None, 34 | field_modification: None, 35 | garbage_collection_start: None, 36 | garbage_collection_finish: None, 37 | class_file_load_hook: None, 38 | class_load: None, 39 | class_prepare: None, 40 | single_step: None, 41 | frame_pop: None, 42 | breakpoint: None, 43 | native_method_bind: None, 44 | compiled_method_load: None, 45 | compiled_method_unload: None, 46 | dynamic_code_generated: None, 47 | data_dump_request: None, 48 | resource_exhausted: None 49 | }; 50 | 51 | pub fn register_vm_init_callback(callback: Option) { 52 | unsafe { CALLBACK_TABLE.vm_init = callback; } 53 | } 54 | 55 | pub fn register_vm_death_callback(callback: Option) { 56 | unsafe { CALLBACK_TABLE.vm_death = callback; } 57 | } 58 | 59 | pub fn register_vm_object_alloc_callback(callback: Option) { 60 | unsafe { CALLBACK_TABLE.vm_object_alloc = callback; } 61 | } 62 | 63 | pub fn register_vm_object_free_callback(callback: Option) { 64 | unsafe { CALLBACK_TABLE.vm_object_free = callback; } 65 | } 66 | 67 | pub fn register_vm_start_callback(callback: Option) { 68 | unsafe { CALLBACK_TABLE.vm_start = callback; } 69 | } 70 | 71 | pub fn register_method_entry_callback(callback: Option) { 72 | unsafe { CALLBACK_TABLE.method_entry = callback; } 73 | } 74 | 75 | pub fn register_method_exit_callback(callback: Option) { 76 | unsafe { CALLBACK_TABLE.method_exit = callback; } 77 | } 78 | 79 | pub fn register_exception_callback(callback: Option) { 80 | unsafe { CALLBACK_TABLE.exception = callback; } 81 | } 82 | 83 | pub fn register_exception_catch_callback(callback: Option) { 84 | unsafe { CALLBACK_TABLE.exception_catch = callback; } 85 | } 86 | 87 | pub fn register_monitor_wait_callback(callback: Option) { 88 | unsafe { CALLBACK_TABLE.monitor_wait = callback; } 89 | } 90 | 91 | pub fn register_monitor_waited_callback(callback: Option) { 92 | unsafe { CALLBACK_TABLE.monitor_waited = callback; } 93 | } 94 | 95 | pub fn register_monitor_contended_enter_callback(callback: Option) { 96 | unsafe { CALLBACK_TABLE.monitor_contended_enter = callback; } 97 | } 98 | 99 | pub fn register_monitor_contended_endered_callback(callback: Option) { 100 | unsafe { CALLBACK_TABLE.monitor_contended_entered = callback; } 101 | } 102 | 103 | pub fn register_thread_start_callback(callback: Option) { 104 | unsafe { CALLBACK_TABLE.thread_start = callback; } 105 | } 106 | 107 | pub fn register_thread_end_callback(callback: Option) { 108 | unsafe { CALLBACK_TABLE.thread_end = callback; } 109 | } 110 | 111 | pub fn register_field_access_callback(callback: Option) { 112 | unsafe { CALLBACK_TABLE.field_access = callback; } 113 | } 114 | 115 | pub fn register_field_modification_callback(callback: Option) { 116 | unsafe { CALLBACK_TABLE.field_modification = callback; } 117 | } 118 | 119 | pub fn register_garbage_collection_start(callback: Option) { 120 | unsafe { CALLBACK_TABLE.garbage_collection_start = callback;} 121 | } 122 | 123 | pub fn register_garbage_collection_finish(callback: Option) { 124 | unsafe { CALLBACK_TABLE.garbage_collection_finish = callback; } 125 | } 126 | 127 | pub fn register_class_file_load_hook(callback: Option) { 128 | unsafe { CALLBACK_TABLE.class_file_load_hook = callback; } 129 | } 130 | 131 | pub fn registered_callbacks() -> (jvmtiEventCallbacks, i32) { 132 | (local_event_callbacks(), size_of::() as i32) 133 | } 134 | 135 | /// 136 | /// Generates a native `jvmtiEventCallbacks` structure holding the local extern even handler methods. 137 | /// 138 | pub fn local_event_callbacks() -> jvmtiEventCallbacks { 139 | jvmtiEventCallbacks { 140 | VMInit: Some(local_cb_vm_init), //jvmtiEventVMInit, 141 | VMDeath: Some(local_cb_vm_death), //jvmtiEventVMDeath, 142 | ThreadStart: Some(local_cb_thread_start), //jvmtiEventThreadStart, 143 | ThreadEnd: Some(local_cb_thread_end), //jvmtiEventThreadEnd, 144 | ClassFileLoadHook: Some(local_cb_class_file_load_hook), //jvmtiEventClassFileLoadHook, 145 | ClassLoad: Some(local_cb_class_load), //jvmtiEventClassLoad, 146 | ClassPrepare: Some(local_cb_class_prepare), //jvmtiEventClassPrepare, 147 | VMStart: Some(local_cb_vm_start), //jvmtiEventVMStart, 148 | Exception: Some(local_cb_exception), //jvmtiEventException, 149 | ExceptionCatch: Some(local_cb_exception_catch), //jvmtiEventExceptionCatch, 150 | SingleStep: Some(local_cb_single_step), //jvmtiEventSingleStep, 151 | FramePop: Some(local_cb_frame_pop), //jvmtiEventFramePop, 152 | Breakpoint: Some(local_cb_breakpoint), //jvmtiEventBreakpoint, 153 | FieldAccess: Some(local_cb_field_access), //jvmtiEventFieldAccess, 154 | FieldModification: Some(local_cb_field_modification), //jvmtiEventFieldModification, 155 | MethodEntry: Some(local_cb_method_entry), //jvmtiEventMethodEntry, 156 | MethodExit: Some(local_cb_method_exit), //jvmtiEventMethodExit, 157 | NativeMethodBind: Some(local_cb_native_method_bind), //jvmtiEventNativeMethodBind, 158 | CompiledMethodLoad: Some(local_cb_compiled_method_load), //jvmtiEventCompiledMethodLoad, 159 | CompiledMethodUnload: Some(local_cb_compiled_method_unload), //jvmtiEventCompiledMethodUnload, 160 | DynamicCodeGenerated: Some(local_cb_dynamic_code_generated), //jvmtiEventDynamicCodeGenerated, 161 | DataDumpRequest: Some(local_cb_data_dump_request), //jvmtiEventDataDumpRequest, 162 | reserved72: None, //jvmtiEventReserved, 163 | MonitorWait: Some(local_cb_monitor_wait), //jvmtiEventMonitorWait, 164 | MonitorWaited: Some(local_cb_monitor_waited), //jvmtiEventMonitorWaited, 165 | MonitorContendedEnter: Some(local_cb_monitor_contended_enter), //jvmtiEventMonitorContendedEnter, 166 | MonitorContendedEntered: Some(local_cb_monitor_contended_entered), //jvmtiEventMonitorContendedEntered, 167 | reserved77: None, //jvmtiEventReserved, 168 | reserved78: None, //jvmtiEventReserved, 169 | reserved79: None, //jvmtiEventReserved, 170 | ResourceExhausted: Some(local_cb_resource_exhausted), //jvmtiEventResourceExhausted, 171 | GarbageCollectionStart: Some(local_cb_garbage_collection_start), //jvmtiEventGarbageCollectionStart, 172 | GarbageCollectionFinish: Some(local_cb_garbage_collection_finish), //jvmtiEventGarbageCollectionFinish, 173 | ObjectFree: Some(local_cb_object_free), //jvmtiEventObjectFree, 174 | VMObjectAlloc: Some(local_cb_vm_object_alloc) //jvmtiEventVMObjectAlloc, 175 | } 176 | } 177 | 178 | 179 | #[allow(unused_variables)] 180 | unsafe extern "C" fn local_cb_vm_object_alloc(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: JavaThread, object: JavaObject, object_klass: JavaClass, size: jlong) -> () { 181 | match CALLBACK_TABLE.vm_object_alloc { 182 | Some(function) => { 183 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 184 | match env.get_thread_info(&thread) { 185 | Ok(current_thread) => { 186 | let class_id = env.get_object_class(&object); 187 | 188 | function(ObjectAllocationEvent { class_id: class_id, size: size as i64, thread: current_thread }) 189 | }, 190 | Err(err) => { 191 | match err { 192 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 193 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 194 | } 195 | } 196 | } 197 | }, 198 | None => println!("No dynamic callback method was found for VM object allocation") 199 | } 200 | } 201 | 202 | #[allow(unused_variables)] 203 | unsafe extern "C" fn local_cb_method_entry(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: JavaThread, method: JavaMethod) -> () { 204 | match CALLBACK_TABLE.method_entry { 205 | Some(function) => { 206 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 207 | match env.get_thread_info(&thread) { 208 | Ok(current_thread) => { 209 | let method_id = MethodId { native_id : method }; 210 | let class_id = env.get_method_declaring_class(&method_id).ok().unwrap(); 211 | let class_sig = env.get_class_signature(&class_id).ok().unwrap(); 212 | let method_sig = env.get_method_name(&method_id).ok().unwrap(); 213 | 214 | function(MethodInvocationEvent { method_id: method_id, method_sig: method_sig, class_sig: class_sig, thread: current_thread }) 215 | 216 | }, 217 | Err(err) => { 218 | match err { 219 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 220 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 221 | } 222 | } 223 | } 224 | }, 225 | None => println!("No dynamic callback method was found for method entry") 226 | } 227 | } 228 | 229 | #[allow(unused_variables)] 230 | unsafe extern "C" fn local_cb_method_exit(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, was_popped_by_exception: jboolean, return_value: jvalue) -> () { 231 | match CALLBACK_TABLE.method_exit { 232 | Some(function) => { 233 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 234 | match env.get_thread_info(&thread) { 235 | Ok(current_thread) => { 236 | let method_id = MethodId { native_id : method }; 237 | let class_id = env.get_method_declaring_class(&method_id).ok().unwrap(); 238 | let class_sig = env.get_class_signature(&class_id).ok().unwrap(); 239 | let method_sig = env.get_method_name(&method_id).ok().unwrap(); 240 | 241 | function(MethodInvocationEvent { method_id: method_id, method_sig: method_sig, class_sig: class_sig, thread: current_thread }) 242 | 243 | }, 244 | Err(err) => { 245 | match err { 246 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 247 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 248 | } 249 | } 250 | } 251 | } 252 | None => println!("No dynamic callback method was found for method exit") 253 | } 254 | } 255 | 256 | #[allow(unused_variables)] 257 | unsafe extern "C" fn local_cb_exception(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation, exception: JavaObject, catch_method: jmethodID, catch_location: jlocation) -> () { 258 | match CALLBACK_TABLE.exception { 259 | Some(function) => { 260 | function(); 261 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 262 | let exception_class = env.get_object_class(&exception); 263 | 264 | function() 265 | }, 266 | None => println!("No dynamic callback method was found for exception") 267 | } 268 | } 269 | 270 | #[allow(unused_variables)] 271 | unsafe extern "C" fn local_cb_exception_catch(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation, exception: jobject) -> () { 272 | match CALLBACK_TABLE.exception_catch { 273 | Some(function) => { 274 | function(); 275 | /* 276 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 277 | let current_thread = env.get_thread_info(&thread).ok().unwrap(); 278 | 279 | function() 280 | */ 281 | }, 282 | None => println!("No dynamic callback method was found for exception catch") 283 | } 284 | } 285 | 286 | #[allow(unused_variables)] 287 | unsafe extern "C" fn local_cb_monitor_wait(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, object: jobject, timeout: jlong) -> () { 288 | match CALLBACK_TABLE.monitor_wait { 289 | Some(function) => { 290 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 291 | match env.get_thread_info(&thread) { 292 | Ok(current_thread) => function(current_thread), 293 | Err(err) => { 294 | match err { 295 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 296 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 297 | } 298 | } 299 | } 300 | }, 301 | None => println!("No dynamic callback method was found for monitor wait") 302 | } 303 | } 304 | 305 | #[allow(unused_variables)] 306 | unsafe extern "C" fn local_cb_monitor_waited(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, object: jobject, timed_out: jboolean) -> () { 307 | match CALLBACK_TABLE.monitor_waited { 308 | Some(function) => { 309 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 310 | match env.get_thread_info(&thread) { 311 | Ok(current_thread) => function(current_thread), 312 | Err(err) => { 313 | match err { 314 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 315 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 316 | } 317 | } 318 | } 319 | }, 320 | None => println!("No dynamic callback method was found for monitor entered") 321 | } 322 | } 323 | 324 | #[allow(unused_variables)] 325 | unsafe extern "C" fn local_cb_monitor_contended_enter(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, object: jobject) -> () { 326 | match CALLBACK_TABLE.monitor_contended_enter { 327 | Some(function) => { 328 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 329 | match env.get_thread_info(&thread) { 330 | Ok(current_thread) => function(current_thread), 331 | Err(err) => { 332 | match err { 333 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 334 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 335 | } 336 | } 337 | } 338 | }, 339 | None => println!("No dynamic callback method was found for monitor contended enter") 340 | } 341 | } 342 | 343 | #[allow(unused_variables)] 344 | unsafe extern "C" fn local_cb_monitor_contended_entered(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, object: jobject) -> () { 345 | match CALLBACK_TABLE.monitor_contended_entered { 346 | Some(function) => { 347 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 348 | match env.get_thread_info(&thread) { 349 | Ok(current_thread) => function(current_thread), 350 | Err(err) => { 351 | match err { 352 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 353 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 354 | } 355 | } 356 | } 357 | }, 358 | None => println!("No dynamic callback method was found for monitor contended entered") 359 | } 360 | } 361 | 362 | #[allow(unused_variables)] 363 | unsafe extern "C" fn local_cb_thread_start(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread) -> () { 364 | match CALLBACK_TABLE.thread_start { 365 | Some(function) => { 366 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 367 | match env.get_thread_info(&thread) { 368 | Ok(current_thread) => function(current_thread), 369 | Err(err) => { 370 | match err { 371 | NativeError::NotAvailable => { /* we're in the wrong phase, just ignore this */ }, 372 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 373 | } 374 | } 375 | } 376 | }, 377 | None => println!("No dynamic callback method was found for thread start events") 378 | } 379 | 380 | } 381 | 382 | #[allow(unused_variables)] 383 | unsafe extern "C" fn local_cb_thread_end(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread) -> () { 384 | match CALLBACK_TABLE.thread_end { 385 | Some(function) => { 386 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 387 | match env.get_thread_info(&thread) { 388 | Ok(current_thread) => function(current_thread), 389 | Err(err) => { 390 | match err { 391 | NativeError::NotAvailable => { /* wrong phase, just ignore this */ }, 392 | _ => println!("Couldn't get thread info: {}", translate_error(&err)) 393 | } 394 | } 395 | } 396 | }, 397 | None => println!("No dynamic callback method was found for thread end events") 398 | } 399 | } 400 | 401 | #[allow(unused_variables)] 402 | unsafe extern "C" fn local_cb_garbage_collection_start(jvmti_env: *mut jvmtiEnv) -> () { 403 | match CALLBACK_TABLE.garbage_collection_start { 404 | Some(function) => { 405 | function(); 406 | 407 | }, 408 | None => println!("No dynamic callback method was found for garbage collection start events") 409 | } 410 | 411 | } 412 | 413 | #[allow(unused_variables)] 414 | unsafe extern "C" fn local_cb_garbage_collection_finish(jvmti_env: *mut jvmtiEnv) -> () { 415 | match CALLBACK_TABLE.garbage_collection_finish { 416 | Some(function) => { 417 | function(); 418 | 419 | }, 420 | None => println!("No dynamic callback method was found for garbage collection finish events") 421 | } 422 | 423 | } 424 | 425 | #[allow(unused_variables)] 426 | unsafe extern "C" fn local_cb_breakpoint(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation) -> () { 427 | 428 | } 429 | 430 | #[allow(unused_variables)] 431 | unsafe extern "C" fn local_cb_class_file_load_hook(jvmti_env: JVMTIEnvPtr, jni_env: JNIEnvPtr, class_being_redefined: JavaClass, loader: JavaObject, 432 | name: *const c_char, protection_domain: JavaObject, class_data_len: jint, class_data: *const c_uchar, 433 | new_class_data_len: *mut jint, new_class_data: *mut *mut c_uchar) -> () { 434 | match CALLBACK_TABLE.class_file_load_hook { 435 | Some(function) => { 436 | let env = Environment::new(JVMTIEnvironment::new(jvmti_env), JNIEnvironment::new(jni_env)); 437 | 438 | let mut raw_data: Vec = Vec::with_capacity(class_data_len as usize); 439 | let data_ptr = raw_data.as_mut_ptr(); 440 | 441 | ptr::copy_nonoverlapping(class_data, data_ptr, class_data_len as usize); 442 | raw_data.set_len(class_data_len as usize); 443 | 444 | if let Ok(classfile) = parse_class(&raw_data) { 445 | match function(ClassFileLoadEvent { class_name: stringify(name), class: classfile }) { 446 | Some(transformed) => { 447 | println!("Transformed class {}", stringify(name)); 448 | 449 | match env.allocate(transformed.len()) { 450 | Ok(allocation) => { 451 | ptr::copy_nonoverlapping(transformed.as_ptr(), allocation.ptr, allocation.len); 452 | *new_class_data_len = allocation.len as i32; 453 | *new_class_data = allocation.ptr; 454 | }, 455 | Err(err) => { 456 | println!("Failed to allocate memory") 457 | } 458 | } 459 | }, 460 | None => () 461 | } 462 | 463 | } else { 464 | println!("Coult not parse class file"); 465 | } 466 | 467 | 468 | println!("Loading class {} with length {}", stringify(name), class_data_len); 469 | }, 470 | None => println!("No dynamic callback method was found for class file load events") 471 | } 472 | } 473 | 474 | fn parse_class(data: &Vec) -> Result { 475 | let mut cursor = Cursor::new(data); 476 | 477 | //let class_result = ClassReader::read_class(&mut cursor); 478 | ClassReader::read_class(&mut cursor) 479 | 480 | /* 481 | match class_result { 482 | Ok(classfile) => { 483 | let output_class: Vec = vec![]; 484 | let mut write_cursor = Cursor::new(output_class); 485 | let mut writer = ClassWriter::new(&mut write_cursor); 486 | match writer.write_class(&classfile) { 487 | Ok(len) => (), 488 | Err(error) => () 489 | } 490 | }, 491 | Err(error) => () 492 | } 493 | */ 494 | } 495 | 496 | #[allow(unused_variables)] 497 | unsafe extern "C" fn local_cb_class_load(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, klass: jclass) -> () { 498 | 499 | } 500 | 501 | #[allow(unused_variables)] 502 | unsafe extern "C" fn local_cb_class_prepare(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, klass: jclass) -> () { 503 | 504 | } 505 | 506 | #[allow(unused_variables)] 507 | unsafe extern "C" fn local_cb_compiled_method_load(jvmti_env: *mut jvmtiEnv, method: jmethodID, code_size: jint, code_addr: *const c_void, map_length: jint, 508 | map: *const jvmtiAddrLocationMap, compile_info: *const c_void) -> () { 509 | 510 | } 511 | 512 | #[allow(unused_variables)] 513 | unsafe extern "C" fn local_cb_compiled_method_unload(jvmti_env: *mut jvmtiEnv, method: jmethodID, code_addr: *const c_void) -> () { 514 | 515 | } 516 | 517 | #[allow(unused_variables)] 518 | unsafe extern "C" fn local_cb_data_dump_request(jvmti_env: *mut jvmtiEnv) -> () { 519 | 520 | } 521 | 522 | #[allow(unused_variables)] 523 | unsafe extern "C" fn local_cb_dynamic_code_generated(jvmti_env: *mut jvmtiEnv, name: *const c_char, address: *const c_void, length: jint) -> () { 524 | 525 | } 526 | 527 | #[allow(unused_variables)] 528 | unsafe extern "C" fn local_cb_field_access(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation, 529 | field_klass: jclass, object: jobject, field: jfieldID) -> () { 530 | match CALLBACK_TABLE.field_access { 531 | Some(function) => { 532 | function(); 533 | }, 534 | None => println!("No dynamic callback method was found for field access events") 535 | } 536 | } 537 | 538 | #[allow(unused_variables)] 539 | unsafe extern "C" fn local_cb_field_modification(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation, 540 | field_klass: jclass, object: jobject, field: jfieldID, signature_type: c_char, new_value: jvalue) -> () { 541 | match CALLBACK_TABLE.field_modification { 542 | Some(function) => { 543 | function(); 544 | }, 545 | None => println!("No dynamic callback method was found for field modification events") 546 | } 547 | } 548 | 549 | #[allow(unused_variables)] 550 | unsafe extern "C" fn local_cb_frame_pop(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, was_popped_by_exception: jboolean) -> () { 551 | 552 | } 553 | 554 | #[allow(unused_variables)] 555 | unsafe extern "C" fn local_cb_native_method_bind(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, address: *mut c_void, 556 | new_address_ptr: *mut *mut c_void) -> () { 557 | 558 | } 559 | 560 | #[allow(unused_variables)] 561 | unsafe extern "C" fn local_cb_object_free(jvmti_env: *mut jvmtiEnv, tag: jlong) -> () { 562 | 563 | } 564 | 565 | #[allow(unused_variables)] 566 | unsafe extern "C" fn local_cb_resource_exhausted(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, flags: jint, reserved: *const c_void, description: *const c_char) -> () { 567 | 568 | } 569 | 570 | #[allow(unused_variables)] 571 | unsafe extern "C" fn local_cb_single_step(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread, method: jmethodID, location: jlocation) -> () { 572 | 573 | } 574 | 575 | #[allow(unused_variables)] 576 | unsafe extern "C" fn local_cb_vm_death(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv) -> () { 577 | 578 | match CALLBACK_TABLE.vm_death { 579 | Some(function) => { 580 | function(); 581 | }, 582 | None => println!("No dynamic callback method was found for VM death events") 583 | } 584 | } 585 | 586 | #[allow(unused_variables)] 587 | unsafe extern "C" fn local_cb_vm_init(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv, thread: jthread) -> () { 588 | 589 | match CALLBACK_TABLE.vm_init { 590 | Some(function) => { 591 | function(); 592 | }, 593 | None => println!("No dynamic callback method was found for VM init events") 594 | } 595 | } 596 | 597 | #[allow(unused_variables)] 598 | unsafe extern "C" fn local_cb_vm_start(jvmti_env: *mut jvmtiEnv, jni_env: *mut JNIEnv) -> () { 599 | match CALLBACK_TABLE.vm_start { 600 | Some(function) => { 601 | function(); 602 | }, 603 | None => println!("No dynamic callback method was found for VM start events") 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /jvmti/src/instrumentation/asm/mod.rs: -------------------------------------------------------------------------------- 1 | use super::super::bytecode::classfile::Classfile as ClassfileImpl; 2 | 3 | pub mod transformer; 4 | 5 | pub enum ClassfileVersion { 6 | Java1_5, 7 | Java1_6, 8 | Java1_7, 9 | Java1_8, 10 | Java1_9 11 | } 12 | 13 | pub struct Class { 14 | version: ClassfileVersion, 15 | constant_pool: ConstantPool 16 | } 17 | 18 | impl Class { 19 | pub fn new() -> Class { 20 | const DEFAULT_VERSION: ClassfileVersion = ClassfileVersion::Java1_8; 21 | 22 | Class { 23 | version: DEFAULT_VERSION, 24 | constant_pool: ConstantPool::new() 25 | } 26 | } 27 | 28 | pub fn set_version(&mut self, new_version: ClassfileVersion) -> () { 29 | self.version = new_version; 30 | } 31 | 32 | pub fn to_classfile(&self) -> ClassfileImpl { 33 | let mut cf = ClassfileImpl::new(); 34 | 35 | cf.version.major_version = match &self.version { 36 | Java1_5 => 49, 37 | Java1_6 => 50, 38 | Java1_7 => 51, 39 | Java1_8 => 52, 40 | Java1_9 => 53 41 | }; 42 | 43 | cf.version.minor_version = 0; 44 | 45 | cf 46 | } 47 | 48 | /// Return mutable reference to stored constant pool 49 | pub fn constant_pool(&mut self) -> &mut ConstantPool { 50 | &mut self.constant_pool 51 | } 52 | } 53 | 54 | pub struct ConstantPool { 55 | } 56 | 57 | impl ConstantPool { 58 | pub fn new() -> ConstantPool { 59 | ConstantPool {} 60 | } 61 | 62 | pub fn add_utf8_constant(&mut self, content: String) { 63 | 64 | } 65 | 66 | pub fn add_string_constant(&mut self, content: String) { 67 | 68 | } 69 | } 70 | 71 | pub struct Method { 72 | } 73 | 74 | impl Method { 75 | pub fn new() -> Method { 76 | Method {} 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /jvmti/src/instrumentation/asm/transformer.rs: -------------------------------------------------------------------------------- 1 | use super::super::super::bytecode::*; 2 | 3 | pub struct Transformer<'a> { 4 | 5 | class: &'a mut Classfile 6 | } 7 | 8 | impl<'a> Transformer<'a> { 9 | 10 | pub fn new(class: &mut Classfile) -> Transformer { 11 | Transformer { 12 | class: class 13 | } 14 | } 15 | 16 | pub fn ensure_constant(&mut self, constant: Constant) -> ConstantPoolIndex { 17 | self.class.constant_pool.get_constant_index(&constant).unwrap_or(self.class.constant_pool.add_constant(constant)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jvmti/src/instrumentation/mod.rs: -------------------------------------------------------------------------------- 1 | use super::bytecode::classfile::*; 2 | 3 | pub mod asm; 4 | 5 | #[derive(PartialEq)] 6 | pub enum JavaType { 7 | Boolean, 8 | String, 9 | Integer, 10 | Long, 11 | Float, 12 | Double, 13 | Array, 14 | Reference, 15 | Void 16 | } 17 | 18 | #[derive(PartialEq)] 19 | pub struct JavaClass { 20 | methods: Vec, 21 | fields: Vec 22 | } 23 | 24 | impl JavaClass { 25 | pub fn new() -> JavaClass { 26 | JavaClass { 27 | methods: vec![], 28 | fields: vec![] 29 | } 30 | } 31 | 32 | pub fn to_classfile(&self) -> Classfile { 33 | Classfile::new() 34 | } 35 | 36 | // TODO: this function should report errors better, instead of just returning nothing on error 37 | pub fn from_classfile(classfile: &Classfile) -> Option { 38 | None 39 | } 40 | 41 | pub fn add_method(method: Method) { 42 | 43 | } 44 | } 45 | 46 | #[derive(PartialEq)] 47 | pub struct Field { 48 | name: String, 49 | field_type: JavaType 50 | } 51 | 52 | impl Field { 53 | pub fn new(name: String, field_type: JavaType) -> Field { 54 | Field { 55 | name: name, 56 | field_type: field_type 57 | } 58 | } 59 | } 60 | 61 | #[derive(PartialEq)] 62 | pub struct Method { 63 | name: String, 64 | return_type: JavaType 65 | } 66 | 67 | impl Method { 68 | pub fn new(name: String) -> Method { 69 | Method { 70 | name: name, 71 | return_type: JavaType::Void 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /jvmti/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | #[macro_use] 3 | extern crate lazy_static; 4 | extern crate time; 5 | extern crate toml; 6 | #[macro_use] 7 | extern crate serde_derive; 8 | 9 | use agent::Agent; 10 | use bytecode::printer::ClassfilePrinter; 11 | use bytecode::classfile::Constant; 12 | use bytecode::io::ClassWriter; 13 | use config::Config; 14 | use context::static_context; 15 | use instrumentation::asm::transformer::Transformer; 16 | use native::{JavaVMPtr, MutString, VoidPtr, ReturnValue}; 17 | use options::Options; 18 | use runtime::*; 19 | use std::io::Cursor; 20 | use thread::Thread; 21 | use util::stringify; 22 | 23 | pub mod agent; 24 | pub mod bytecode; 25 | pub mod capabilities; 26 | pub mod class; 27 | pub mod config; 28 | pub mod context; 29 | pub mod emulator; 30 | pub mod environment; 31 | pub mod error; 32 | pub mod event; 33 | pub mod event_handler; 34 | pub mod instrumentation; 35 | pub mod mem; 36 | pub mod method; 37 | pub mod native; 38 | pub mod options; 39 | pub mod runtime; 40 | pub mod thread; 41 | pub mod util; 42 | pub mod version; 43 | -------------------------------------------------------------------------------- /jvmti/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | use std::env; 4 | use std::fs::File; 5 | //use std::io::{stdout}; 6 | 7 | use jvmti::bytecode::*; 8 | use jvmti::bytecode::printer::*; 9 | 10 | fn main2() { 11 | let class = Classfile::new(); 12 | 13 | if let Ok(mut outfile) = File::create("RustEmpty.class") { 14 | let mut writer = ClassWriter::new(&mut outfile); 15 | let _ = writer.write_class(&class); 16 | } 17 | } 18 | 19 | // The main program is a simple interface to access the bytecode parsing and generating 20 | // functionality and as such, it's not intended for actual use. 21 | fn main() { 22 | if let (Some(action), Some(class_name)) = (env::args().nth(1), env::args().nth(2)) { 23 | match File::open(class_name.clone()) { 24 | Ok(mut file) => { 25 | match ClassReader::read_class(&mut file) { 26 | Ok(class) => { 27 | match action.as_str() { 28 | "read" => println!("{}", format!("{:#?}", class)), 29 | "print" => println!("{}", ClassfilePrinter::render_lines(&class).iter().map(|line| format!("{}\n", line)).fold(String::new(), |mut acc, x| { acc.push_str(x.as_str()); acc})), 30 | "counts" => println!("Class: {} Field count: {} Method count: {}", class_name, class.fields.len(), class.methods.len()), 31 | "methods" => show_methods(class, class_name), 32 | "write" => write_class(&class), 33 | _ => println!("Unknown action: {}", action) 34 | } 35 | }, 36 | Err(err) => assert!(false, "{}", err) 37 | } 38 | 39 | }, 40 | Err(err) => assert!(false, "{}", err) 41 | } 42 | } else { 43 | println!("Invalid arguments. Usage: jvmti [read|write] ") 44 | } 45 | } 46 | 47 | fn write_class(class: &Classfile) { 48 | if let Ok(mut outfile) = File::create(format!("{}.out.class", env::args().nth(2).unwrap_or(String::from("tmp.out.class")))) { 49 | //let mut out = stdout(); 50 | let mut writer = ClassWriter::new(&mut outfile); 51 | let _ = writer.write_class(class); 52 | } else { 53 | println!("Can't open output file"); 54 | } 55 | } 56 | 57 | fn show_methods(class: Classfile, class_name: String ) { 58 | class.methods.iter().map(|method| { 59 | method.attributes.iter().map(|a| { 60 | match a { 61 | &jvmti::bytecode::Attribute::Code { max_stack: _, max_locals: _, code: _, exception_table: _, ref attributes } => { 62 | attributes.iter().map(|b| { 63 | match b { 64 | &jvmti::bytecode::Attribute::LineNumberTable(ref table) => { 65 | if table.len() > 1 { 66 | let first = table[0].line_number; 67 | let last = table[table.len() - 1].line_number; 68 | 69 | let method_name = class.constant_pool.get_utf8_string(method.name_index.idx as u16).unwrap_or(String::from("Unknown")); 70 | 71 | println!("Class: {} Method: {} Length: {}", class_name, method_name, last - first); 72 | } 73 | () 74 | }, 75 | _ => () 76 | } 77 | 78 | }).fold(0, |_, _| 0); 79 | }, 80 | _ => () 81 | }/* 82 | */ 83 | }).fold(0, |_, _| 0); 84 | }).fold(0, |_, _| 0); 85 | } 86 | -------------------------------------------------------------------------------- /jvmti/src/mem.rs: -------------------------------------------------------------------------------- 1 | use super::native::MutByteArray; 2 | 3 | pub struct MemoryAllocation { 4 | pub ptr: MutByteArray, 5 | pub len: usize 6 | } 7 | -------------------------------------------------------------------------------- /jvmti/src/method.rs: -------------------------------------------------------------------------------- 1 | use super::native::JavaMethod; 2 | 3 | pub struct MethodId { 4 | pub native_id: JavaMethod 5 | } 6 | 7 | pub struct Method { 8 | pub id: MethodId 9 | } 10 | 11 | pub struct MethodSignature { 12 | pub name: String 13 | } 14 | 15 | impl MethodSignature { 16 | 17 | pub fn new(raw_signature: String) -> MethodSignature { 18 | MethodSignature { name: raw_signature } 19 | } 20 | 21 | pub fn unknown() -> MethodSignature { 22 | MethodSignature { name: "".to_string() } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jvmti/src/options.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | /// 4 | /// Represents the possible program options configured from command-line arguments. 5 | /// 6 | pub struct Options { 7 | pub agent_id: String, 8 | pub custom_args: HashMap, 9 | pub config_location: Option 10 | } 11 | 12 | impl Options { 13 | 14 | /// Turn a string of command-line arguments into an `Options` instance 15 | pub fn parse(opt_args: String) -> Options { 16 | let mut options = Options::default(); 17 | 18 | if opt_args.len() > 0 { 19 | for arg in opt_args.split(",") { 20 | match arg.find('=') { 21 | Some(position) => { 22 | let (key, value) = arg.split_at(position); 23 | Options::parse_key_value(&mut options, key, &value[1..value.len()]); 24 | }, 25 | None => { Options::parse_directive(&mut options, arg); } 26 | } 27 | } 28 | } 29 | 30 | options 31 | } 32 | 33 | fn parse_key_value(options: &mut Options, key: &str, value: &str) { 34 | println!("Parsing key: {} -> {}", key, value); 35 | match key { 36 | "agentid" => { options.agent_id = value.to_string(); }, 37 | "config" => { options.config_location = Some(value.to_string()); }, 38 | _ => { options.custom_args.insert(key.to_string(), value.to_string()); } 39 | } 40 | } 41 | 42 | fn parse_directive(options: &mut Options, directive: &str) { 43 | match directive { 44 | _ => options.custom_args.insert(directive.to_string(), "".to_string()) 45 | }; 46 | } 47 | 48 | /// Return the default configuration options 49 | pub fn default() -> Options { 50 | Options { 51 | agent_id: "jvmti".to_string(), 52 | custom_args: HashMap::new(), 53 | config_location: None 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /jvmti/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use super::bytecode::Classfile; 2 | use super::class::{ClassId, ClassSignature}; 3 | use super::method::{MethodId, MethodSignature}; 4 | use super::thread::Thread; 5 | 6 | pub trait RuntimeEvent { 7 | } 8 | 9 | pub struct ObjectAllocationEvent { 10 | pub class_id: ClassId, 11 | pub thread: Thread, 12 | pub size: i64 13 | } 14 | 15 | pub struct ObjectFreeEvent { 16 | 17 | } 18 | 19 | pub struct MethodInvocationEvent { 20 | pub method_id: MethodId, 21 | pub method_sig: MethodSignature, 22 | pub class_sig: ClassSignature, 23 | pub thread: Thread 24 | } 25 | 26 | impl RuntimeEvent for ObjectAllocationEvent {} 27 | impl RuntimeEvent for MethodInvocationEvent {} 28 | 29 | pub struct ClassFileLoadEvent { 30 | pub class_name: String, 31 | pub class: Classfile 32 | } 33 | 34 | impl RuntimeEvent for ClassFileLoadEvent {} 35 | -------------------------------------------------------------------------------- /jvmti/src/thread.rs: -------------------------------------------------------------------------------- 1 | use super::native::JavaThread; 2 | 3 | /// 4 | /// Represents a link between a JVM thread and the Rust code calling the JVMTI API. 5 | /// 6 | #[derive(Eq, PartialEq, Hash, Clone)] 7 | pub struct ThreadId { 8 | pub native_id: JavaThread, 9 | } 10 | 11 | /// Marker trait implementation for `Send` 12 | unsafe impl Send for ThreadId { } 13 | 14 | /// Marker trait implementation for `Sync` 15 | unsafe impl Sync for ThreadId { } 16 | 17 | pub struct Thread { 18 | pub id: ThreadId, 19 | pub name: String, 20 | pub priority: u32, 21 | pub is_daemon: bool 22 | } 23 | -------------------------------------------------------------------------------- /jvmti/src/util.rs: -------------------------------------------------------------------------------- 1 | use super::native::RawString; 2 | use std::ffi::CStr; 3 | use std::ptr; 4 | 5 | /// 6 | /// Turns a C-style string pointer into a String instance. If the string pointer points to NULL, 7 | /// then a "(NULL)" string will be returned. 8 | /// 9 | pub fn stringify(input: RawString) -> String { 10 | unsafe { 11 | if input != ptr::null_mut() { 12 | match CStr::from_ptr(input).to_str() { 13 | Ok(string) => string.to_string(), 14 | Err(_) => "(UTF8-ERROR)".to_string() 15 | } 16 | } else { 17 | "(NULL)".to_string() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jvmti/src/version.rs: -------------------------------------------------------------------------------- 1 | use super::native::jvmti_native::*; 2 | 3 | /// 4 | /// Represents a Java API version structure. 5 | /// 6 | #[derive(Eq, PartialEq, Debug)] 7 | pub struct VersionNumber { 8 | pub major_version: u16, 9 | pub minor_version: u8, 10 | pub micro_version: u8 11 | } 12 | 13 | impl VersionNumber { 14 | 15 | /// 16 | /// Parse an unsigned 32-bit integer as a Java API version number 17 | /// 18 | pub fn from_u32(version: &u32) -> VersionNumber { 19 | let major_version = ((version & JVMTI_VERSION_MASK_MAJOR) >> JVMTI_VERSION_SHIFT_MAJOR) as u16; 20 | let minor_version = ((version & JVMTI_VERSION_MASK_MINOR) >> JVMTI_VERSION_SHIFT_MINOR) as u8; 21 | let micro_version = ((version & JVMTI_VERSION_MASK_MICRO) >> JVMTI_VERSION_SHIFT_MICRO) as u8; 22 | 23 | VersionNumber { 24 | major_version: major_version, 25 | minor_version: minor_version, 26 | micro_version: micro_version 27 | } 28 | } 29 | 30 | /// Return a magic version number that is most likely not used anywhere else to indicate an unknown 31 | /// version number (for cases where the version number cannot be precisely determined) 32 | pub fn unknown() -> VersionNumber { 33 | VersionNumber { major_version: 0x7FFF, minor_version: 0x8F, micro_version: 0x9F } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jvmti/tests/agent.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::agent::Agent; 7 | use jvmti::emulator::JVMEmulator; 8 | use jvmti::runtime::MethodInvocationEvent; 9 | use jvmti::thread::Thread; 10 | use jvmti::version::VersionNumber; 11 | 12 | #[test] 13 | fn agents_are_fucking_even_working() { 14 | let emulator = JVMEmulator::new(); 15 | let agent = Agent::new_from(Box::new(emulator)); 16 | let version = agent.get_version(); 17 | 18 | assert_eq!(0x7FFF, version.major_version); 19 | } 20 | 21 | #[test] 22 | fn agents_are_initialized_with_empty_capabilities() { 23 | let emulator = JVMEmulator::new(); 24 | let agent = Agent::new_from(Box::new(emulator)); 25 | 26 | assert_eq!(false, agent.capabilities.can_suspend); 27 | assert_eq!(false, agent.capabilities.can_pop_frame); 28 | assert_eq!(false, agent.capabilities.can_generate_monitor_events); 29 | assert_eq!(false, agent.capabilities.can_generate_method_entry_events); 30 | assert_eq!(false, agent.capabilities.can_generate_method_exit_events); 31 | assert_eq!(false, agent.capabilities.can_generate_vm_object_alloc_events); 32 | assert_eq!(false, agent.capabilities.can_generate_breakpoint_events); 33 | // TODO this test is not complete at all. surprisingly 34 | } 35 | 36 | #[test] 37 | fn agents_respond_to_shutdown() { 38 | let emulator = JVMEmulator::new(); 39 | let agent = Agent::new_from(Box::new(emulator)); 40 | agent.shutdown(); 41 | } 42 | 43 | #[test] 44 | fn agents_provide_with_version_numbers() { 45 | let emulator = JVMEmulator::new(); 46 | let agent = Agent::new_from(Box::new(emulator)); 47 | let version = agent.get_version(); 48 | let unknown_version = VersionNumber::unknown(); 49 | assert_eq!(unknown_version.major_version, version.major_version); 50 | assert_eq!(unknown_version.minor_version, version.minor_version); 51 | assert_eq!(unknown_version.micro_version, version.micro_version); 52 | } 53 | 54 | #[test] 55 | fn callbacks_trigger_capabilities() { 56 | let emulator = JVMEmulator::new(); 57 | let mut agent = Agent::new_from(Box::new(emulator)); 58 | 59 | agent.on_method_entry(Some(test_on_method_entry)); 60 | assert_eq!(true, agent.capabilities.can_generate_method_entry_events); 61 | agent.on_method_entry(None); 62 | assert_eq!(false, agent.capabilities.can_generate_method_entry_events); 63 | 64 | assert_eq!(false, agent.capabilities.can_generate_monitor_events); 65 | agent.on_monitor_wait(Some(test_on_monitor_events)); 66 | assert_eq!(true, agent.capabilities.can_generate_monitor_events); 67 | agent.on_monitor_waited(Some(test_on_monitor_events)); 68 | assert_eq!(true, agent.capabilities.can_generate_monitor_events); 69 | agent.on_monitor_contended_enter(Some(test_on_monitor_events)); 70 | assert_eq!(true, agent.capabilities.can_generate_monitor_events); 71 | agent.on_monitor_wait(None); 72 | assert_eq!(true, agent.capabilities.can_generate_monitor_events); 73 | agent.on_monitor_waited(None); 74 | assert_eq!(true, agent.capabilities.can_generate_monitor_events); 75 | agent.on_monitor_contended_enter(None); 76 | } 77 | 78 | #[allow(unused_variables)] 79 | fn test_on_method_entry(event: MethodInvocationEvent) { 80 | // this is a callback method for testing purposes 81 | } 82 | 83 | #[allow(unused_variables)] 84 | fn test_on_monitor_events(thread: Thread) { 85 | // this is a callback method for testing purposes 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /jvmti/tests/bc/collections.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | mod class_stream { 6 | use jvmti::bytecode::stream::ClassInputStream; 7 | 8 | #[test] 9 | fn peek_bytes_should_return_the_requested_number_of_bytes() { 10 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 11 | let cs = ClassInputStream::from_vec(&bytes); 12 | 13 | assert_eq!(0, cs.peek_bytes(0)); 14 | assert_eq!(0, cs.peek_bytes(1)); 15 | assert_eq!(1, cs.peek_bytes(2)); 16 | assert_eq!(0x102, cs.peek_bytes(3)); 17 | assert_eq!(0x10203, cs.peek_bytes(4)); 18 | assert_eq!(0x1020304, cs.peek_bytes(5)); 19 | assert_eq!(0x102030405, cs.peek_bytes(6)); 20 | assert_eq!(0x10203040506, cs.peek_bytes(7)); 21 | assert_eq!(0x1020304050607, cs.peek_bytes(8)); 22 | } 23 | 24 | #[test] 25 | fn peek_bytes_should_never_move_the_stream_index() { 26 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 27 | let cs = ClassInputStream::from_vec(&bytes); 28 | 29 | cs.peek_bytes(0); 30 | assert_eq!(8, cs.available()); 31 | 32 | cs.peek_bytes(2); 33 | assert_eq!(8, cs.available()); 34 | 35 | cs.peek_bytes(1); 36 | assert_eq!(8, cs.available()); 37 | } 38 | 39 | #[test] 40 | fn read_bytes_should_move_the_stream_index_with_the_requested_amount() { 41 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 42 | let cs = ClassInputStream::from_vec(&bytes); 43 | 44 | assert_eq!(8, cs.available()); 45 | cs.read_bytes(1); 46 | assert_eq!(7, cs.available()); 47 | cs.read_bytes(2); 48 | assert_eq!(5, cs.available()); 49 | cs.read_bytes(3); 50 | assert_eq!(2, cs.available()); 51 | cs.read_bytes(1); 52 | assert_eq!(1, cs.available()); 53 | } 54 | 55 | #[test] 56 | fn mark_should_not_change_the_current_index() { 57 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 58 | let cs = ClassInputStream::from_vec(&bytes); 59 | 60 | assert_eq!(8, cs.available()); 61 | cs.read_bytes(4); 62 | assert_eq!(4, cs.available()); 63 | cs.mark(); 64 | assert_eq!(4, cs.available()); 65 | cs.mark(); 66 | assert_eq!(4, cs.available()); 67 | cs.read_bytes(2); 68 | assert_eq!(2, cs.available()); 69 | cs.mark(); 70 | cs.mark(); 71 | assert_eq!(2, cs.available()); 72 | 73 | } 74 | 75 | #[test] 76 | fn mark_should_override_the_previous_call_positions() { 77 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 78 | let cs = ClassInputStream::from_vec(&bytes); 79 | 80 | assert_eq!(8, cs.available()); 81 | cs.read_bytes(4); 82 | assert_eq!(4, cs.available()); 83 | cs.mark(); 84 | cs.read_bytes(2); 85 | assert_eq!(2, cs.available()); 86 | cs.mark(); 87 | cs.read_bytes(2); 88 | assert_eq!(0, cs.available()); 89 | cs.reset(); 90 | 91 | let or = cs.read_bytes(2); 92 | 93 | assert!(or.is_some()); 94 | } 95 | 96 | #[test] 97 | fn reset_should_rewind_the_stream_to_the_lastly_marked_position() { 98 | let bytes: Vec = vec![ 0, 1, 2, 3, 4, 5, 6, 7 ]; 99 | let cs = ClassInputStream::from_vec(&bytes); 100 | 101 | cs.read_bytes(4); 102 | cs.mark(); 103 | cs.read_bytes(2); 104 | cs.reset(); 105 | assert_eq!(4, cs.available()); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /jvmti/tests/bc/constant.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | mod constant_pool { 7 | 8 | use jvmti::bytecode::constant::*; 9 | 10 | #[test] 11 | fn get_idx_should_consider_long_entries() { 12 | let cp_empty = ConstantPool::from_vec(vec![]); 13 | assert_eq!(0, cp_empty.len()); 14 | 15 | // Inputs starting with a placeholder should be unmodified 16 | let cp_ph = ConstantPool::from_vec(vec![ Constant::Placeholder ]); 17 | assert_eq!(1, cp_ph.len()); 18 | 19 | let cp_valid1 = ConstantPool::from_vec(vec![ 20 | Constant::Placeholder, 21 | Constant::Integer(14), 22 | Constant::Long(3), 23 | Constant::String(9) 24 | ]); 25 | 26 | assert_eq!(4, cp_valid1.len()); 27 | assert!(cp_valid1.get_idx(0).is_none()); 28 | assert!(cp_valid1.get_idx(5).is_none()); 29 | assert!(cp_valid1.get_idx(1).is_some()); 30 | 31 | match cp_valid1.get_idx(1) { 32 | Some(&Constant::Placeholder) => assert!(true), 33 | _ => assert!(false) 34 | } 35 | match cp_valid1.get_idx(2) { 36 | Some(&Constant::Integer(14)) => assert!(true), 37 | _ => assert!(false) 38 | } 39 | match cp_valid1.get_idx(3) { 40 | Some(&Constant::Long(3)) => assert!(true), 41 | _ => assert!(false) 42 | } 43 | match cp_valid1.get_idx(4) { 44 | Some(&Constant::String(9)) => assert!(true), 45 | _ => assert!(false) 46 | } 47 | 48 | let cp_valid2 = ConstantPool::from_vec(vec![ 49 | Constant::Integer(7), 50 | Constant::Long(15), 51 | Constant::String(3) 52 | ]); 53 | 54 | assert_eq!(4, cp_valid2.len()); 55 | 56 | match cp_valid2.get_idx(1) { 57 | Some(&Constant::Integer(7)) => assert!(true), 58 | _ => assert!(false) 59 | } 60 | match cp_valid2.get_idx(2) { 61 | Some(&Constant::Long(15)) => assert!(true), 62 | _ => assert!(false) 63 | } 64 | match cp_valid2.get_idx(3) { 65 | Some(&Constant::Placeholder) => assert!(true), 66 | _ => assert!(false) 67 | } 68 | match cp_valid2.get_idx(4) { 69 | Some(&Constant::String(3)) => assert!(true), 70 | _ => assert!(false) 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /jvmti/tests/bc/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | mod collections; 4 | mod constant; 5 | mod stream; 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | 10 | mod class_reader { 11 | 12 | use jvmti::bytecode::classfile::*; 13 | use jvmti::bytecode::constant::*; 14 | use jvmti::bytecode::ClassReader; 15 | 16 | fn simple_class() -> &'static [u8] { 17 | include_bytes!("../../Simple.class") 18 | } 19 | 20 | fn test_class() -> &'static [u8] { 21 | include_bytes!("../../Test.class") 22 | } 23 | 24 | #[test] 25 | fn read_bytes_reads_simple_class_version_number_correctly() { 26 | let result = ClassReader::read_array(simple_class()); 27 | 28 | assert!(result.is_ok(), format!("Error: {}", result.err().unwrap())); 29 | let class = result.ok().unwrap(); 30 | 31 | assert_eq!(52, class.version.major_version); 32 | assert_eq!(0, class.version.minor_version); 33 | } 34 | 35 | #[test] 36 | fn read_bytes_reads_simple_constant_pool_correctly() { 37 | let result = ClassReader::read_array(simple_class()); 38 | 39 | assert!(result.is_ok(), format!("Error: {}", result.err().unwrap())); 40 | let class = result.ok().unwrap(); 41 | 42 | assert!(class.constant_pool.get(&class.this_class).is_some()); 43 | 44 | let this_class: &Constant = class.constant_pool.get(&class.this_class).unwrap(); 45 | 46 | match this_class { 47 | &Constant::Class(idx) => { 48 | assert!(class.constant_pool.get(&ConstantPoolIndex::of(idx)).is_some(), format!("Referenced constant missing: {}", idx)); 49 | }, 50 | _ => assert!(false, format!("{:?}", this_class)) 51 | } 52 | 53 | } 54 | 55 | #[test] 56 | fn read_bytes_reads_simple_access_flags_correctly() { 57 | let result = ClassReader::read_array(simple_class()); 58 | 59 | assert!(result.is_ok(), format!("Error: {}", result.err().unwrap())); 60 | let class = result.ok().unwrap(); 61 | 62 | assert!(class.access_flags.has_flag(ClassAccessFlags::PUBLIC as u16)); 63 | assert!(class.access_flags.has_flag(ClassAccessFlags::SUPER as u16)); 64 | assert!(!class.access_flags.has_flag(ClassAccessFlags::INTERFACE as u16)); 65 | assert!(!class.access_flags.has_flag(ClassAccessFlags::ENUM as u16)); 66 | } 67 | 68 | #[test] 69 | fn read_bytes_reads_interfaces_correctly() { 70 | let result = ClassReader::read_array(simple_class()); 71 | 72 | assert!(result.is_ok(), format!("Error: {}", result.err().unwrap())); 73 | let class = result.ok().unwrap(); 74 | 75 | assert_eq!(0, class.interfaces.len()); 76 | } 77 | 78 | #[test] 79 | fn read_bytes_reads_test_class_correctly() { 80 | let reader = ClassReader::read_array(test_class()); 81 | 82 | assert!(reader.is_ok()); 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /jvmti/tests/bc/stream.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::bytecode::stream::ClassInputStream; 7 | use jvmti::bytecode::stream::ClassOutputStream; 8 | use jvmti::bytecode::stream::WriteChunks; 9 | use jvmti::bytecode::classfile::ClassfileVersion; 10 | 11 | #[test] 12 | fn write_u8_writes_a_single_byte_at_the_end_of_the_stream() { 13 | let mut os: ClassOutputStream = ClassOutputStream::new(); 14 | os.write_u8(14); 15 | 16 | assert_eq!(vec![ 14 ], os.to_vec()); 17 | } 18 | 19 | #[test] 20 | fn write_u16_writes_two_bytes_at_the_end_of_the_stream() { 21 | let mut os: ClassOutputStream = ClassOutputStream::new(); 22 | os.write_u16(0xABCD); 23 | 24 | assert_eq!(vec![ 0xAB, 0xCD ], os.to_vec()); 25 | } 26 | 27 | #[test] 28 | fn write_u64_writes_eight_bytes_at_the_end_of_the_stream() { 29 | let mut os: ClassOutputStream = ClassOutputStream::new(); 30 | os.write_u64(0x1122334455667788); 31 | 32 | assert_eq!(vec![ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 ], os.to_vec()); 33 | } 34 | 35 | #[test] 36 | fn write_magic_bytes_writes_cafebabe() { 37 | let mut os: ClassOutputStream = ClassOutputStream::new(); 38 | os.write_magic_bytes(); 39 | 40 | assert_eq!(vec![ 0xCA, 0xFE, 0xBA, 0xBE ], os.to_vec()); 41 | } 42 | 43 | #[test] 44 | fn write_version_number_writes_the_provided_version_number() { 45 | let versions: Vec<(ClassfileVersion, Vec)> = vec![ 46 | (ClassfileVersion::new(52, 0), vec![ 0x00, 0x00, 0x00, 0x34 ]), 47 | (ClassfileVersion::new(0, 0), vec![ 0x00, 0x00, 0x00, 0x0 ]), 48 | (ClassfileVersion::new(256, 256), vec![ 0x01, 0x0, 0x01, 0x0 ]), 49 | ]; 50 | 51 | for (version, expected) in versions { 52 | let mut os: ClassOutputStream = ClassOutputStream::new(); 53 | os.write_version_number(&version); 54 | 55 | assert_eq!(expected, os.to_vec()); 56 | 57 | } 58 | } 59 | 60 | #[test] 61 | fn read_constant_pool_reads_exactly_the_desired_number_of_constants() { 62 | let inputs: Vec<(Vec, usize, usize)> = vec![ 63 | // Empty constant pool 64 | (vec![ 0, 1 ], 0, 0), 65 | // Empty constant pool with overflowing bytes 66 | (vec![ 0, 1, 0xf, 0xf, 0xf ], 0, 3), 67 | // Single integer constant 68 | (vec![ 0, 2, 3, 1, 2, 3, 4 ], 1, 0), 69 | // Single integer constant with overflowing bytes 70 | (vec![ 0, 2, 3, 1, 2, 3, 4, 0xf, 0xf, 0xf ], 1, 3), 71 | // Two integer constants 72 | (vec![ 0, 3, 3, 1, 1, 1, 1, 3, 2, 2, 2, 2 ], 2, 0), 73 | // One long constant 74 | (vec![ 0, 3, 5, 1, 1, 1, 1, 1, 1, 1, 1 ], 2, 0), 75 | // Two long constants 76 | (vec![ 0, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 5, 2, 2, 2, 2, 2, 2, 2, 2 ], 4, 0), 77 | // An integer and a long constant 78 | (vec![ 0, 4, 3, 1, 1, 1, 1, 5, 2, 2, 2, 2, 2, 2, 2, 2 ], 3, 0), 79 | // A long and an integer constant 80 | (vec![ 0, 4, 5, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 2, 2 ], 3, 0), 81 | // long, int, long 82 | (vec![ 0, 6, 5, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 2, 2, 5, 3, 3, 3, 3, 3, 3, 3, 3 ], 5, 0), 83 | ]; 84 | 85 | for (bytes, expected_count, expected_avail) in inputs { 86 | let is: ClassInputStream = ClassInputStream::from_vec(&bytes); 87 | 88 | let result = is.read_constant_pool(); 89 | 90 | assert!(result.is_ok(), format!("Bytes {:?} Error: {:?}", bytes, result.err().unwrap())); 91 | 92 | let cp = result.ok().unwrap(); 93 | 94 | //assert_eq!(expected_count, cp.len()); 95 | assert!(expected_count == cp.len(), format!("Bytes {:?} Expected: {} Found: {}", bytes, expected_count, cp.len())); 96 | assert!(expected_avail == is.available(), format!("Bytes {:?} Expected: {} Available: {}", bytes, expected_avail, is.available())); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /jvmti/tests/bytecode/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::bytecode::*; 7 | use std::fs::File; 8 | use std::io::{ Cursor, Read, Write, Error }; 9 | 10 | #[test] 11 | fn test_read_simple() { 12 | match File::open("Simple.class") { 13 | Ok(mut file) => { 14 | match ClassReader::read_class(&mut file) { 15 | Ok(class) => { 16 | assert!(true, format!("{:?}", class)); 17 | }, 18 | Err(err) => assert!(false, format!("{:?}", err)) 19 | } 20 | 21 | }, 22 | Err(err) => assert!(false, format!("{:?}", err)) 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_read_test() { 28 | match File::open("Test.class") { 29 | Ok(mut file) => { 30 | match ClassReader::read_class(&mut file) { 31 | Ok(class) => { 32 | assert!(true, format!("{:#?}", class.methods)); 33 | }, 34 | Err(err) => assert!(false, format!("{:?}", err)) 35 | } 36 | 37 | }, 38 | Err(err) => assert!(false, format!("{:?}", err)) 39 | } 40 | } 41 | 42 | #[test] 43 | fn test_read_n() { 44 | let mut target: Vec = vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; 45 | let mut cursor = Cursor::new(&mut target); 46 | let mut reader = BlockReader::new(&mut cursor); 47 | 48 | let r1 = reader.read_n(4); 49 | assert_eq!(vec![1, 2, 3, 4], r1.ok().unwrap()); 50 | 51 | let r2 = reader.read_n(4); 52 | assert_eq!(vec![5, 6, 7, 8], r2.ok().unwrap()); 53 | 54 | let r3 = reader.read_u8(); 55 | assert_eq!(9, r3.ok().unwrap()); 56 | assert!(reader.read_u8().is_err()); 57 | } 58 | 59 | #[test] 60 | fn test_read_write_roundtrip() { 61 | let class: Classfile = Classfile { 62 | version: ClassfileVersion::new(52, 0), 63 | constant_pool: ConstantPool::new(vec![ 64 | Constant::Placeholder, 65 | Constant::Integer(14), 66 | Constant::Long(5), 67 | Constant::Placeholder, 68 | Constant::Utf8("AAAAAA".to_string().into_bytes()), 69 | Constant::Class(ConstantPoolIndex::new(3)) 70 | ]), 71 | access_flags: AccessFlags::of(0x000F), 72 | this_class: ConstantPoolIndex::new(1), 73 | super_class: ConstantPoolIndex::new(2), 74 | interfaces: vec![ 75 | ConstantPoolIndex::new(7), 76 | ConstantPoolIndex::new(8) 77 | ], 78 | fields: vec![ 79 | Field { access_flags: AccessFlags::of(0x0011), name_index: ConstantPoolIndex::new(70), descriptor_index: ConstantPoolIndex::new(71), attributes: vec![] } 80 | ], 81 | methods: vec![], 82 | attributes: vec![ 83 | Attribute::RawAttribute { name_index: ConstantPoolIndex::new(4), info: vec![ 1, 2, 3, 4 ] }, 84 | Attribute::RawAttribute { name_index: ConstantPoolIndex::new(4), info: vec![ 11, 12, 13, 14, 15 ] } 85 | ] 86 | }; 87 | 88 | let r1_version = (class.version.major_version, class.version.minor_version); 89 | let r1_cp_len = class.constant_pool.cp_len(); 90 | let r1_aflag = class.access_flags.flags; 91 | let r1_this_idx = class.this_class.idx; 92 | let r1_super_idx = class.super_class.idx; 93 | let r1_ifs_len = class.interfaces.len(); 94 | let r1_fields_len = class.fields.len(); 95 | let r1_methods_len = class.methods.len(); 96 | let r1_attributes_len = class.attributes.len(); 97 | 98 | let mut target: Vec = vec![]; 99 | { 100 | let mut writer: ClassWriter = ClassWriter::new(&mut target); 101 | let write_result = writer.write_class(&class); 102 | 103 | match write_result { 104 | Ok(_) => assert!(true), 105 | Err(err) => assert!(false, format!("{:?}", err)) 106 | } 107 | } 108 | // assert!(false, format!("{:?}", target)); 109 | { 110 | let read_result: Result = ClassReader::read_class(&mut Cursor::new(&mut target)); 111 | 112 | assert!(read_result.is_ok(), format!("{:?}", read_result.err())); 113 | 114 | let read_class = read_result.ok().unwrap(); 115 | { 116 | // assert!(false, format!("{:?} {:?}", target, read_class.constant_pool.constants)); 117 | } 118 | assert_eq!(r1_version.0, read_class.version.major_version); 119 | assert_eq!(r1_version.1, read_class.version.minor_version); 120 | assert_eq!(r1_cp_len, read_class.constant_pool.cp_len()); 121 | assert_eq!(r1_aflag, read_class.access_flags.flags); 122 | assert_eq!(r1_this_idx, read_class.this_class.idx); 123 | assert_eq!(r1_super_idx, read_class.super_class.idx); 124 | assert_eq!(r1_ifs_len, read_class.interfaces.len()); 125 | assert_eq!(r1_fields_len, read_class.fields.len()); 126 | assert_eq!(r1_methods_len, read_class.methods.len()); 127 | assert_eq!(r1_attributes_len, read_class.attributes.len()); 128 | } 129 | assert!(true, format!("{:?}", target)); 130 | } 131 | 132 | #[test] 133 | fn test_cursor_read_usage() { 134 | let mut cursor = Cursor::new(vec![ 1, 2, 3, 4 as u8 ]); 135 | 136 | let mut input = [ 0, 0 ]; 137 | 138 | match cursor.read(&mut input) { 139 | Ok(_) => { 140 | assert_eq!([ 1, 2 ], input) 141 | }, 142 | _ => assert!(false) 143 | } 144 | 145 | match cursor.read(&mut input) { 146 | Ok(_) => { 147 | assert_eq!([ 3, 4 ], input) 148 | }, 149 | _ => assert!(false) 150 | } 151 | } 152 | 153 | #[test] 154 | fn test_cursor_write_usage() { 155 | let mut cursor: Cursor> = Cursor::new(vec![]); 156 | 157 | match cursor.write(&[ 1, 2 ]) { 158 | Ok(_) => assert!(true), 159 | _ => assert!(false) 160 | } 161 | 162 | match cursor.write(&[ 3, 4 ]) { 163 | Ok(_) => assert!(true), 164 | _ => assert!(false) 165 | } 166 | 167 | let output = cursor.into_inner(); 168 | 169 | assert_eq!(vec![ 1, 2, 3, 4 ], output); 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /jvmti/tests/capabilities.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::capabilities::Capabilities; 7 | 8 | #[test] 9 | fn agent_capabilities_are_generated_with_capabilities_off() { 10 | let caps = Capabilities::new(); 11 | assert_eq!(false, caps.can_pop_frame); 12 | assert_eq!(false, caps.can_redefine_classes); 13 | assert_eq!(false, caps.can_get_bytecodes); 14 | assert_eq!(false, caps.can_generate_monitor_events); 15 | assert_eq!(false, caps.can_generate_exception_events); 16 | } 17 | 18 | #[test] 19 | fn agent_capabilities_are_reflected_in_native_capabilities() { 20 | let mut caps = Capabilities::new(); 21 | caps.can_pop_frame = true; 22 | 23 | let native_caps = caps.to_native(); 24 | let recaps = Capabilities::from_native(&native_caps); 25 | 26 | assert_eq!(true, recaps.can_pop_frame); 27 | assert_eq!(false, recaps.can_redefine_classes); 28 | } 29 | 30 | #[test] 31 | fn merge_combines_enabled_flags_from_both_capabilities() { 32 | let mut caps1 = Capabilities::new(); 33 | let mut caps2 = Capabilities::new(); 34 | 35 | caps1.can_pop_frame = true; 36 | caps2.can_generate_monitor_events = true; 37 | 38 | let caps_result = caps1.merge(&caps2); 39 | 40 | assert_eq!(true, caps_result.can_pop_frame); 41 | assert_eq!(true, caps_result.can_generate_monitor_events); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jvmti/tests/class.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::class::{Class, ClassId, JavaType}; 7 | use std::ptr; 8 | 9 | #[test] 10 | fn primitive_types_are_parsed_correctly() { 11 | assert_eq!(Some(JavaType::Void), JavaType::parse("V")); 12 | assert_eq!(Some(JavaType::Int), JavaType::parse("I")); 13 | assert_eq!(Some(JavaType::Boolean), JavaType::parse("Z")); 14 | assert_eq!(Some(JavaType::Short), JavaType::parse("S")); 15 | assert_eq!(Some(JavaType::Long), JavaType::parse("J")); 16 | } 17 | 18 | #[test] 19 | fn arrays_are_parsed_correctly() { 20 | assert_eq!(Some(JavaType::Array(Box::new(JavaType::Int))), JavaType::parse("[I")); 21 | assert_eq!(Some(JavaType::Array(Box::new(JavaType::Boolean))), JavaType::parse("[Z")); 22 | assert_eq!(Some(JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Int))))), JavaType::parse("[[I")); 23 | } 24 | 25 | #[test] 26 | fn classes_are_parsed_correctly() { 27 | assert_eq!(Some(JavaType::Class("Lso/blacklight/Test;")), JavaType::parse("Lso/blacklight/Test;")); 28 | assert_eq!(Some(JavaType::Class("LTest;")), JavaType::parse("LTest;")); 29 | } 30 | 31 | #[test] 32 | fn arrays_of_classes_are_parsed() { 33 | assert_eq!(Some(JavaType::Array(Box::new(JavaType::Class("Lso/blacklight/Test;")))), JavaType::parse("[Lso/blacklight/Test;")); 34 | } 35 | 36 | #[test] 37 | fn java_types_are_stringified() { 38 | assert_eq!("void", JavaType::to_string(&JavaType::Void)); 39 | assert_eq!("int", JavaType::to_string(&JavaType::Int)); 40 | assert_eq!("int[]", JavaType::to_string(&JavaType::Array(Box::new(JavaType::Int)))); 41 | assert_eq!("so.blacklight.Test", JavaType::to_string(&JavaType::Class("Lso/blacklight/Test;"))); 42 | assert_eq!("so.blacklight.Test[]", JavaType::to_string(&JavaType::Array(Box::new(JavaType::Class("Lso/blacklight/Test;"))))); 43 | assert_eq!("short[][]", JavaType::to_string(&JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Short)))))); 44 | } 45 | 46 | #[test] 47 | fn class_to_string_returns_the_fully_qualified_class_name() { 48 | assert_eq!("so.blacklight.Test", Class::new(ClassId { native_id: ptr::null_mut() }, JavaType::Class("Lso/blacklight/Test;")).to_string()); 49 | assert_eq!("so.blacklight.Test$1", Class::new(ClassId { native_id: ptr::null_mut() }, JavaType::Class("Lso/blacklight/Test$1;")).to_string()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jvmti/tests/context.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | #[test] 7 | fn test() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jvmti/tests/emulator.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | extern crate libc; 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | 7 | use jvmti::capabilities::Capabilities; 8 | use jvmti::emulator::JVMEmulator; 9 | use jvmti::environment::jvm::JVMF; 10 | use jvmti::environment::jvmti::JVMTI; 11 | use jvmti::version::VersionNumber; 12 | 13 | #[test] 14 | fn get_environment_returns_a_valid_environment() { 15 | let emu = JVMEmulator::new(); 16 | 17 | assert!(emu.get_environment().is_ok()); 18 | let env = emu.get_environment().ok().unwrap(); 19 | assert_eq!(VersionNumber::unknown(), env.get_version_number()); 20 | } 21 | 22 | #[test] 23 | fn get_version_number_returns_unknown_version() { 24 | let emu = JVMEmulator::new(); 25 | 26 | assert!(emu.get_environment().is_ok()); 27 | let env = emu.get_environment().ok().unwrap(); 28 | assert_eq!(VersionNumber::unknown(), env.get_version_number()); 29 | } 30 | 31 | #[test] 32 | fn add_capabilities_retains_the_previously_added_capabilities() { 33 | let mut emu = JVMEmulator::new(); 34 | 35 | let mut capabilities = Capabilities::new(); 36 | capabilities.can_get_bytecodes = true; 37 | 38 | assert_eq!(false, emu.capabilities.can_get_bytecodes); 39 | assert!(emu.add_capabilities(&capabilities).is_ok()); 40 | assert_eq!(true, emu.capabilities.can_get_bytecodes); 41 | 42 | capabilities.can_get_bytecodes = false; 43 | capabilities.can_suspend = true; 44 | 45 | assert_eq!(false, emu.capabilities.can_suspend); 46 | assert!(emu.add_capabilities(&capabilities).is_ok()); 47 | assert_eq!(true, emu.capabilities.can_suspend); 48 | assert_eq!(true, emu.capabilities.can_get_bytecodes); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jvmti/tests/environment/jvm.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /jvmti/tests/environment/jvmti.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | extern crate libc; 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /jvmti/tests/environment/mod.rs: -------------------------------------------------------------------------------- 1 | mod jvm; 2 | mod jvmti; 3 | -------------------------------------------------------------------------------- /jvmti/tests/event.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::event::EventCallbacks; 7 | 8 | #[test] 9 | fn empty_event_callbacks_are_instantiatable_using_new() { 10 | let ec = EventCallbacks::new(); 11 | assert_eq!(None, ec.method_entry); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jvmti/tests/instrumentation/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | use jvmti::instrumentation::JavaClass; 7 | 8 | #[test] 9 | fn can_create_empty_class() { 10 | let new_class = JavaClass::new(); 11 | 12 | let classfile = new_class.to_classfile(); 13 | 14 | assert_eq!(classfile, classfile); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jvmti/tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | extern crate libc; 3 | 4 | mod bytecode; 5 | mod environment; 6 | mod instrumentation; 7 | 8 | /* 9 | mod agent; 10 | mod capabilities; 11 | mod class; 12 | mod emulator; 13 | mod environment; 14 | mod event; 15 | mod util; 16 | mod version; 17 | */ 18 | -------------------------------------------------------------------------------- /jvmti/tests/options.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::options::Options; 7 | 8 | #[test] 9 | fn options_return_an_options_instance_with_no_uninitialised_values() { 10 | let opts = Options::default(); 11 | 12 | assert!(opts.agent_id.len() > 0); 13 | } 14 | 15 | #[test] 16 | fn options_can_take_an_agenetid_argument() { 17 | let opts = Options::parse("agentid=testid".to_string()); 18 | 19 | assert_eq!("testid", opts.agent_id); 20 | } 21 | 22 | #[test] 23 | fn can_parse_several_comma_separated_arguments() { 24 | let opts = Options::parse("setting1,setting2,agentid=testid,setting3".to_string()); 25 | 26 | assert_eq!("testid", opts.agent_id); 27 | } 28 | 29 | #[test] 30 | fn unknown_arguments_go_into_custom_args() { 31 | let opts = Options::parse("setting1,setting2=true,agentid=testid,setting3".to_string()); 32 | 33 | assert_eq!(true, opts.custom_args.contains_key("setting1")); 34 | assert_eq!(true, opts.custom_args.contains_key("setting2")); 35 | assert_eq!(true, opts.custom_args.contains_key("setting3")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jvmti/tests/util.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::util::stringify; 7 | use jvmti::native::MutString; 8 | use std::ptr; 9 | use std::ffi::CString; 10 | 11 | #[test] 12 | fn stringify_returns_a_meaningful_value_on_null_ptr() { 13 | let tv: MutString = ptr::null_mut(); 14 | let expected = "(NULL)".to_string(); 15 | assert_eq!(expected, stringify(tv)); 16 | } 17 | 18 | #[test] 19 | fn stringify_returns_the_stringified_content_if_its_a_valid_utf8_string() { 20 | let expected = "test"; 21 | let s: MutString = CString::new(expected).unwrap().as_ptr() as *mut i8; 22 | assert_eq!(expected, stringify(s)); 23 | } 24 | 25 | #[test] 26 | fn stringify_returns_an_empty_string_if_the_input_was_an_empty_string() { 27 | let expected = ""; 28 | let s: MutString = CString::new(expected).unwrap().as_ptr() as *mut i8; 29 | assert_eq!(expected, stringify(s)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jvmti/tests/version.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | 6 | use jvmti::version::VersionNumber; 7 | 8 | #[test] 9 | fn valid_version_numbers_should_be_parsed_correctly() { 10 | let mut input: u32 = 0x00000000; 11 | let mut version = VersionNumber::from_u32(&input); 12 | 13 | assert_eq!(0, version.major_version); 14 | assert_eq!(0, version.minor_version); 15 | assert_eq!(0, version.micro_version); 16 | 17 | input = 0x00000010; 18 | version = VersionNumber::from_u32(&input); 19 | 20 | assert_eq!(0, version.major_version); 21 | assert_eq!(0, version.minor_version); 22 | assert_eq!(0x10, version.micro_version); 23 | 24 | input = 0x00002010; 25 | version = VersionNumber::from_u32(&input); 26 | 27 | assert_eq!(0, version.major_version); 28 | assert_eq!(0x20, version.minor_version); 29 | assert_eq!(0x10, version.micro_version); 30 | 31 | input = 0x00302010; 32 | version = VersionNumber::from_u32(&input); 33 | 34 | assert_eq!(0x30, version.major_version); 35 | assert_eq!(0x20, version.minor_version); 36 | assert_eq!(0x10, version.micro_version); 37 | 38 | input = 0x33214310; 39 | version = VersionNumber::from_u32(&input); 40 | 41 | assert_eq!(0x321, version.major_version); 42 | assert_eq!(0x43, version.minor_version); 43 | assert_eq!(0x10, version.micro_version); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /regression-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=./test-data 4 | 5 | if [ ! -z $1 ]; then 6 | BASEDIR=$1 7 | fi 8 | 9 | function clean_test_data { 10 | find $BASEDIR -name "*.out.class" -exec rm -f {} \; 11 | } 12 | 13 | clean_test_data 14 | 15 | CLASSCOUNT=0 16 | FAILCOUNT=0 17 | 18 | find -H ./test-data -name "*.class" | while read CLASSFILE; do 19 | echo "Checking ${CLASSFILE}" 20 | OUTFILE="${CLASSFILE}.out.class" 21 | RESULTS="regression-results" 22 | ./target/release/jvmti write $CLASSFILE 23 | HASHES=`md5 -q $CLASSFILE $OUTFILE | paste -s -d " " -` 24 | read -r -a RESULT <<< $HASHES 25 | 26 | CLASSCOUNT=$((CLASSCOUNT + 1)) 27 | 28 | if [ "${RESULT[0]}" != "${RESULT[1]}" ]; then 29 | FAILCOUNT=$((FAILCOUNT + 1)) 30 | echo " ---------------------- Mismatch found: ${RESULT[0]} ${RESULT[1]} ${CLASSFILE} -----------------------" 31 | javap -v $CLASSFILE > ./tmp-compare-1 32 | javap -v $OUTFILE > ./tmp-compare-2 33 | diff -u ./tmp-compare-1 ./tmp-compare-2 >> $RESULTS 34 | fi 35 | 36 | rm -f $OUTFILE ./tmp-compare-1 ./tmp-compare-2 37 | done 38 | 39 | echo "Classes found: ${CLASSCOUNT} Errors: $FAILCOUNT" 40 | -------------------------------------------------------------------------------- /sample/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jvmti-sample" 3 | version = "0.5.0" 4 | authors = [ "Robert Lu " ] 5 | description = "Java native agent example" 6 | keywords = [ "java", "jvm", "jvmti", "debugger" ] 7 | 8 | [lib] 9 | crate_type = [ "cdylib", "rlib" ] 10 | 11 | [dependencies] 12 | jvmti = {path = "../jvmti", version = "0.5.0"} 13 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | # jvmti-sample 2 | 3 | The simple Java native agent written by rust. 4 | 5 | ## Build and Run 6 | 7 | ```sh 8 | $ cargo build 9 | $ java -agentpath:./target/debug/libjvmti_sample.dylib -version 10 | ``` 11 | -------------------------------------------------------------------------------- /sample/src/agent.rs: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * TODO The functions below are essentially parts of an actual client implementation. Because this 4 | * implementation is highly experimental and incomplete they shall remain here for a while but 5 | * they will have to find a new home, eventually 6 | */ 7 | 8 | use jvmti::{runtime::{MethodInvocationEvent, ObjectAllocationEvent, ClassFileLoadEvent}, context::static_context, thread::Thread, instrumentation::asm::transformer::Transformer, bytecode::{Constant, printer::ClassfilePrinter}}; 9 | 10 | pub fn on_method_entry(event: MethodInvocationEvent) { 11 | let shall_record = match static_context().config.read() { 12 | Ok(cfg) => (*cfg).entry_points.iter().any(|item| *item == format!("{}.{}.{}", event.class_sig.package, event.class_sig.name, event.method_sig.name) ), //event.class_name.as_str() == item), 13 | _ => false 14 | }; 15 | 16 | if !shall_record { 17 | println!("[M-{}.{}{}]", event.class_sig.package, event.class_sig.name, event.method_sig.name); 18 | } 19 | 20 | static_context().method_enter(&event.thread.id); 21 | } 22 | 23 | pub fn on_method_exit(event: MethodInvocationEvent) { 24 | match static_context().method_exit(&event.thread.id) { 25 | //Some(_) => (), 26 | Some(duration) => println!("Method {} exited after {}", event.method_sig.name, duration), 27 | None => println!("Method has no start: {}", event.method_sig.name) 28 | } 29 | } 30 | 31 | pub fn on_thread_start(thread: Thread) { 32 | println!("[TS-{}]", thread.name); 33 | 34 | static_context().thread_start(&thread.id); 35 | } 36 | 37 | pub fn on_thread_end(thread: Thread) { 38 | println!("[TE-{}]", thread.name); 39 | 40 | match static_context().thread_end(&thread.id) { 41 | Some(duration) => println!("Thread {} lived {}", thread.name, duration), 42 | None => println!("Thread {} has no start", thread.name) 43 | } 44 | } 45 | 46 | pub fn on_monitor_wait(thread: Thread) { 47 | println!("[W1-{}]", thread.name); 48 | } 49 | 50 | pub fn on_monitor_waited(thread: Thread) { 51 | println!("[W2-{}]", thread.name); 52 | } 53 | 54 | pub fn on_monitor_contended_enter(thread: Thread) { 55 | println!("[C1-{}]", thread.name); 56 | 57 | static_context().monitor_enter(&thread.id); 58 | } 59 | 60 | pub fn on_monitor_contended_entered(thread: Thread) { 61 | println!("[C2-{}]", thread.name); 62 | 63 | match static_context().monitor_entered(&thread.id) { 64 | Some(duration) => println!("Thread {} waited {}", thread.name, duration), 65 | None => println!("Thread {} has never waited", thread.name) 66 | } 67 | } 68 | 69 | pub fn on_class_file_load(mut event: ClassFileLoadEvent) -> Option> { 70 | let shall_transform = match static_context().config.read() { 71 | Ok(cfg) => (*cfg).entry_points.iter().any(|item| item.starts_with(event.class_name.as_str())), //event.class_name.as_str() == item), 72 | _ => false 73 | }; 74 | 75 | if shall_transform { 76 | { 77 | let mut transformer = Transformer::new(&mut event.class); 78 | let result = transformer.ensure_constant(Constant::Utf8(String::from("Cde").into_bytes())); 79 | 80 | println!("Result: {:?}", result); 81 | } 82 | let _: Vec<()> = ClassfilePrinter::render_lines(&event.class).iter().map(|line| println!("{}", line)).collect(); 83 | } 84 | /* 85 | let output_class: Vec = vec![]; 86 | let mut write_cursor = Cursor::new(output_class); 87 | 88 | let mut new_class = event.class; 89 | 90 | new_class.constant_pool.constants = new_class.constant_pool.constants.into_iter().map(|constant| { 91 | match constant { 92 | Constant::Utf8(bytes) => String::from_utf8(bytes.clone()).map(|string| match string.as_str() { 93 | "Hello World" => Constant::Utf8(String::from("Lofasz").into_bytes()), 94 | _ => Constant::Utf8(string.into_bytes()) 95 | }).unwrap_or(Constant::Utf8(bytes)), 96 | other @ _ => other 97 | } 98 | }).collect(); 99 | 100 | let result = { 101 | let mut writer = ClassWriter::new(&mut write_cursor); 102 | writer.write_class(&new_class) 103 | }; 104 | 105 | if let Ok(_) = result { 106 | Some(write_cursor.into_inner()) 107 | } else { 108 | None 109 | } 110 | */ 111 | None 112 | } 113 | 114 | pub fn on_garbage_collection_start() { 115 | println!("GC Start: {:?}", std::time::Instant::now()); 116 | } 117 | 118 | pub fn on_garbage_collection_finish() { 119 | println!("GC Finish: {:?}", std::time::Instant::now()); 120 | } 121 | 122 | pub fn on_object_alloc(event: ObjectAllocationEvent) { 123 | println!("Object allocation: (size: {})", event.size); 124 | } 125 | 126 | pub fn on_object_free() { 127 | println!("Object free"); 128 | } -------------------------------------------------------------------------------- /sample/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate jvmti; 2 | use jvmti::{ 3 | agent::Agent, 4 | config::Config, 5 | context::static_context, 6 | native::{JavaVMPtr, MutString, ReturnValue, VoidPtr}, 7 | options::Options, 8 | }; 9 | 10 | use jvmti::util::stringify; 11 | 12 | mod agent; 13 | 14 | use crate::agent::{ 15 | on_class_file_load, on_garbage_collection_finish, on_garbage_collection_start, on_method_entry, 16 | on_method_exit, on_monitor_contended_enter, on_monitor_contended_entered, on_monitor_wait, 17 | on_monitor_waited, on_object_alloc, on_object_free, on_thread_end, on_thread_start, 18 | }; 19 | 20 | /// 21 | /// `Agent_OnLoad` is the actual entry point of the agent code and it is called by the 22 | /// Java Virtual Machine directly. 23 | /// 24 | #[no_mangle] 25 | #[allow(non_snake_case, unused_variables)] 26 | pub extern "C" fn Agent_OnLoad( 27 | vm: JavaVMPtr, 28 | options: MutString, 29 | reserved: VoidPtr, 30 | ) -> ReturnValue { 31 | let options = Options::parse(stringify(options)); 32 | println!("Starting up as {}", options.agent_id); 33 | 34 | if let Some(config) = Config::read_config() { 35 | println!("Setting configuration"); 36 | static_context().set_config(config); 37 | } 38 | 39 | let mut agent = Agent::new(vm); 40 | 41 | agent.on_garbage_collection_start(Some(on_garbage_collection_start)); 42 | agent.on_garbage_collection_finish(Some(on_garbage_collection_finish)); 43 | agent.on_vm_object_alloc(Some(on_object_alloc)); 44 | agent.on_vm_object_free(Some(on_object_free)); 45 | agent.on_class_file_load(Some(on_class_file_load)); 46 | agent.on_method_entry(Some(on_method_entry)); 47 | agent.on_method_exit(Some(on_method_exit)); 48 | agent.on_thread_start(Some(on_thread_start)); 49 | agent.on_thread_end(Some(on_thread_end)); 50 | agent.on_monitor_wait(Some(on_monitor_wait)); 51 | agent.on_monitor_waited(Some(on_monitor_waited)); 52 | agent.on_monitor_contended_enter(Some(on_monitor_contended_enter)); 53 | agent.on_monitor_contended_entered(Some(on_monitor_contended_entered)); 54 | agent.on_class_file_load(Some(on_class_file_load)); 55 | 56 | agent.update(); 57 | 58 | return 0; 59 | } 60 | 61 | /// 62 | /// `Agent_OnUnload` is the exit point of the agent code. It is called when the JVM has finished 63 | /// running and the virtual machine is unloading the agent from memory before shutting down. 64 | /// Note: this method is also called when the JVM crashes due to an internal error. 65 | /// 66 | #[no_mangle] 67 | #[allow(non_snake_case, unused_variables)] 68 | pub extern "C" fn Agent_OnUnload(vm: JavaVMPtr) {} 69 | -------------------------------------------------------------------------------- /testwatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true; do 4 | inotifywait -r --exclude ".*swp" -e modify src tests 5 | cargo test 6 | done 7 | --------------------------------------------------------------------------------