• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <string>
20 #include <vector>
21 
22 #include "record_file.h"
23 #include "report_utils.h"
24 #include "thread_tree.h"
25 
26 using namespace simpleperf;
27 
28 class CallChainReportBuilderTest : public testing::Test {
29  protected:
SetUp()30   virtual void SetUp() {
31     // To test different options for CallChainReportBuilder, we create a fake thread, add fake
32     // libraries used by the thread, and provide fake symbols in each library. We need four
33     // types of libraries: native, interpreter, jit cache and dex file.
34     thread_tree.SetThreadName(1, 1, "thread1");
35     thread = thread_tree.FindThread(1);
36 
37     // Add symbol info for the native library.
38     FileFeature file;
39     file.path = fake_native_lib_path;
40     file.type = DSO_ELF_FILE;
41     file.min_vaddr = file.file_offset_of_min_vaddr = 0;
42     file.symbols = {
43         Symbol("native_func1", 0x0, 0x100),
44         Symbol("art_jni_trampoline", 0x100, 0x100),
45     };
46     thread_tree.AddDsoInfo(file);
47 
48     // Add symbol info for the interpreter library.
49     file.path = fake_interpreter_path;
50     file.type = DSO_ELF_FILE;
51     file.min_vaddr = file.file_offset_of_min_vaddr = 0;
52     file.symbols = {
53         Symbol("art_func1", 0x0, 0x100),
54         Symbol("art_func2", 0x100, 0x100),
55         Symbol("_ZN3artL13Method_invokeEP7_JNIEnvP8_jobjectS3_P13_jobjectArray", 0x200, 0x100),
56     };
57     thread_tree.AddDsoInfo(file);
58 
59     // Add symbol info for the dex file.
60     file.path = fake_dex_file_path;
61     file.type = DSO_DEX_FILE;
62     file.min_vaddr = file.file_offset_of_min_vaddr = 0;
63     file.symbols = {
64         Symbol("java_method1", 0x0, 0x100),
65         Symbol("java_method2", 0x100, 0x100),
66         Symbol("obfuscated_class.obfuscated_java_method", 0x200, 0x100),
67     };
68     thread_tree.AddDsoInfo(file);
69 
70     // Add symbol info for the jit cache.
71     file.path = fake_jit_cache_path;
72     file.type = DSO_ELF_FILE;
73     file.min_vaddr = file.file_offset_of_min_vaddr = 0;
74     file.symbols = {
75         Symbol("java_method2", 0x3000, 0x100),
76         Symbol("java_method3", 0x3100, 0x100),
77         Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100),
78     };
79     thread_tree.AddDsoInfo(file);
80 
81     // Add map layout for libraries used in the thread:
82     // 0x0000 - 0x1000 is mapped to the native library.
83     // 0x1000 - 0x2000 is mapped to the interpreter library.
84     // 0x2000 - 0x3000 is mapped to the dex file.
85     // 0x3000 - 0x4000 is mapped to the jit cache.
86     thread_tree.AddThreadMap(1, 1, 0x0, 0x1000, 0x0, fake_native_lib_path);
87     thread_tree.AddThreadMap(1, 1, 0x1000, 0x1000, 0x0, fake_interpreter_path);
88     thread_tree.AddThreadMap(1, 1, 0x2000, 0x1000, 0x0, fake_dex_file_path);
89     thread_tree.AddThreadMap(1, 1, 0x3000, 0x1000, 0x0, fake_jit_cache_path,
90                              map_flags::PROT_JIT_SYMFILE_MAP);
91   }
92 
93   ThreadTree thread_tree;
94   const ThreadEntry* thread;
95   const std::string fake_native_lib_path = "fake_dir/fake_native_lib.so";
96   const std::string fake_interpreter_path = "fake_dir/libart.so";
97   const std::string fake_dex_file_path = "fake_dir/framework.jar";
98   const std::string fake_jit_cache_path = "fake_jit_app_cache:0";
99 
100   const std::vector<uint64_t> fake_ips = {
101       0x1000,  // art_func1
102       0x1100,  // art_func2
103       0x2000,  // java_method1 in dex file
104       0x1000,  // art_func1
105       0x1100,  // art_func2
106       0x3000,  // java_method2 in jit cache
107       0x1000,  // art_func1
108       0x1100,  // art_func2
109   };
110 };
111 
TEST_F(CallChainReportBuilderTest,default_option)112 TEST_F(CallChainReportBuilderTest, default_option) {
113   // Test default option: remove_art_frame = true, convert_jit_frame = true.
114   // The callchain shouldn't include interpreter frames. And the JIT frame is
115   // converted to a dex frame.
116   CallChainReportBuilder builder(thread_tree);
117   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
118   ASSERT_EQ(entries.size(), 2);
119   ASSERT_EQ(entries[0].ip, 0x2000);
120   ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
121   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
122   ASSERT_EQ(entries[0].vaddr_in_file, 0);
123   ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
124   ASSERT_EQ(entries[1].ip, 0x3000);
125   ASSERT_STREQ(entries[1].symbol->Name(), "java_method2");
126   ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
127   ASSERT_EQ(entries[1].vaddr_in_file, 0x100);
128   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
129 }
130 
TEST_F(CallChainReportBuilderTest,not_convert_jit_frame)131 TEST_F(CallChainReportBuilderTest, not_convert_jit_frame) {
132   // Test option: remove_art_frame = true, convert_jit_frame = false.
133   // The callchain shouldn't include interpreter frames. And the JIT frame isn't
134   // converted to a dex frame.
135   CallChainReportBuilder builder(thread_tree);
136   builder.SetConvertJITFrame(false);
137   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
138   ASSERT_EQ(entries.size(), 2);
139   ASSERT_EQ(entries[0].ip, 0x2000);
140   ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
141   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
142   ASSERT_EQ(entries[0].vaddr_in_file, 0);
143   ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
144   ASSERT_EQ(entries[1].ip, 0x3000);
145   ASSERT_STREQ(entries[1].symbol->Name(), "java_method2");
146   ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
147   ASSERT_EQ(entries[1].vaddr_in_file, 0x3000);
148   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
149 }
150 
TEST_F(CallChainReportBuilderTest,not_remove_art_frame)151 TEST_F(CallChainReportBuilderTest, not_remove_art_frame) {
152   // Test option: remove_art_frame = false, convert_jit_frame = true.
153   // The callchain should include interpreter frames. And the JIT frame is
154   // converted to a dex frame.
155   CallChainReportBuilder builder(thread_tree);
156   builder.SetRemoveArtFrame(false);
157   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
158   ASSERT_EQ(entries.size(), 8);
159   for (size_t i : {0, 3, 6}) {
160     ASSERT_EQ(entries[i].ip, 0x1000);
161     ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
162     ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
163     ASSERT_EQ(entries[i].vaddr_in_file, 0);
164     ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::ART_METHOD);
165     ASSERT_EQ(entries[i + 1].ip, 0x1100);
166     ASSERT_STREQ(entries[i + 1].symbol->Name(), "art_func2");
167     ASSERT_EQ(entries[i + 1].dso->Path(), fake_interpreter_path);
168     ASSERT_EQ(entries[i + 1].vaddr_in_file, 0x100);
169     ASSERT_EQ(entries[i + 1].execution_type, CallChainExecutionType::ART_METHOD);
170   }
171   ASSERT_EQ(entries[2].ip, 0x2000);
172   ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
173   ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
174   ASSERT_EQ(entries[2].vaddr_in_file, 0);
175   ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
176   ASSERT_EQ(entries[5].ip, 0x3000);
177   ASSERT_STREQ(entries[5].symbol->Name(), "java_method2");
178   ASSERT_EQ(entries[5].dso->Path(), fake_dex_file_path);
179   ASSERT_EQ(entries[5].vaddr_in_file, 0x100);
180   ASSERT_EQ(entries[5].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
181 }
182 
TEST_F(CallChainReportBuilderTest,remove_jit_frame_called_by_dex_frame)183 TEST_F(CallChainReportBuilderTest, remove_jit_frame_called_by_dex_frame) {
184   // Test option: remove_art_frame = true, convert_jit_frame = true.
185   // The callchain should remove the JIT frame called by a dex frame having the same symbol name.
186   std::vector<uint64_t> fake_ips = {
187       0x3000,  // java_method2 in jit cache
188       0x1000,  // art_func1
189       0x1100,  // art_func2
190       0x2100,  // java_method2 in dex file
191       0x1000,  // art_func1
192   };
193   CallChainReportBuilder builder(thread_tree);
194   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
195   ASSERT_EQ(entries.size(), 1);
196   ASSERT_EQ(entries[0].ip, 0x2100);
197   ASSERT_STREQ(entries[0].symbol->Name(), "java_method2");
198   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
199   ASSERT_EQ(entries[0].vaddr_in_file, 0x100);
200   ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
201 }
202 
TEST_F(CallChainReportBuilderTest,remove_art_frame_only_near_jvm_method)203 TEST_F(CallChainReportBuilderTest, remove_art_frame_only_near_jvm_method) {
204   // Test option: remove_art_frame = true, convert_jit_frame = true.
205   // The callchain should not remove ART symbols not near a JVM method.
206   std::vector<uint64_t> fake_ips = {
207       0x1000,  // art_func1
208       0x0,     // native_func1
209       0x2000,  // java_method1 in dex file
210       0x0,     // native_func1
211       0x1000,  // art_func1
212   };
213   CallChainReportBuilder builder(thread_tree);
214   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
215   ASSERT_EQ(entries.size(), 5);
216   for (size_t i : {0, 4}) {
217     ASSERT_EQ(entries[i].ip, 0x1000);
218     ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
219     ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
220     ASSERT_EQ(entries[i].vaddr_in_file, 0);
221     ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
222   }
223   for (size_t i : {1, 3}) {
224     ASSERT_EQ(entries[i].ip, 0x0);
225     ASSERT_STREQ(entries[i].symbol->Name(), "native_func1");
226     ASSERT_EQ(entries[i].dso->Path(), fake_native_lib_path);
227     ASSERT_EQ(entries[i].vaddr_in_file, 0);
228     ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
229   }
230 
231   ASSERT_EQ(entries[2].ip, 0x2000);
232   ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
233   ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
234   ASSERT_EQ(entries[2].vaddr_in_file, 0x0);
235   ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
236 }
237 
TEST_F(CallChainReportBuilderTest,keep_art_jni_method)238 TEST_F(CallChainReportBuilderTest, keep_art_jni_method) {
239   // Test option: remove_art_frame = true.
240   // The callchain should remove art_jni_trampoline, but keep jni methods.
241   std::vector<uint64_t> fake_ips = {
242       0x1200,  // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
243       0x100,   // art_jni_trampoline
244       0x2000,  // java_method1 in dex file
245       0x1200,  // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
246       0x100,   // art_jni_trampoline
247   };
248   CallChainReportBuilder builder(thread_tree);
249   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
250   ASSERT_EQ(entries.size(), 3);
251   for (size_t i : {0, 2}) {
252     ASSERT_EQ(entries[i].ip, 0x1200);
253     ASSERT_STREQ(entries[i].symbol->DemangledName(),
254                  "art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)");
255     ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
256     ASSERT_EQ(entries[i].vaddr_in_file, 0x200);
257     ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
258   }
259   ASSERT_EQ(entries[1].ip, 0x2000);
260   ASSERT_STREQ(entries[1].symbol->Name(), "java_method1");
261   ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
262   ASSERT_EQ(entries[1].vaddr_in_file, 0x0);
263   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
264 }
265 
TEST_F(CallChainReportBuilderTest,add_proguard_mapping_file)266 TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
267   std::vector<uint64_t> fake_ips = {
268       0x2200,  // 2200,  // obfuscated_class.obfuscated_java_method
269       0x3200,  // 3200,  // obfuscated_class.obfuscated_java_method2
270   };
271   CallChainReportBuilder builder(thread_tree);
272   // Symbol names aren't changed when not given proguard mapping files.
273   std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
274   ASSERT_EQ(entries.size(), 2);
275   ASSERT_EQ(entries[0].ip, 0x2200);
276   ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method");
277   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
278   ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
279   ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
280   ASSERT_EQ(entries[1].ip, 0x3200);
281   ASSERT_STREQ(entries[1].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method2");
282   ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
283   ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
284   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
285 
286   // Symbol names are changed when given a proguard mapping file.
287   TemporaryFile tmpfile;
288   close(tmpfile.release());
289   ASSERT_TRUE(android::base::WriteStringToFile(
290       "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
291       "    13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
292       "Parcel) -> obfuscated_java_method\n"
293       "    13:13:androidx.core.app.RemoteActionCompat "
294       "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
295       "VersionedParcel) -> obfuscated_java_method2",
296       tmpfile.path));
297   builder.AddProguardMappingFile(tmpfile.path);
298   entries = builder.Build(thread, fake_ips, 0);
299   ASSERT_EQ(entries.size(), 2);
300   ASSERT_EQ(entries[0].ip, 0x2200);
301   ASSERT_STREQ(entries[0].symbol->DemangledName(),
302                "android.support.v4.app.RemoteActionCompatParcelizer.read");
303   ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
304   ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
305   ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
306   ASSERT_EQ(entries[1].ip, 0x3200);
307   ASSERT_STREQ(entries[1].symbol->DemangledName(),
308                "android.support.v4.app.RemoteActionCompatParcelizer.read2");
309   ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
310   ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
311   ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
312 }
313