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 kVersionDualClockStreaming = 0xf5;
32 static const int kVersionDualClock = 0x05;
33 static const int kThreadInfo = 0;
34 static const int kMethodInfo = 1;
35 static const int kTraceEntries = 2;
36 static const int kTraceActionBits = 2;
37 static const int kSummary = 3;
38 static const int kMethodEntry = 0;
39 static const int kMethodExitNormal = 1;
40 static const int kMethodExitError = 2;
41
42 // List of methods that could be triggered by a GC. It isn't possible to control
43 // when GCs happen especially in gcstress configs. So we ignore certain methods
44 // that could be executed based on when GC occurs.
45 static const std::string_view ignored_methods_list[] = {
46 "java.lang.ref.ReferenceQueue add (Ljava/lang/ref/Reference;)V ReferenceQueue.java"
47 };
48
ReadNumber(int num_bytes,uint8_t * header)49 uint64_t ReadNumber(int num_bytes, uint8_t* header) {
50 uint64_t number = 0;
51 for (int i = 0; i < num_bytes; i++) {
52 uint64_t c = header[i];
53 number += c << (i * 8);
54 }
55 return number;
56 }
57
ProcessThreadOrMethodInfo(std::unique_ptr<File> & file,std::map<uint64_t,std::string> & name_map,bool is_method)58 bool ProcessThreadOrMethodInfo(std::unique_ptr<File>& file,
59 std::map<uint64_t, std::string>& name_map,
60 bool is_method) {
61 uint8_t header[10];
62 uint8_t header_length = is_method ? 10 : 6;
63 if (!file->ReadFully(&header, header_length)) {
64 printf("Couldn't read header\n");
65 return false;
66 }
67 uint8_t num_bytes_for_id = is_method ? 8 : 4;
68 uint64_t id = ReadNumber(num_bytes_for_id, header);
69 int length = ReadNumber(2, header + num_bytes_for_id);
70
71 std::unique_ptr<char[]> name(new char[length]);
72 if (!file->ReadFully(name.get(), length)) {
73 return false;
74 }
75 std::string str(name.get(), 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 return true;
82 }
83
MethodInIgnoreList(const std::string & method_name)84 bool MethodInIgnoreList(const std::string& method_name) {
85 for (const std::string_view& ignored_method : ignored_methods_list) {
86 if (method_name.compare(ignored_method) == 0) {
87 return true;
88 }
89 }
90 return false;
91 }
92
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)93 void PrintTraceEntry(const std::string& thread_name,
94 const std::string& method_name,
95 int event_type,
96 int* current_depth,
97 std::string& ignored_method,
98 int* ignored_method_depth) {
99 bool ignore_trace_entry = false;
100 if (ignored_method.empty()) {
101 // Check if we need to ignore the method.
102 if (MethodInIgnoreList(method_name)) {
103 CHECK_EQ(event_type, kMethodEntry);
104 ignored_method = method_name;
105 *ignored_method_depth = *current_depth;
106 ignore_trace_entry = true;
107 }
108 } else {
109 // Check if the ignored method is exiting.
110 if (MethodInIgnoreList(method_name) && *current_depth == *ignored_method_depth + 1) {
111 CHECK_NE(event_type, kMethodEntry);
112 ignored_method.clear();
113 }
114 ignore_trace_entry = true;
115 }
116 std::string entry;
117 for (int i = 0; i < *current_depth; i++) {
118 entry.push_back('.');
119 }
120 if (event_type == kMethodEntry) {
121 *current_depth += 1;
122 entry.append(".>> ");
123 } else if (event_type == kMethodExitNormal) {
124 *current_depth -= 1;
125 entry.append("<< ");
126 } else if (event_type == kMethodExitError) {
127 *current_depth -= 1;
128 entry.append("<<E ");
129 } else {
130 entry.append("?? ");
131 }
132 entry.append(thread_name);
133 entry.append(" ");
134 entry.append(method_name);
135 entry.append("\n");
136 if (!ignore_trace_entry) {
137 printf("%s", entry.c_str());
138 }
139 }
140
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)141 bool ProcessTraceEntries(std::unique_ptr<File>& file,
142 std::map<int64_t, int>& current_depth_map,
143 std::map<uint64_t, std::string>& thread_map,
144 std::map<uint64_t, std::string>& method_map,
145 bool is_dual_clock,
146 const char* thread_name_filter,
147 std::map<uint64_t, std::string>& ignored_method_map,
148 std::map<int64_t, int>& ignored_method_depth_map) {
149 uint8_t header[11];
150 if (!file->ReadFully(header, sizeof(header))) {
151 return false;
152 }
153
154 uint32_t thread_id = ReadNumber(4, header);
155 int offset = 4;
156 int num_records = ReadNumber(3, header + offset);
157 offset += 3;
158 int total_size = ReadNumber(4, header + offset);
159 std::unique_ptr<uint8_t[]> buffer(new uint8_t[total_size]);
160 if (!file->ReadFully(buffer.get(), total_size)) {
161 return false;
162 }
163
164 int current_depth = 0;
165 if (current_depth_map.find(thread_id) != current_depth_map.end()) {
166 // Get the current call stack depth. If it is the first method we are seeing on this thread
167 // then this map wouldn't haven an entry we start with the depth of 0.
168 current_depth = current_depth_map[thread_id];
169 }
170
171 int ignored_method_depth = 0;
172 std::string ignored_method;
173 if (ignored_method_map.find(thread_id) != ignored_method_map.end()) {
174 ignored_method = ignored_method_map[thread_id];
175 ignored_method_depth = ignored_method_depth_map[thread_id];
176 }
177
178 std::string thread_name = thread_map[thread_id];
179 bool print_thread_events = (thread_name.compare(thread_name_filter) == 0);
180
181 const uint8_t* current_buffer_ptr = buffer.get();
182 int64_t prev_method_value = 0;
183 for (int i = 0; i < num_records; i++) {
184 int64_t diff = 0;
185 if (!DecodeSignedLeb128Checked<int64_t>(
186 ¤t_buffer_ptr, buffer.get() + total_size - 1, &diff)) {
187 LOG(FATAL) << "Reading past the buffer???";
188 }
189 int64_t curr_method_value = prev_method_value + diff;
190 prev_method_value = curr_method_value;
191 uint8_t event_type = curr_method_value & 0x3;
192 uint64_t method_id = (curr_method_value >> kTraceActionBits) << kTraceActionBits;
193 if (method_map.find(method_id) == method_map.end()) {
194 LOG(FATAL) << "No entry for method " << std::hex << method_id;
195 }
196 if (print_thread_events) {
197 PrintTraceEntry(thread_name,
198 method_map[method_id],
199 event_type,
200 ¤t_depth,
201 ignored_method,
202 &ignored_method_depth);
203 }
204 // Read timestamps
205 DecodeUnsignedLeb128<uint64_t>(¤t_buffer_ptr);
206 if (is_dual_clock) {
207 DecodeUnsignedLeb128<uint64_t>(¤t_buffer_ptr);
208 }
209 }
210 current_depth_map[thread_id] = current_depth;
211 if (!ignored_method.empty()) {
212 ignored_method_map[thread_id] = ignored_method;
213 ignored_method_depth_map[thread_id] = ignored_method_depth;
214 }
215 return true;
216 }
217
Java_Main_dumpTrace(JNIEnv * env,jclass,jstring fileName,jstring threadName)218 extern "C" JNIEXPORT void JNICALL Java_Main_dumpTrace(JNIEnv* env,
219 jclass,
220 jstring fileName,
221 jstring threadName) {
222 const char* file_name = env->GetStringUTFChars(fileName, nullptr);
223 const char* thread_name = env->GetStringUTFChars(threadName, nullptr);
224 std::map<uint64_t, std::string> thread_map;
225 std::map<uint64_t, std::string> method_map;
226 std::map<uint64_t, std::string> ignored_method_map;
227 std::map<int64_t, int> current_depth_map;
228 std::map<int64_t, int> ignored_method_depth_map;
229
230 std::unique_ptr<File> file(OS::OpenFileForReading(file_name));
231 if (file == nullptr) {
232 printf("Couldn't open file\n");
233 return;
234 }
235
236 uint8_t header[32];
237 if (!file->ReadFully(&header, sizeof(header))) {
238 printf("Couldn't read header\n");
239 return;
240 }
241 int magic_value = ReadNumber(4, header);
242 if (magic_value != kMagicValue) {
243 printf("Incorrect magic value got:%0x expected:%0x\n", magic_value, kMagicValue);
244 return;
245 }
246 int version = ReadNumber(2, header + 4);
247 printf("version=%0x\n", version);
248
249 bool is_dual_clock = (version == kVersionDualClock) || (version == kVersionDualClockStreaming);
250 bool has_entries = true;
251 while (has_entries) {
252 uint8_t entry_header;
253 if (!file->ReadFully(&entry_header, sizeof(entry_header))) {
254 break;
255 }
256 switch (entry_header) {
257 case kThreadInfo:
258 if (!ProcessThreadOrMethodInfo(file, thread_map, /*is_method=*/false)) {
259 has_entries = false;
260 }
261 break;
262 case kMethodInfo:
263 if (!ProcessThreadOrMethodInfo(file, method_map, /*is_method=*/true)) {
264 has_entries = false;
265 }
266 break;
267 case kTraceEntries:
268 ProcessTraceEntries(file,
269 current_depth_map,
270 thread_map,
271 method_map,
272 is_dual_clock,
273 thread_name,
274 ignored_method_map,
275 ignored_method_depth_map);
276 break;
277 case kSummary:
278 has_entries = false;
279 break;
280 default:
281 printf("Invalid Header %d\n", entry_header);
282 has_entries = false;
283 break;
284 }
285 }
286
287 env->ReleaseStringUTFChars(fileName, file_name);
288 env->ReleaseStringUTFChars(threadName, thread_name);
289 }
290
291 } // namespace
292 } // namespace art
293