blob: 3de1865b8018458dffe05ee292e4d845c474d125 [file] [log] [blame]
Kavi Gupta2d84ae72019-06-11 09:19:35 -07001// 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 <atomic>
22#include <ctime>
23#include <fstream>
24#include <iomanip>
25#include <iostream>
26#include <istream>
27#include <memory>
28#include <sstream>
29#include <string>
30#include <vector>
31
32using std::get;
33using std::tuple;
34using std::chrono::system_clock;
35
36namespace dump_coverage {
37
38#define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE)
39#define CHECK_NOTNULL(x) CHECK((x) != nullptr)
40#define CHECK_NO_EXCEPTION(env) CHECK(!(env)->ExceptionCheck());
41
42static JavaVM* java_vm = nullptr;
43
44// Get the current JNI environment.
45static JNIEnv* GetJNIEnv() {
46 JNIEnv* env = nullptr;
47 CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6),
48 JNI_OK);
49 return env;
50}
51
52// Get the JaCoCo Agent class and an instance of the class, given a JNI
53// environment.
54// Will crash if the Agent isn't found or if any Java Exception occurs.
55static tuple<jclass, jobject> GetJavaAgent(JNIEnv* env) {
56 jclass java_agent_class =
57 env->FindClass("org/jacoco/agent/rt/internal/Agent");
58 CHECK_NOTNULL(java_agent_class);
59
60 jmethodID java_agent_get_instance =
61 env->GetStaticMethodID(java_agent_class, "getInstance",
62 "()Lorg/jacoco/agent/rt/internal/Agent;");
63 CHECK_NOTNULL(java_agent_get_instance);
64
65 jobject java_agent_instance =
66 env->CallStaticObjectMethod(java_agent_class, java_agent_get_instance);
67 CHECK_NO_EXCEPTION(env);
68 CHECK_NOTNULL(java_agent_instance);
69
70 return tuple(java_agent_class, java_agent_instance);
71}
72
73// Runs equivalent of Agent.getInstance().getExecutionData(false) and returns
74// the result.
75// Will crash if the Agent isn't found or if any Java Exception occurs.
76static jbyteArray GetExecutionData(JNIEnv* env) {
77 auto java_agent = GetJavaAgent(env);
78 jmethodID java_agent_get_execution_data =
79 env->GetMethodID(get<0>(java_agent), "getExecutionData", "(Z)[B");
80 CHECK_NO_EXCEPTION(env);
81 CHECK_NOTNULL(java_agent_get_execution_data);
82
83 jbyteArray java_result_array = (jbyteArray)env->CallObjectMethod(
84 get<1>(java_agent), java_agent_get_execution_data, false);
85 CHECK_NO_EXCEPTION(env);
86
87 return java_result_array;
88}
89
90// Gets the filename to write execution data to
91// dirname: the directory in which to place the file
92// outputs <dirname>/YYYY-MM-DD-HH-MM-SS.SSS.exec
93static std::string GetFilename(const std::string& dirname) {
94 system_clock::time_point time_point = system_clock::now();
95 auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(time_point);
96 auto fractional_time = time_point - seconds;
97 auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(fractional_time);
98
99 std::time_t time = system_clock::to_time_t(time_point);
100 auto tm = *std::gmtime(&time);
101
102 std::ostringstream oss;
103 oss
104 << dirname
105 << "/"
106 << std::put_time(&tm, "%Y-%m-%d-%H-%M-%S.")
107 << std::setfill('0') << std::setw(3) << millis.count()
108 << ".ec";
109 return oss.str();
110}
111
112// Writes the execution data to a file
113// data, length: represent the data, as a sequence of bytes
114// dirname: directory name to contain the file
115// returns JNI_ERR if there is an error in writing the file, otherwise JNI_OK.
116static jint WriteFile(const char* data, int length, const std::string& dirname) {
117 auto filename = GetFilename(dirname);
118
119 LOG(INFO) << "Writing file of length " << length << " to '" << filename
120 << "'";
121 std::ofstream file(filename, std::ios::binary);
122
123 if (!file.is_open()) {
124 LOG(ERROR) << "Could not open file: '" << filename << "'";
125 return JNI_ERR;
126 }
127 file.write(data, length);
128 file.close();
129
130 if (!file) {
131 LOG(ERROR) << "I/O error in reading file";
132 return JNI_ERR;
133 }
134
135 LOG(INFO) << "Done writing file";
136 return JNI_OK;
137}
138
139// Grabs execution data and writes it to a file
140// dirname: directory name to contain the file
141// returns JNI_ERR if there is an error writing the file.
142// Will crash if the Agent isn't found or if any Java Exception occurs.
143static jint Dump(const std::string& dirname) {
144 LOG(INFO) << "Dumping file";
145
146 JNIEnv* env = GetJNIEnv();
147 jbyteArray java_result_array = GetExecutionData(env);
148 CHECK_NOTNULL(java_result_array);
149
150 jbyte* result_ptr = env->GetByteArrayElements(java_result_array, 0);
151 CHECK_NOTNULL(result_ptr);
152
153 int result_len = env->GetArrayLength(java_result_array);
154
155 return WriteFile((const char*) result_ptr, result_len, dirname);
156}
157
158// Resets execution data, performing the equivalent of
159// Agent.getInstance().reset();
160// args: should be empty
161// returns JNI_ERR if the arguments are invalid.
162// Will crash if the Agent isn't found or if any Java Exception occurs.
163static jint Reset(const std::string& args) {
164 if (args != "") {
165 LOG(ERROR) << "reset takes no arguments, but received '" << args << "'";
166 return JNI_ERR;
167 }
168
169 JNIEnv* env = GetJNIEnv();
170 auto java_agent = GetJavaAgent(env);
171
172 jmethodID java_agent_reset =
173 env->GetMethodID(get<0>(java_agent), "reset", "()V");
174 CHECK_NOTNULL(java_agent_reset);
175
176 env->CallVoidMethod(get<1>(java_agent), java_agent_reset);
177 CHECK_NO_EXCEPTION(env);
178 return JNI_OK;
179}
180
181// Given a string of the form "<a>:<b>" returns (<a>, <b>).
182// Given a string <a> that doesn't contain a colon, returns (<a>, "").
183static tuple<std::string, std::string> SplitOnColon(const std::string& options) {
184 size_t loc_delim = options.find(':');
185 std::string command, args;
186
187 if (loc_delim == std::string::npos) {
188 command = options;
189 } else {
190 command = options.substr(0, loc_delim);
191 args = options.substr(loc_delim + 1, options.length());
192 }
193 return tuple(command, args);
194}
195
196// Parses and executes a command specified by options of the form
197// "<command>:<args>" where <command> is either "dump" or "reset".
198static jint ParseOptionsAndExecuteCommand(const std::string& options) {
199 auto split = SplitOnColon(options);
200 auto command = get<0>(split), args = get<1>(split);
201
202 LOG(INFO) << "command: '" << command << "' args: '" << args << "'";
203
204 if (command == "dump") {
205 return Dump(args);
206 }
207
208 if (command == "reset") {
209 return Reset(args);
210 }
211
212 LOG(ERROR) << "Invalid command: expected 'dump' or 'reset' but was '"
213 << command << "'";
214 return JNI_ERR;
215}
216
217static jint AgentStart(JavaVM* vm, char* options) {
218 android::base::InitLogging(/* argv= */ nullptr);
219 java_vm = vm;
220
221 return ParseOptionsAndExecuteCommand(options);
222}
223
224// Late attachment (e.g. 'am attach-agent').
225extern "C" JNIEXPORT jint JNICALL
226Agent_OnAttach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
227 return AgentStart(vm, options);
228}
229
230// Early attachment.
231extern "C" JNIEXPORT jint JNICALL
232Agent_OnLoad(JavaVM* jvm ATTRIBUTE_UNUSED, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
233 LOG(ERROR)
234 << "The dumpcoverage agent will not work on load,"
235 << " as it does not have access to the runtime.";
236 return JNI_ERR;
237}
238
239} // namespace dump_coverage