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