1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18
19 #include <map>
20 #include <memory>
21
22 #include "base/leb128.h"
23 #include "base/os.h"
24 #include "base/unix_file/fd_file.h"
25 #include "jni.h"
26
27 namespace art {
28 namespace {
29
30 static const int kMagicValue = 0x574f4c53;
31 static const int kVersionDualClock = 0xf5;
32 static const int kThreadInfo = 0;
33 static const int kMethodInfo = 1;
34 static const int kTraceEntries = 2;
35 static const int kTraceActionBits = 2;
36 static const int kSummary = 3;
37 static const int kMethodEntry = 0;
38 static const int kMethodExitNormal = 1;
39 static const int kMethodExitError = 2;
40
41 // List of methods that could be triggered by a GC. It isn't possible to control
42 // when GCs happen especially in gcstress configs. So we ignore certain methods
43 // that could be executed based on when GC occurs.
44 static const std::string_view ignored_methods_list[] = {
45 "java.lang.ref.ReferenceQueue add (Ljava/lang/ref/Reference;)V ReferenceQueue.java"
46 };
47
ReadNumber(int num_bytes,uint8_t * header)48 uint64_t ReadNumber(int num_bytes, uint8_t* header) {
49 uint64_t number = 0;
50 for (int i = 0; i < num_bytes; i++) {
51 uint64_t c = header[i];
52 number += c << (i * 8);
53 }
54 return number;
55 }
56
ProcessThreadOrMethodInfo(std::unique_ptr<File> & file,std::map<uint64_t,std::string> & name_map,bool is_method)57 bool ProcessThreadOrMethodInfo(std::unique_ptr<File>& file,
58 std::map<uint64_t, std::string>& name_map,
59 bool is_method) {
60 uint8_t header[10];
61 uint8_t header_length = is_method ? 10 : 6;
62 if (!file->ReadFully(&header, header_length)) {
63 printf("Couldn't read header\n");
64 return false;
65 }
66 uint8_t num_bytes_for_id = is_method ? 8 : 4;
67 uint64_t id = ReadNumber(num_bytes_for_id, header);
68 int length = ReadNumber(2, header + num_bytes_for_id);
69
70 char* name = new char[length];
71 if (!file->ReadFully(name, length)) {
72 delete[] name;
73 return false;
74 }
75 std::string str(name, length);
76 std::replace(str.begin(), str.end(), '\t', ' ');
77 if (str[str.length() - 1] == '\n') {
78 str.erase(str.length() - 1);
79 }
80 name_map.emplace(id, str);
81 delete[] name;
82 return true;
83 }
84
MethodInIgnoreList(const std::string & method_name)85 bool MethodInIgnoreList(const std::string& method_name) {
86 for (const std::string_view& ignored_method : ignored_methods_list) {
87 if (method_name.compare(ignored_method) == 0) {
88 return true;
89 }
90 }
91 return false;
92 }
93
PrintTraceEntry(const std::string & thread_name,const std::string & method_name,int event_type,int * current_depth,std::string & ignored_method,int * ignored_method_depth)94 void PrintTraceEntry(const std::string& thread_name,
95 const std::string& method_name,
96 int event_type,
97 int* current_depth,
98 std::string& ignored_method,
99 int* ignored_method_depth) {
100 bool ignore_trace_entry = false;
101 if (ignored_method.empty()) {
102 // Check if we need to ignore the method.
103 if (MethodInIgnoreList(method_name)) {
104 CHECK_EQ(event_type, kMethodEntry);
105 ignored_method = method_name;
106 *ignored_method_depth = *current_depth;
107 ignore_trace_entry = true;
108 }
109 } else {
110 // Check if the ignored method is exiting.
111 if (MethodInIgnoreList(method_name) && *current_depth == *ignored_method_depth + 1) {
112 CHECK_NE(event_type, kMethodEntry);
113 ignored_method.clear();
114 }
115 ignore_trace_entry = true;
116 }
117 std::string entry;
118 for (int i = 0; i < *current_depth; i++) {
119 entry.push_back('.');
120 }
121 if (event_type == kMethodEntry) {
122 *current_depth += 1;
123 entry.append(".>> ");
124 } else if (event_type == kMethodExitNormal) {
125 *current_depth -= 1;
126 entry.append("<< ");
127 } else if (event_type == kMethodExitError) {
128 *current_depth -= 1;
129 entry.append("<<E ");
130 } else {
131 entry.append("?? ");
132 }
133 entry.append(thread_name);
134 entry.append(" ");
135 entry.append(method_name);
136 entry.append("\n");
137 if (!ignore_trace_entry) {
138 printf("%s", entry.c_str());
139 }
140 }
141
ProcessTraceEntries(std::unique_ptr<File> & file,std::map<int64_t,int> & current_depth_map,std::map<uint64_t,std::string> & thread_map,std::map<uint64_t,std::string> & method_map,bool is_dual_clock,const char * thread_name_filter,std::map<uint64_t,std::string> & ignored_method_map,std::map<int64_t,int> & ignored_method_depth_map)142 bool ProcessTraceEntries(std::unique_ptr<File>& file,
143 std::map<int64_t, int>& current_depth_map,
144 std::map<uint64_t, std::string>& thread_map,
145 std::map<uint64_t, std::string>& method_map,
146 bool is_dual_clock,
147 const char* thread_name_filter,
148 std::map<uint64_t, std::string>& ignored_method_map,
149 std::map<int64_t, int>& ignored_method_depth_map) {
150 uint8_t header[24];
151 int header_size = is_dual_clock ? 24 : 20;
152 if (!file->ReadFully(header, header_size)) {
153 return false;
154 }
155
156 uint32_t thread_id = ReadNumber(4, header);
157 uint64_t method_value = ReadNumber(8, header + 4);
158 int offset = 12;
159 if (is_dual_clock) {
160 // Read timestamp.
161 ReadNumber(4, header + offset);
162 offset += 4;
163 }
164 // Read timestamp.
165 ReadNumber(4, header + offset);
166 offset += 4;
167 int num_records = ReadNumber(2, header + offset);
168 offset += 2;
169 int total_size = ReadNumber(2, header + offset);
170 uint8_t* buffer = new uint8_t[total_size];
171 if (!file->ReadFully(buffer, total_size)) {
172 delete[] buffer;
173 return false;
174 }
175
176 const uint8_t* current_buffer_ptr = buffer;
177 uint8_t event_type = method_value & 0x3;
178 uint64_t method_id = (method_value >> kTraceActionBits) << kTraceActionBits;
179 int current_depth = 0;
180 if (current_depth_map.find(thread_id) != current_depth_map.end()) {
181 // Get the current call stack depth. If it is the first method we are seeing on this thread
182 // then this map wouldn't haven an entry we start with the depth of 0.
183 current_depth = current_depth_map[thread_id];
184 }
185
186 int ignored_method_depth = 0;
187 std::string ignored_method;
188 if (ignored_method_map.find(thread_id) != ignored_method_map.end()) {
189 ignored_method = ignored_method_map[thread_id];
190 ignored_method_depth = ignored_method_depth_map[thread_id];
191 }
192
193 std::string thread_name = thread_map[thread_id];
194 bool print_thread_events = (thread_name.compare(thread_name_filter) == 0);
195 if (method_map.find(method_id) == method_map.end()) {
196 LOG(FATAL) << "No entry for init method " << std::hex << method_id << " " << method_value;
197 }
198 if (print_thread_events) {
199 PrintTraceEntry(thread_name,
200 method_map[method_id],
201 event_type,
202 ¤t_depth,
203 ignored_method,
204 &ignored_method_depth);
205 }
206 int64_t prev_method_value = method_value;
207 for (int i = 0; i < num_records; i++) {
208 int64_t diff = 0;
209 auto buffer_ptr = current_buffer_ptr;
210 if (!DecodeSignedLeb128Checked(¤t_buffer_ptr, buffer + total_size - 1, &diff)) {
211 LOG(FATAL) << "Reading past the buffer???";
212 }
213 int64_t curr_method_value = prev_method_value + diff;
214 prev_method_value = curr_method_value;
215 event_type = curr_method_value & 0x3;
216 method_id = (curr_method_value >> kTraceActionBits) << kTraceActionBits;
217 if (method_map.find(method_id) == method_map.end()) {
218 LOG(FATAL) << "No entry for method " << std::hex << method_id;
219 }
220 if (print_thread_events) {
221 PrintTraceEntry(thread_name,
222 method_map[method_id],
223 event_type,
224 ¤t_depth,
225 ignored_method,
226 &ignored_method_depth);
227 }
228 // Read timestamps
229 DecodeUnsignedLeb128(¤t_buffer_ptr);
230 if (is_dual_clock) {
231 DecodeUnsignedLeb128(¤t_buffer_ptr);
232 }
233 }
234 current_depth_map[thread_id] = current_depth;
235 if (!ignored_method.empty()) {
236 ignored_method_map[thread_id] = ignored_method;
237 ignored_method_depth_map[thread_id] = ignored_method_depth;
238 }
239 return true;
240 }
241
Java_Main_dumpTrace(JNIEnv * env,jclass,jstring fileName,jstring threadName)242 extern "C" JNIEXPORT void JNICALL Java_Main_dumpTrace(JNIEnv* env,
243 jclass,
244 jstring fileName,
245 jstring threadName) {
246 const char* file_name = env->GetStringUTFChars(fileName, nullptr);
247 const char* thread_name = env->GetStringUTFChars(threadName, nullptr);
248 std::map<uint64_t, std::string> thread_map;
249 std::map<uint64_t, std::string> method_map;
250 std::map<uint64_t, std::string> ignored_method_map;
251 std::map<int64_t, int> current_depth_map;
252 std::map<int64_t, int> ignored_method_depth_map;
253
254 std::unique_ptr<File> file(OS::OpenFileForReading(file_name));
255 if (file == nullptr) {
256 printf("Couldn't open file\n");
257 return;
258 }
259
260 uint8_t header[32];
261 if (!file->ReadFully(&header, sizeof(header))) {
262 printf("Couldn't read header\n");
263 return;
264 }
265 int magic_value = ReadNumber(4, header);
266 if (magic_value != kMagicValue) {
267 printf("Incorrect magic value\n");
268 return;
269 }
270 int version = ReadNumber(2, header + 4);
271 printf("version=%0x\n", version);
272
273 bool is_dual_clock = (version == kVersionDualClock);
274 bool has_entries = true;
275 while (has_entries) {
276 uint8_t entry_header;
277 if (!file->ReadFully(&entry_header, sizeof(entry_header))) {
278 break;
279 }
280 switch (entry_header) {
281 case kThreadInfo:
282 if (!ProcessThreadOrMethodInfo(file, thread_map, /*is_method=*/false)) {
283 has_entries = false;
284 }
285 break;
286 case kMethodInfo:
287 if (!ProcessThreadOrMethodInfo(file, method_map, /*is_method=*/true)) {
288 has_entries = false;
289 }
290 break;
291 case kTraceEntries:
292 ProcessTraceEntries(file,
293 current_depth_map,
294 thread_map,
295 method_map,
296 is_dual_clock,
297 thread_name,
298 ignored_method_map,
299 ignored_method_depth_map);
300 break;
301 case kSummary:
302 has_entries = false;
303 break;
304 default:
305 printf("Invalid Header %d\n", entry_header);
306 has_entries = false;
307 break;
308 }
309 }
310
311 env->ReleaseStringUTFChars(fileName, file_name);
312 env->ReleaseStringUTFChars(threadName, thread_name);
313 }
314
315 } // namespace
316 } // namespace art
317