1 /*
2 * Copyright (C) 2020 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 <gtest/gtest.h>
18
19 #include <stdlib.h>
20
21 #include <string>
22 #include <vector>
23
24 #include "record_file.h"
25 #include "report_utils.h"
26 #include "thread_tree.h"
27
28 using namespace simpleperf;
29
TEST(ProguardMappingRetrace,smoke)30 TEST(ProguardMappingRetrace, smoke) {
31 TemporaryFile tmpfile;
32 close(tmpfile.release());
33 ASSERT_TRUE(
34 android::base::WriteStringToFile("original.class.A -> A:\n"
35 "\n"
36 " void method_a() -> a\n"
37 " void method_b() -> b\n"
38 " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
39 " # some other comments\n"
40 " void original.class.M.method_c() -> c\n"
41 " void original.class.A.method_d() -> d\n"
42 "original.class.B -> B:\n"
43 "# some other comments\n"
44 "original.class.C -> C:\n"
45 "# {\'id\':\'com.android.tools.r8.synthesized\'}\n",
46 tmpfile.path));
47 ProguardMappingRetrace retrace;
48 ASSERT_TRUE(retrace.AddProguardMappingFile(tmpfile.path));
49 std::string original_name;
50 bool synthesized;
51 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.a", &original_name, &synthesized));
52 ASSERT_EQ(original_name, "original.class.A.method_a");
53 ASSERT_FALSE(synthesized);
54 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.b", &original_name, &synthesized));
55 ASSERT_EQ(original_name, "original.class.A.method_b");
56 ASSERT_TRUE(synthesized);
57 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.c", &original_name, &synthesized));
58 ASSERT_EQ(original_name, "original.class.M.method_c");
59 ASSERT_FALSE(synthesized);
60 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.d", &original_name, &synthesized));
61 ASSERT_EQ(original_name, "original.class.A.method_d");
62 ASSERT_FALSE(synthesized);
63 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("B.b", &original_name, &synthesized));
64 ASSERT_EQ(original_name, "original.class.B.b");
65 ASSERT_FALSE(synthesized);
66 ASSERT_TRUE(retrace.DeObfuscateJavaMethods("C.c", &original_name, &synthesized));
67 ASSERT_EQ(original_name, "original.class.C.c");
68 ASSERT_TRUE(synthesized);
69 }
70
71 class CallChainReportBuilderTest : public testing::Test {
72 protected:
SetUp()73 virtual void SetUp() {
74 // To test different options for CallChainReportBuilder, we create a fake thread, add fake
75 // libraries used by the thread, and provide fake symbols in each library. We need four
76 // types of libraries: native, interpreter, jit cache and dex file.
77 thread_tree.SetThreadName(1, 1, "thread1");
78 thread = thread_tree.FindThread(1);
79
80 // Add symbol info for the native library.
81 SetSymbols(fake_native_lib_path, DSO_ELF_FILE,
82 {
83 Symbol("native_func1", 0x0, 0x100),
84 Symbol("art_jni_trampoline", 0x100, 0x100),
85 });
86
87 // Add symbol info for the interpreter library.
88 SetSymbols(
89 fake_interpreter_path, DSO_ELF_FILE,
90 {
91 Symbol("art_func1", 0x0, 0x100),
92 Symbol("art_func2", 0x100, 0x100),
93 Symbol("_ZN3artL13Method_invokeEP7_JNIEnvP8_jobjectS3_P13_jobjectArray", 0x200, 0x100),
94 Symbol("art_quick_generic_jni_trampoline", 0x300, 0x100),
95 });
96
97 // Add symbol info for the dex file.
98 SetSymbols(fake_dex_file_path, DSO_DEX_FILE,
99 {
100 Symbol("java_method1", 0x0, 0x100),
101 Symbol("java_method2", 0x100, 0x100),
102 Symbol("obfuscated_class.obfuscated_java_method", 0x200, 0x100),
103 });
104
105 // Add symbol info for the jit cache.
106 SetSymbols(fake_jit_cache_path, DSO_ELF_FILE,
107 {
108 Symbol("java_method2", 0x3000, 0x100),
109 Symbol("java_method3", 0x3100, 0x100),
110 Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100),
111 Symbol("obfuscated_class.java_method4", 0x3300, 0x100),
112 });
113
114 // Add map layout for libraries used in the thread:
115 // 0x0000 - 0x1000 is mapped to the native library.
116 // 0x1000 - 0x2000 is mapped to the interpreter library.
117 // 0x2000 - 0x3000 is mapped to the dex file.
118 // 0x3000 - 0x4000 is mapped to the jit cache.
119 thread_tree.AddThreadMap(1, 1, 0x0, 0x1000, 0x0, fake_native_lib_path);
120 thread_tree.AddThreadMap(1, 1, 0x1000, 0x1000, 0x0, fake_interpreter_path);
121 thread_tree.AddThreadMap(1, 1, 0x2000, 0x1000, 0x0, fake_dex_file_path);
122 thread_tree.AddThreadMap(1, 1, 0x3000, 0x1000, 0x0, fake_jit_cache_path,
123 map_flags::PROT_JIT_SYMFILE_MAP);
124 }
125
SetSymbols(const std::string & path,DsoType dso_type,const std::vector<Symbol> & symbols)126 void SetSymbols(const std::string& path, DsoType dso_type, const std::vector<Symbol>& symbols) {
127 FileFeature file;
128 file.path = path;
129 file.type = dso_type;
130 file.min_vaddr = file.file_offset_of_min_vaddr = 0;
131 file.symbols = symbols;
132 thread_tree.AddDsoInfo(file);
133 }
134
135 ThreadTree thread_tree;
136 const ThreadEntry* thread;
137 const std::string fake_native_lib_path = "fake_dir/fake_native_lib.so";
138 const std::string fake_interpreter_path = "fake_dir/libart.so";
139 const std::string fake_dex_file_path = "fake_dir/framework.jar";
140 const std::string fake_jit_cache_path = "fake_jit_app_cache:0";
141
142 const std::vector<uint64_t> fake_ips = {
143 0x1000, // art_func1
144 0x1100, // art_func2
145 0x2000, // java_method1 in dex file
146 0x1000, // art_func1
147 0x1100, // art_func2
148 0x3000, // java_method2 in jit cache
149 0x1000, // art_func1
150 0x1100, // art_func2
151 };
152 };
153
TEST_F(CallChainReportBuilderTest,default_option)154 TEST_F(CallChainReportBuilderTest, default_option) {
155 // Test default option: remove_art_frame = true, convert_jit_frame = true.
156 // The callchain shouldn't include interpreter frames. And the JIT frame is
157 // converted to a dex frame.
158 CallChainReportBuilder builder(thread_tree);
159 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
160 ASSERT_EQ(entries.size(), 2);
161 ASSERT_EQ(entries[0].ip, 0x2000);
162 ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
163 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
164 ASSERT_EQ(entries[0].vaddr_in_file, 0);
165 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
166 ASSERT_EQ(entries[1].ip, 0x3000);
167 ASSERT_STREQ(entries[1].symbol->Name(), "java_method2");
168 ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
169 ASSERT_EQ(entries[1].vaddr_in_file, 0x100);
170 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
171 }
172
TEST_F(CallChainReportBuilderTest,not_convert_jit_frame)173 TEST_F(CallChainReportBuilderTest, not_convert_jit_frame) {
174 // Test option: remove_art_frame = true, convert_jit_frame = false.
175 // The callchain shouldn't include interpreter frames. And the JIT frame isn't
176 // converted to a dex frame.
177 CallChainReportBuilder builder(thread_tree);
178 builder.SetConvertJITFrame(false);
179 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
180 ASSERT_EQ(entries.size(), 2);
181 ASSERT_EQ(entries[0].ip, 0x2000);
182 ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
183 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
184 ASSERT_EQ(entries[0].vaddr_in_file, 0);
185 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
186 ASSERT_EQ(entries[1].ip, 0x3000);
187 ASSERT_STREQ(entries[1].symbol->Name(), "java_method2");
188 ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
189 ASSERT_EQ(entries[1].vaddr_in_file, 0x3000);
190 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
191 }
192
TEST_F(CallChainReportBuilderTest,not_remove_art_frame)193 TEST_F(CallChainReportBuilderTest, not_remove_art_frame) {
194 // Test option: remove_art_frame = false, convert_jit_frame = true.
195 // The callchain should include interpreter frames. And the JIT frame is
196 // converted to a dex frame.
197 CallChainReportBuilder builder(thread_tree);
198 builder.SetRemoveArtFrame(false);
199 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
200 ASSERT_EQ(entries.size(), 8);
201 for (size_t i : {0, 3, 6}) {
202 ASSERT_EQ(entries[i].ip, 0x1000);
203 ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
204 ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
205 ASSERT_EQ(entries[i].vaddr_in_file, 0);
206 ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::ART_METHOD);
207 ASSERT_EQ(entries[i + 1].ip, 0x1100);
208 ASSERT_STREQ(entries[i + 1].symbol->Name(), "art_func2");
209 ASSERT_EQ(entries[i + 1].dso->Path(), fake_interpreter_path);
210 ASSERT_EQ(entries[i + 1].vaddr_in_file, 0x100);
211 ASSERT_EQ(entries[i + 1].execution_type, CallChainExecutionType::ART_METHOD);
212 }
213 ASSERT_EQ(entries[2].ip, 0x2000);
214 ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
215 ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
216 ASSERT_EQ(entries[2].vaddr_in_file, 0);
217 ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
218 ASSERT_EQ(entries[5].ip, 0x3000);
219 ASSERT_STREQ(entries[5].symbol->Name(), "java_method2");
220 ASSERT_EQ(entries[5].dso->Path(), fake_dex_file_path);
221 ASSERT_EQ(entries[5].vaddr_in_file, 0x100);
222 ASSERT_EQ(entries[5].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
223 }
224
TEST_F(CallChainReportBuilderTest,remove_jit_frame_called_by_dex_frame)225 TEST_F(CallChainReportBuilderTest, remove_jit_frame_called_by_dex_frame) {
226 // Test option: remove_art_frame = true, convert_jit_frame = true.
227 // The callchain should remove the JIT frame called by a dex frame having the same symbol name.
228 std::vector<uint64_t> fake_ips = {
229 0x3000, // java_method2 in jit cache
230 0x1000, // art_func1
231 0x1100, // art_func2
232 0x2100, // java_method2 in dex file
233 0x1000, // art_func1
234 };
235 CallChainReportBuilder builder(thread_tree);
236 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
237 ASSERT_EQ(entries.size(), 1);
238 ASSERT_EQ(entries[0].ip, 0x2100);
239 ASSERT_STREQ(entries[0].symbol->Name(), "java_method2");
240 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
241 ASSERT_EQ(entries[0].vaddr_in_file, 0x100);
242 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
243 }
244
TEST_F(CallChainReportBuilderTest,remove_art_frame_only_near_jvm_method)245 TEST_F(CallChainReportBuilderTest, remove_art_frame_only_near_jvm_method) {
246 // Test option: remove_art_frame = true, convert_jit_frame = true.
247 // The callchain should not remove ART symbols not near a JVM method.
248 std::vector<uint64_t> fake_ips = {
249 0x1000, // art_func1
250 0x0, // native_func1
251 0x2000, // java_method1 in dex file
252 0x0, // native_func1
253 0x1000, // art_func1
254 };
255 CallChainReportBuilder builder(thread_tree);
256 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
257 ASSERT_EQ(entries.size(), 5);
258 for (size_t i : {0, 4}) {
259 ASSERT_EQ(entries[i].ip, 0x1000);
260 ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
261 ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
262 ASSERT_EQ(entries[i].vaddr_in_file, 0);
263 ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
264 }
265 for (size_t i : {1, 3}) {
266 ASSERT_EQ(entries[i].ip, 0x0);
267 ASSERT_STREQ(entries[i].symbol->Name(), "native_func1");
268 ASSERT_EQ(entries[i].dso->Path(), fake_native_lib_path);
269 ASSERT_EQ(entries[i].vaddr_in_file, 0);
270 ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
271 }
272
273 ASSERT_EQ(entries[2].ip, 0x2000);
274 ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
275 ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
276 ASSERT_EQ(entries[2].vaddr_in_file, 0x0);
277 ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
278 }
279
TEST_F(CallChainReportBuilderTest,keep_art_jni_method)280 TEST_F(CallChainReportBuilderTest, keep_art_jni_method) {
281 // Test option: remove_art_frame = true.
282 // The callchain should remove art_jni_trampoline, but keep jni methods.
283 std::vector<uint64_t> fake_ips = {
284 0x1200, // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
285 0x100, // art_jni_trampoline
286 0x2000, // java_method1 in dex file
287 0x1200, // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
288 0x1300, // art_quick_generic_jni_trampoline
289 };
290 CallChainReportBuilder builder(thread_tree);
291 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
292 ASSERT_EQ(entries.size(), 3);
293 for (size_t i : {0, 2}) {
294 ASSERT_EQ(entries[i].ip, 0x1200);
295 ASSERT_STREQ(entries[i].symbol->DemangledName(),
296 "art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)");
297 ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
298 ASSERT_EQ(entries[i].vaddr_in_file, 0x200);
299 ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
300 }
301 ASSERT_EQ(entries[1].ip, 0x2000);
302 ASSERT_STREQ(entries[1].symbol->Name(), "java_method1");
303 ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
304 ASSERT_EQ(entries[1].vaddr_in_file, 0x0);
305 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
306 }
307
TEST_F(CallChainReportBuilderTest,add_proguard_mapping_file)308 TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
309 std::vector<uint64_t> fake_ips = {
310 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
311 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
312 0x3300, // 3300, // obfuscated_class.java_method4
313 };
314 CallChainReportBuilder builder(thread_tree);
315 // Symbol names aren't changed when not given proguard mapping files.
316 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
317 ASSERT_EQ(entries.size(), 3);
318 ASSERT_EQ(entries[0].ip, 0x2200);
319 ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method");
320 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
321 ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
322 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
323 ASSERT_EQ(entries[1].ip, 0x3200);
324 ASSERT_STREQ(entries[1].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method2");
325 ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
326 ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
327 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
328 ASSERT_EQ(entries[2].ip, 0x3300);
329 ASSERT_STREQ(entries[2].symbol->DemangledName(), "obfuscated_class.java_method4");
330 ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
331 ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
332 ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
333
334 // Symbol names are changed when given a proguard mapping file.
335 TemporaryFile tmpfile;
336 close(tmpfile.release());
337 ASSERT_TRUE(android::base::WriteStringToFile(
338 "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
339 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
340 "Parcel) -> obfuscated_java_method\n"
341 " 13:13:androidx.core.app.RemoteActionCompat "
342 "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
343 "VersionedParcel) -> obfuscated_java_method2",
344 tmpfile.path));
345 builder.AddProguardMappingFile(tmpfile.path);
346 entries = builder.Build(thread, fake_ips, 0);
347 ASSERT_EQ(entries.size(), 3);
348 ASSERT_EQ(entries[0].ip, 0x2200);
349 ASSERT_STREQ(entries[0].symbol->DemangledName(),
350 "android.support.v4.app.RemoteActionCompatParcelizer.read");
351 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
352 ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
353 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
354 ASSERT_EQ(entries[1].ip, 0x3200);
355 ASSERT_STREQ(entries[1].symbol->DemangledName(),
356 "android.support.v4.app.RemoteActionCompatParcelizer.read2");
357 ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
358 ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
359 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
360 ASSERT_STREQ(entries[2].symbol->DemangledName(),
361 "android.support.v4.app.RemoteActionCompatParcelizer.java_method4");
362 ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
363 ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
364 ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
365 }
366
TEST_F(CallChainReportBuilderTest,not_remove_synthesized_frame_by_default)367 TEST_F(CallChainReportBuilderTest, not_remove_synthesized_frame_by_default) {
368 std::vector<uint64_t> fake_ips = {
369 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
370 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
371 };
372
373 TemporaryFile tmpfile;
374 ASSERT_TRUE(android::base::WriteStringToFile(
375 "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
376 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
377 "Parcel) -> obfuscated_java_method\n"
378 " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
379 " 13:13:androidx.core.app.RemoteActionCompat "
380 "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
381 "VersionedParcel) -> obfuscated_java_method2",
382 tmpfile.path));
383
384 // By default, synthesized frames are kept.
385 CallChainReportBuilder builder(thread_tree);
386 builder.AddProguardMappingFile(tmpfile.path);
387 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
388 ASSERT_EQ(entries.size(), 2);
389 ASSERT_EQ(entries[0].ip, 0x2200);
390 ASSERT_STREQ(entries[0].symbol->DemangledName(),
391 "android.support.v4.app.RemoteActionCompatParcelizer.read");
392 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
393 ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
394 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
395 ASSERT_EQ(entries[1].ip, 0x3200);
396 ASSERT_STREQ(entries[1].symbol->DemangledName(),
397 "android.support.v4.app.RemoteActionCompatParcelizer.read2");
398 ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
399 ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
400 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
401 }
402
TEST_F(CallChainReportBuilderTest,remove_synthesized_frame_with_env_variable)403 TEST_F(CallChainReportBuilderTest, remove_synthesized_frame_with_env_variable) {
404 // Windows doesn't support setenv and unsetenv. So don't test on it.
405 #if !defined(__WIN32)
406 std::vector<uint64_t> fake_ips = {
407 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
408 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
409 };
410
411 TemporaryFile tmpfile;
412 ASSERT_TRUE(android::base::WriteStringToFile(
413 "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
414 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
415 "Parcel) -> obfuscated_java_method\n"
416 " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
417 " 13:13:androidx.core.app.RemoteActionCompat "
418 "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
419 "VersionedParcel) -> obfuscated_java_method2",
420 tmpfile.path));
421
422 // With environment variable set, synthesized frames are removed.
423 ASSERT_EQ(setenv("REMOVE_R8_SYNTHESIZED_FRAME", "1", 1), 0);
424 CallChainReportBuilder builder(thread_tree);
425 ASSERT_EQ(unsetenv("REMOVE_R8_SYNTHESIZED_FRAME"), 0);
426 builder.AddProguardMappingFile(tmpfile.path);
427 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
428 ASSERT_EQ(entries.size(), 1);
429 ASSERT_EQ(entries[0].ip, 0x3200);
430 ASSERT_STREQ(entries[0].symbol->DemangledName(),
431 "android.support.v4.app.RemoteActionCompatParcelizer.read2");
432 ASSERT_EQ(entries[0].dso->Path(), fake_jit_cache_path);
433 ASSERT_EQ(entries[0].vaddr_in_file, 0x3200);
434 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
435 #endif // !defined(__WIN32)
436 }
437
TEST_F(CallChainReportBuilderTest,add_proguard_mapping_file_for_jit_method_with_signature)438 TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with_signature) {
439 std::vector<uint64_t> fake_ips = {
440 0x3200, // 3200, // void ctep.v(cteo, ctgc, ctbn)
441 };
442 SetSymbols(fake_jit_cache_path, DSO_ELF_FILE,
443 {Symbol("void ctep.v(cteo, ctgc, ctbn)", 0x3200, 0x100)});
444 CallChainReportBuilder builder(thread_tree);
445 TemporaryFile tmpfile;
446 close(tmpfile.release());
447 ASSERT_TRUE(android::base::WriteStringToFile(
448 "android.support.v4.app.RemoteActionCompatParcelizer -> ctep:\n"
449 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
450 "Parcel) -> v\n",
451 tmpfile.path));
452 builder.AddProguardMappingFile(tmpfile.path);
453 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
454 ASSERT_EQ(entries.size(), 1);
455 ASSERT_EQ(entries[0].ip, 0x3200);
456 ASSERT_STREQ(entries[0].symbol->DemangledName(),
457 "android.support.v4.app.RemoteActionCompatParcelizer.read");
458 ASSERT_EQ(entries[0].dso->Path(), fake_jit_cache_path);
459 ASSERT_EQ(entries[0].vaddr_in_file, 0x3200);
460 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
461 }
462
TEST_F(CallChainReportBuilderTest,add_proguard_mapping_file_for_compiled_java_method_with_signature)463 TEST_F(CallChainReportBuilderTest,
464 add_proguard_mapping_file_for_compiled_java_method_with_signature) {
465 TemporaryFile tmpfile;
466 close(tmpfile.release());
467 ASSERT_TRUE(android::base::WriteStringToFile(
468 "android.support.v4.app.RemoteActionCompatParcelizer -> ctep:\n"
469 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
470 "Parcel) -> v\n",
471 tmpfile.path));
472
473 for (const char* suffix : {".odex", ".oat", ".dex"}) {
474 std::string compiled_java_path = "compiled_java" + std::string(suffix);
475 SetSymbols(compiled_java_path, DSO_ELF_FILE,
476 {Symbol("void ctep.v(cteo, ctgc, ctbn)", 0x0, 0x100)});
477 thread_tree.AddThreadMap(1, 1, 0x4000, 0x1000, 0x0, compiled_java_path);
478 std::vector<uint64_t> fake_ips = {
479 0x4000, // 4000, // void ctep.v(cteo, ctgc, ctbn)
480 };
481
482 CallChainReportBuilder builder(thread_tree);
483 builder.AddProguardMappingFile(tmpfile.path);
484 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
485 ASSERT_EQ(entries.size(), 1);
486 ASSERT_EQ(entries[0].ip, 0x4000);
487 ASSERT_STREQ(entries[0].symbol->DemangledName(),
488 "android.support.v4.app.RemoteActionCompatParcelizer.read");
489 ASSERT_EQ(entries[0].dso->Path(), compiled_java_path);
490 ASSERT_EQ(entries[0].vaddr_in_file, 0x0);
491 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::NATIVE_METHOD);
492 }
493 }
494
TEST_F(CallChainReportBuilderTest,convert_jit_frame_for_jit_method_with_signature)495 TEST_F(CallChainReportBuilderTest, convert_jit_frame_for_jit_method_with_signature) {
496 std::vector<uint64_t> fake_ips = {
497 0x2200, // 2200, // ctep.v
498 0x3200, // 3200, // void ctep.v(cteo, ctgc, ctbn)
499 };
500 SetSymbols(fake_dex_file_path, DSO_DEX_FILE, {Symbol("ctep.v", 0x200, 0x100)});
501 SetSymbols(fake_jit_cache_path, DSO_ELF_FILE,
502 {Symbol("void ctep.v(cteo, ctgc, ctbn)", 0x3200, 0x100)});
503 CallChainReportBuilder builder(thread_tree);
504 // Test if we can convert jit method with signature.
505 std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
506 ASSERT_EQ(entries.size(), 2);
507 ASSERT_EQ(entries[0].ip, 0x2200);
508 ASSERT_STREQ(entries[0].symbol->DemangledName(), "ctep.v");
509 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
510 ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
511 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
512 ASSERT_EQ(entries[1].ip, 0x3200);
513 ASSERT_STREQ(entries[1].symbol->DemangledName(), "ctep.v");
514 ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
515 ASSERT_EQ(entries[1].vaddr_in_file, 0x200);
516 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
517
518 // Test adding proguard mapping file.
519 TemporaryFile tmpfile;
520 close(tmpfile.release());
521 ASSERT_TRUE(android::base::WriteStringToFile(
522 "android.support.v4.app.RemoteActionCompatParcelizer -> ctep:\n"
523 " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
524 "Parcel) -> v\n",
525 tmpfile.path));
526 builder.AddProguardMappingFile(tmpfile.path);
527 entries = builder.Build(thread, fake_ips, 0);
528 ASSERT_EQ(entries.size(), 2);
529 ASSERT_EQ(entries[0].ip, 0x2200);
530 ASSERT_STREQ(entries[0].symbol->DemangledName(),
531 "android.support.v4.app.RemoteActionCompatParcelizer.read");
532 ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
533 ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
534 ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
535 ASSERT_EQ(entries[1].ip, 0x3200);
536 ASSERT_STREQ(entries[1].symbol->DemangledName(),
537 "android.support.v4.app.RemoteActionCompatParcelizer.read");
538 ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
539 ASSERT_EQ(entries[1].vaddr_in_file, 0x200);
540 ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
541 }
542
543 class ThreadReportBuilderTest : public testing::Test {
544 protected:
SetUp()545 virtual void SetUp() {
546 thread_tree.SetThreadName(1, 1, "thread1");
547 thread_tree.SetThreadName(1, 2, "thread-pool1");
548 thread_tree.SetThreadName(1, 3, "thread-pool2");
549 }
550
IsReportEqual(const ThreadReport & report1,const ThreadReport & report2)551 bool IsReportEqual(const ThreadReport& report1, const ThreadReport& report2) {
552 return report1.pid == report2.pid && report1.tid == report2.tid &&
553 strcmp(report1.thread_name, report2.thread_name) == 0;
554 }
555
556 ThreadTree thread_tree;
557 };
558
TEST_F(ThreadReportBuilderTest,no_setting)559 TEST_F(ThreadReportBuilderTest, no_setting) {
560 ThreadReportBuilder builder;
561 ThreadEntry* thread = thread_tree.FindThread(1);
562 ThreadReport report = builder.Build(*thread);
563 ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
564 }
565
TEST_F(ThreadReportBuilderTest,aggregate_threads)566 TEST_F(ThreadReportBuilderTest, aggregate_threads) {
567 ThreadReportBuilder builder;
568 ASSERT_TRUE(builder.AggregateThreads({"thread-pool.*"}));
569 ThreadEntry* thread = thread_tree.FindThread(1);
570 ThreadReport report = builder.Build(*thread);
571 ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
572 thread = thread_tree.FindThread(2);
573 report = builder.Build(*thread);
574 ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
575 thread = thread_tree.FindThread(3);
576 report = builder.Build(*thread);
577 ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
578 }
579
TEST_F(ThreadReportBuilderTest,aggregate_threads_bad_regex)580 TEST_F(ThreadReportBuilderTest, aggregate_threads_bad_regex) {
581 ThreadReportBuilder builder;
582 ASSERT_FALSE(builder.AggregateThreads({"?thread-pool*"}));
583 }
584