1 /*
2  * Copyright (C) 2018 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 "art_api/dex_file_external.h"
18 
19 #include <inttypes.h>
20 #include <stdint.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 
25 #include <algorithm>
26 #include <cerrno>
27 #include <cstring>
28 #include <deque>
29 #include <map>
30 #include <memory>
31 #include <string>
32 #include <utility>
33 #include <vector>
34 
35 #include <android-base/logging.h>
36 #include <android-base/macros.h>
37 #include <android-base/mapped_file.h>
38 #include <android-base/stringprintf.h>
39 
40 #include <dex/class_accessor-inl.h>
41 #include <dex/code_item_accessors-inl.h>
42 #include <dex/dex_file-inl.h>
43 #include <dex/dex_file_loader.h>
44 
45 extern "C" {
46 
47 struct ADexFile_Method {
48   ADexFile* adex;
49   uint32_t index;
50   size_t offset;
51   size_t size;
52 };
53 
54 // Opaque implementation of ADexFile for the C interface.
55 struct ADexFile {
ADexFileADexFile56   explicit ADexFile(std::unique_ptr<const art::DexFile> dex_file)
57       : dex_file_(std::move(dex_file)) {}
58 
FindMethodADexFile59   inline bool FindMethod(uint32_t dex_offset, /*out*/ ADexFile_Method* result) {
60     uint32_t class_def_index;
61     if (GetClassDefIndex(dex_offset, &class_def_index)) {
62       art::ClassAccessor accessor(*dex_file_, class_def_index);
63       for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
64         art::CodeItemInstructionAccessor code = method.GetInstructions();
65         if (!code.HasCodeItem()) {
66           continue;
67         }
68         size_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
69         size_t size = code.InsnsSizeInBytes();
70         if (offset <= dex_offset && dex_offset < offset + size) {
71           *result = ADexFile_Method {
72             .adex = this,
73             .index = method.GetIndex(),
74             .offset = offset,
75             .size = size,
76           };
77           return true;
78         }
79       }
80     }
81     return false;
82   }
83 
CreateClassCacheADexFile84   void CreateClassCache() {
85     // Create binary search table with (end_dex_offset, class_def_index) entries.
86     // That is, we don't assume that dex code of given class is consecutive.
87     std::deque<std::pair<uint32_t, uint32_t>> cache;
88     for (art::ClassAccessor accessor : dex_file_->GetClasses()) {
89       for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
90         art::CodeItemInstructionAccessor code = method.GetInstructions();
91         if (code.HasCodeItem()) {
92           int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
93           DCHECK_NE(offset, 0);
94           cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex());
95         }
96       }
97     }
98     std::sort(cache.begin(), cache.end());
99 
100     // If two consecutive methods belong to same class, we can merge them.
101     // This tends to reduce the number of entries (used memory) by 10x.
102     size_t num_entries = cache.size();
103     if (cache.size() > 1) {
104       for (auto it = std::next(cache.begin()); it != cache.end(); it++) {
105         if (std::prev(it)->second == it->second) {
106           std::prev(it)->first = 0;  // Clear entry with lower end_dex_offset (mark to remove).
107           num_entries--;
108         }
109       }
110     }
111 
112     // The cache is immutable now. Store it as continuous vector to save space.
113     class_cache_.reserve(num_entries);
114     auto pred = [](auto it) { return it.first != 0; };  // Entries to copy (not cleared above).
115     std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred);
116   }
117 
GetClassDefIndexADexFile118   inline bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) {
119     if (class_cache_.empty()) {
120       CreateClassCache();
121     }
122 
123     // Binary search in the class cache. First element of the pair is the key.
124     auto comp = [](uint32_t value, const auto& it) { return value < it.first; };
125     auto it = std::upper_bound(class_cache_.begin(), class_cache_.end(), dex_offset, comp);
126     if (it != class_cache_.end()) {
127       *class_def_index = it->second;
128       return true;
129     }
130     return false;
131   }
132 
133   // The underlying ART object.
134   std::unique_ptr<const art::DexFile> dex_file_;
135 
136   // Binary search table with (end_dex_offset, class_def_index) entries.
137   std::vector<std::pair<uint32_t, uint32_t>> class_cache_;
138 
139   // Used as short lived temporary when needed. Avoids alloc/free.
140   std::string temporary_qualified_name_;
141 };
142 
ADexFile_create(const void * _Nonnull address,size_t size,size_t * _Nullable new_size,const char * _Nonnull location,ADexFile * _Nullable * _Nonnull out_dex_file)143 ADexFile_Error ADexFile_create(const void* _Nonnull address,
144                                size_t size,
145                                size_t* _Nullable new_size,
146                                const char* _Nonnull location,
147                                /*out*/ ADexFile* _Nullable * _Nonnull out_dex_file) {
148   *out_dex_file = nullptr;
149 
150   if (size < sizeof(art::DexFile::Header)) {
151     if (new_size != nullptr) {
152       *new_size = sizeof(art::DexFile::Header);
153     }
154     return ADEXFILE_ERROR_NOT_ENOUGH_DATA;
155   }
156 
157   const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(address);
158   if (size < header->header_size_) {
159     if (new_size != nullptr) {
160       *new_size = header->header_size_;
161     }
162     return ADEXFILE_ERROR_NOT_ENOUGH_DATA;
163   }
164 
165   uint32_t dex_size = header->file_size_;  // Size of "one dex file" excluding any shared data.
166   uint32_t full_size = dex_size;           // Includes referenced shared data past the end of dex.
167   if (art::CompactDexFile::IsMagicValid(header->magic_)) {
168     // Compact dex files store the data section separately so that it can be shared.
169     // Therefore we need to extend the read memory range to include it.
170     // TODO: This might be wasteful as we might read data in between as well.
171     //       In practice, this should be fine, as such sharing only happens on disk.
172     uint32_t computed_file_size;
173     if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) {
174       return ADEXFILE_ERROR_INVALID_HEADER;
175     }
176     if (computed_file_size > full_size) {
177       full_size = computed_file_size;
178     }
179   } else if (art::StandardDexFile::IsMagicValid(header->magic_)) {
180     full_size = header->ContainerSize() - header->HeaderOffset();
181   } else {
182     return ADEXFILE_ERROR_INVALID_HEADER;
183   }
184 
185   if (size < full_size) {
186     if (new_size != nullptr) {
187       *new_size = full_size;
188     }
189     return ADEXFILE_ERROR_NOT_ENOUGH_DATA;
190   }
191 
192   std::string loc_str(location);
193   std::string error_msg;
194   art::DexFileLoader loader(static_cast<const uint8_t*>(address), full_size, loc_str);
195   std::unique_ptr<const art::DexFile> dex_file = loader.OpenOne(/*header_offset=*/0,
196                                                                 header->checksum_,
197                                                                 /*oat_dex_file=*/nullptr,
198                                                                 /*verify=*/false,
199                                                                 /*verify_checksum=*/false,
200                                                                 &error_msg);
201   if (dex_file == nullptr) {
202     LOG(ERROR) << "Can not open dex file " << loc_str << ": " << error_msg;
203     return ADEXFILE_ERROR_INVALID_DEX;
204   }
205 
206   *out_dex_file = new ADexFile(std::move(dex_file));
207   return ADEXFILE_ERROR_OK;
208 }
209 
ADexFile_destroy(ADexFile * self)210 void ADexFile_destroy(ADexFile* self) {
211   delete self;
212 }
213 
ADexFile_findMethodAtOffset(ADexFile * self,size_t dex_offset,ADexFile_MethodCallback * callback,void * callback_data)214 size_t ADexFile_findMethodAtOffset(ADexFile* self,
215                                    size_t dex_offset,
216                                    ADexFile_MethodCallback* callback,
217                                    void* callback_data) {
218   const art::DexFile* dex_file = self->dex_file_.get();
219   if (!dex_file->IsInDataSection(dex_file->Begin() + dex_offset)) {
220     return 0;  // The DEX offset is not within the bytecode of this dex file.
221   }
222 
223   if (dex_file->IsCompactDexFile()) {
224     // The data section of compact dex files might be shared.
225     // Check the subrange unique to this compact dex.
226     const art::CompactDexFile::Header& cdex_header =
227         dex_file->AsCompactDexFile()->GetHeader();
228     uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin();
229     uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd();
230     if (dex_offset < begin || dex_offset >= end) {
231       return 0;  // The DEX offset is not within the bytecode of this dex file.
232     }
233   }
234 
235   ADexFile_Method info;
236   if (!self->FindMethod(dex_offset, &info)) {
237     return 0;
238   }
239 
240   callback(callback_data, &info);
241   return 1;
242 }
243 
ADexFile_forEachMethod(ADexFile * self,ADexFile_MethodCallback * callback,void * callback_data)244 size_t ADexFile_forEachMethod(ADexFile* self,
245                               ADexFile_MethodCallback* callback,
246                               void* callback_data) {
247   size_t count = 0;
248   for (art::ClassAccessor accessor : self->dex_file_->GetClasses()) {
249     for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
250       art::CodeItemInstructionAccessor code = method.GetInstructions();
251       if (code.HasCodeItem()) {
252         size_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - self->dex_file_->Begin();
253         ADexFile_Method info {
254           .adex = self,
255           .index = method.GetIndex(),
256           .offset = offset,
257           .size = code.InsnsSizeInBytes(),
258         };
259         callback(callback_data, &info);
260         count++;
261       }
262     }
263   }
264   return count;
265 }
266 
ADexFile_Method_getCodeOffset(const ADexFile_Method * self,size_t * out_size)267 size_t ADexFile_Method_getCodeOffset(const ADexFile_Method* self,
268                                      size_t* out_size) {
269   if (out_size != nullptr) {
270     *out_size = self->size;
271   }
272   return self->offset;
273 }
274 
ADexFile_Method_getName(const ADexFile_Method * self,size_t * out_size)275 const char* ADexFile_Method_getName(const ADexFile_Method* self,
276                                     size_t* out_size) {
277   const char* name = self->adex->dex_file_->GetMethodName(self->index);
278   if (out_size != nullptr) {
279     *out_size = strlen(name);
280   }
281   return name;
282 }
283 
ADexFile_Method_getQualifiedName(const ADexFile_Method * self,int with_params,size_t * out_size)284 const char* ADexFile_Method_getQualifiedName(const ADexFile_Method* self,
285                                              int with_params,
286                                              size_t* out_size) {
287   std::string& temp = self->adex->temporary_qualified_name_;
288   temp.clear();
289   self->adex->dex_file_->AppendPrettyMethod(self->index, with_params, &temp);
290   if (out_size != nullptr) {
291     *out_size = temp.size();
292   }
293   return temp.data();
294 }
295 
ADexFile_Method_getClassDescriptor(const ADexFile_Method * self,size_t * out_size)296 const char* ADexFile_Method_getClassDescriptor(const ADexFile_Method* self,
297                                                size_t* out_size) {
298   const art::dex::MethodId& method_id = self->adex->dex_file_->GetMethodId(self->index);
299   const char* name = self->adex->dex_file_->GetMethodDeclaringClassDescriptor(method_id);
300   if (out_size != nullptr) {
301     *out_size = strlen(name);
302   }
303   return name;
304 }
305 
ADexFile_Error_toString(ADexFile_Error self)306 const char* ADexFile_Error_toString(ADexFile_Error self) {
307   switch (self) {
308     case ADEXFILE_ERROR_OK: return "Ok";
309     case ADEXFILE_ERROR_INVALID_DEX: return "Dex file is invalid.";
310     case ADEXFILE_ERROR_NOT_ENOUGH_DATA: return "Not enough data. Incomplete dex file.";
311     case ADEXFILE_ERROR_INVALID_HEADER: return "Invalid dex file header.";
312   }
313   return nullptr;
314 }
315 
316 }  // extern "C"
317