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