1 // Copyright (C) 2018 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15
16 #include <android-base/logging.h>
17 #include <jni.h>
18 #include <jvmti.h>
19 #include <string.h>
20
21 #include <fstream>
22
23 using std::get;
24 using std::tuple;
25
26 namespace dump_coverage {
27
28 #define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE)
29 #define CHECK_NOTNULL(x) CHECK((x) != nullptr)
30 #define CHECK_NO_EXCEPTION(env) CHECK(!(env)->ExceptionCheck());
31
32 static JavaVM* java_vm = nullptr;
33
34 // Get the current JNI environment.
GetJNIEnv()35 static JNIEnv* GetJNIEnv() {
36 JNIEnv* env = nullptr;
37 CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6),
38 JNI_OK);
39 return env;
40 }
41
42 // Get the JaCoCo Agent class and an instance of the class, given a JNI
43 // environment.
44 // Will crash if the Agent isn't found or if any Java Exception occurs.
GetJavaAgent(JNIEnv * env)45 static tuple<jclass, jobject> GetJavaAgent(JNIEnv* env) {
46 jclass java_agent_class =
47 env->FindClass("org/jacoco/agent/rt/internal/Agent");
48 CHECK_NOTNULL(java_agent_class);
49
50 jmethodID java_agent_get_instance =
51 env->GetStaticMethodID(java_agent_class, "getInstance",
52 "()Lorg/jacoco/agent/rt/internal/Agent;");
53 CHECK_NOTNULL(java_agent_get_instance);
54
55 jobject java_agent_instance =
56 env->CallStaticObjectMethod(java_agent_class, java_agent_get_instance);
57 CHECK_NO_EXCEPTION(env);
58 CHECK_NOTNULL(java_agent_instance);
59
60 return tuple(java_agent_class, java_agent_instance);
61 }
62
63 // Runs equivalent of Agent.getInstance().getExecutionData(false) and returns
64 // the result.
65 // Will crash if the Agent isn't found or if any Java Exception occurs.
GetExecutionData(JNIEnv * env)66 static jbyteArray GetExecutionData(JNIEnv* env) {
67 auto java_agent = GetJavaAgent(env);
68 jmethodID java_agent_get_execution_data =
69 env->GetMethodID(get<0>(java_agent), "getExecutionData", "(Z)[B");
70 CHECK_NO_EXCEPTION(env);
71 CHECK_NOTNULL(java_agent_get_execution_data);
72
73 jbyteArray java_result_array = (jbyteArray)env->CallObjectMethod(
74 get<1>(java_agent), java_agent_get_execution_data, false);
75 CHECK_NO_EXCEPTION(env);
76
77 return java_result_array;
78 }
79
80 // Writes the execution data to a file.
81 // data, length: represent the data, as a sequence of bytes.
82 // filename: file to write coverage data to.
83 // returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
WriteFile(const char * data,int length,const std::string & filename)84 static jint WriteFile(const char* data, int length, const std::string& filename) {
85 LOG(INFO) << "Writing file of length " << length << " to '" << filename
86 << "'";
87 std::ofstream file(filename, std::ios::binary);
88
89 if (!file.is_open()) {
90 LOG(ERROR) << "Could not open file: '" << filename << "'";
91 return JNI_ERR;
92 }
93 file.write(data, length);
94 file.close();
95
96 if (!file) {
97 LOG(ERROR) << "I/O error in reading file";
98 return JNI_ERR;
99 }
100
101 LOG(INFO) << "Done writing file";
102 return JNI_OK;
103 }
104
105 // Grabs execution data and writes it to a file.
106 // filename: file to write coverage data to.
107 // returns JNI_ERR if there is an error writing the file.
108 // Will crash if the Agent isn't found or if any Java Exception occurs.
Dump(const std::string & filename)109 static jint Dump(const std::string& filename) {
110 LOG(INFO) << "Dumping file";
111
112 JNIEnv* env = GetJNIEnv();
113 jbyteArray java_result_array = GetExecutionData(env);
114 CHECK_NOTNULL(java_result_array);
115
116 jbyte* result_ptr = env->GetByteArrayElements(java_result_array, 0);
117 CHECK_NOTNULL(result_ptr);
118
119 int result_len = env->GetArrayLength(java_result_array);
120
121 return WriteFile((const char*) result_ptr, result_len, filename);
122 }
123
124 // Resets execution data, performing the equivalent of
125 // Agent.getInstance().reset();
126 // args: should be empty.
127 // returns JNI_ERR if the arguments are invalid.
128 // Will crash if the Agent isn't found or if any Java Exception occurs.
Reset(const std::string & args)129 static jint Reset(const std::string& args) {
130 if (args != "") {
131 LOG(ERROR) << "reset takes no arguments, but received '" << args << "'";
132 return JNI_ERR;
133 }
134
135 JNIEnv* env = GetJNIEnv();
136 auto java_agent = GetJavaAgent(env);
137
138 jmethodID java_agent_reset =
139 env->GetMethodID(get<0>(java_agent), "reset", "()V");
140 CHECK_NOTNULL(java_agent_reset);
141
142 env->CallVoidMethod(get<1>(java_agent), java_agent_reset);
143 CHECK_NO_EXCEPTION(env);
144 return JNI_OK;
145 }
146
147 // Given a string of the form "<a>:<b>" returns (<a>, <b>).
148 // Given a string <a> that doesn't contain a colon, returns (<a>, "").
SplitOnColon(const std::string & options)149 static tuple<std::string, std::string> SplitOnColon(const std::string& options) {
150 size_t loc_delim = options.find(':');
151 std::string command, args;
152
153 if (loc_delim == std::string::npos) {
154 command = options;
155 } else {
156 command = options.substr(0, loc_delim);
157 args = options.substr(loc_delim + 1, options.length());
158 }
159 return tuple(command, args);
160 }
161
162 // Parses and executes a command specified by options of the form
163 // "<command>:<args>" where <command> is either "dump" or "reset".
ParseOptionsAndExecuteCommand(const std::string & options)164 static jint ParseOptionsAndExecuteCommand(const std::string& options) {
165 auto split = SplitOnColon(options);
166 auto command = get<0>(split), args = get<1>(split);
167
168 LOG(INFO) << "command: '" << command << "' args: '" << args << "'";
169
170 if (command == "dump") {
171 return Dump(args);
172 }
173
174 if (command == "reset") {
175 return Reset(args);
176 }
177
178 LOG(ERROR) << "Invalid command: expected 'dump' or 'reset' but was '"
179 << command << "'";
180 return JNI_ERR;
181 }
182
AgentStart(JavaVM * vm,char * options)183 static jint AgentStart(JavaVM* vm, char* options) {
184 android::base::InitLogging(/* argv= */ nullptr);
185 java_vm = vm;
186
187 return ParseOptionsAndExecuteCommand(options);
188 }
189
190 // Late attachment (e.g. 'am attach-agent').
191 extern "C" JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM * vm,char * options,void * reserved ATTRIBUTE_UNUSED)192 Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
193 return AgentStart(vm, options);
194 }
195
196 // Early attachment.
197 extern "C" JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM * jvm ATTRIBUTE_UNUSED,char * options ATTRIBUTE_UNUSED,void * reserved ATTRIBUTE_UNUSED)198 Agent_OnLoad(JavaVM* jvm ATTRIBUTE_UNUSED, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
199 LOG(ERROR)
200 << "The dumpcoverage agent will not work on load,"
201 << " as it does not have access to the runtime.";
202 return JNI_ERR;
203 }
204
205 } // namespace dump_coverage
206