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/mman.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
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 namespace art {
46 namespace {
47
48 struct MethodCacheEntry {
49 int32_t offset; // Offset relative to the start of the dex file header.
50 int32_t len;
51 int32_t index; // Method index.
52 };
53
54 class MappedFileContainer : public DexFileContainer {
55 public:
MappedFileContainer(std::unique_ptr<android::base::MappedFile> && map)56 explicit MappedFileContainer(std::unique_ptr<android::base::MappedFile>&& map)
57 : map_(std::move(map)) {}
~MappedFileContainer()58 ~MappedFileContainer() override {}
GetPermissions()59 int GetPermissions() override { return 0; }
IsReadOnly()60 bool IsReadOnly() override { return true; }
EnableWrite()61 bool EnableWrite() override { return false; }
DisableWrite()62 bool DisableWrite() override { return false; }
63
64 private:
65 std::unique_ptr<android::base::MappedFile> map_;
66 DISALLOW_COPY_AND_ASSIGN(MappedFileContainer);
67 };
68
69 } // namespace
70 } // namespace art
71
72 extern "C" {
73
74 struct ExtDexFileString {
75 const std::string str_;
76 };
77
78 static const ExtDexFileString empty_string{""};
79
ExtDexFileMakeString(const char * str,size_t size)80 const ExtDexFileString* ExtDexFileMakeString(const char* str, size_t size) {
81 if (size == 0) {
82 return &empty_string;
83 }
84 return new ExtDexFileString{std::string(str, size)};
85 }
86
ExtDexFileGetString(const ExtDexFileString * ext_string,size_t * size)87 const char* ExtDexFileGetString(const ExtDexFileString* ext_string, /*out*/ size_t* size) {
88 DCHECK(ext_string != nullptr);
89 *size = ext_string->str_.size();
90 return ext_string->str_.data();
91 }
92
ExtDexFileFreeString(const ExtDexFileString * ext_string)93 void ExtDexFileFreeString(const ExtDexFileString* ext_string) {
94 DCHECK(ext_string != nullptr);
95 if (ext_string != &empty_string) {
96 delete (ext_string);
97 }
98 }
99
100 // Wraps DexFile to add the caching needed by the external interface. This is
101 // what gets passed over as ExtDexFile*.
102 struct ExtDexFile {
103 public:
104 std::unique_ptr<const art::DexFile> dex_file_;
ExtDexFileExtDexFile105 explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file)
106 : dex_file_(std::move(dex_file)) {}
107
GetMethodCacheEntryForOffsetExtDexFile108 art::MethodCacheEntry* GetMethodCacheEntryForOffset(int64_t dex_offset) {
109 // First look in the method cache.
110 auto it = method_cache_.upper_bound(dex_offset);
111 if (it != method_cache_.end() && dex_offset >= it->second.offset) {
112 return &it->second;
113 }
114
115 uint32_t class_def_index;
116 if (GetClassDefIndex(dex_offset, &class_def_index)) {
117 art::ClassAccessor accessor(*dex_file_, class_def_index);
118
119 for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
120 art::CodeItemInstructionAccessor code = method.GetInstructions();
121 if (!code.HasCodeItem()) {
122 continue;
123 }
124
125 int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
126 int32_t len = code.InsnsSizeInBytes();
127 if (offset <= dex_offset && dex_offset < offset + len) {
128 int32_t index = method.GetIndex();
129 auto res = method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index});
130 return &res.first->second;
131 }
132 }
133 }
134
135 return nullptr;
136 }
137
138 private:
GetClassDefIndexExtDexFile139 bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) {
140 if (class_cache_.empty()) {
141 // Create binary search table with (end_dex_offset, class_def_index) entries.
142 // That is, we don't assume that dex code of given class is consecutive.
143 std::deque<std::pair<uint32_t, uint32_t>> cache;
144 for (art::ClassAccessor accessor : dex_file_->GetClasses()) {
145 for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
146 art::CodeItemInstructionAccessor code = method.GetInstructions();
147 if (code.HasCodeItem()) {
148 int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin();
149 DCHECK_NE(offset, 0);
150 cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex());
151 }
152 }
153 }
154 std::sort(cache.begin(), cache.end());
155
156 // If two consecutive methods belong to same class, we can merge them.
157 // This tends to reduce the number of entries (used memory) by 10x.
158 size_t num_entries = cache.size();
159 if (cache.size() > 1) {
160 for (auto it = std::next(cache.begin()); it != cache.end(); it++) {
161 if (std::prev(it)->second == it->second) {
162 std::prev(it)->first = 0; // Clear entry with lower end_dex_offset (mark to remove).
163 num_entries--;
164 }
165 }
166 }
167
168 // The cache is immutable now. Store it as continuous vector to save space.
169 class_cache_.reserve(num_entries);
170 auto pred = [](auto it) { return it.first != 0; }; // Entries to copy (not cleared above).
171 std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred);
172 }
173
174 // Binary search in the class cache. First element of the pair is the key.
175 auto comp = [](uint32_t value, const auto& it) { return value < it.first; };
176 auto it = std::upper_bound(class_cache_.begin(), class_cache_.end(), dex_offset, comp);
177 if (it != class_cache_.end()) {
178 *class_def_index = it->second;
179 return true;
180 }
181 return false;
182 }
183
184 // Binary search table with (end_dex_offset, class_def_index) entries.
185 std::vector<std::pair<uint32_t, uint32_t>> class_cache_;
186 std::map<uint32_t, art::MethodCacheEntry> method_cache_; // end_dex_offset -> method.
187 };
188
ExtDexFileOpenFromMemory(const void * addr,size_t * size,const char * location,const ExtDexFileString ** ext_error_msg,ExtDexFile ** ext_dex_file)189 int ExtDexFileOpenFromMemory(const void* addr,
190 /*inout*/ size_t* size,
191 const char* location,
192 /*out*/ const ExtDexFileString** ext_error_msg,
193 /*out*/ ExtDexFile** ext_dex_file) {
194 if (*size < sizeof(art::DexFile::Header)) {
195 *size = sizeof(art::DexFile::Header);
196 *ext_error_msg = nullptr;
197 return false;
198 }
199
200 const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(addr);
201 uint32_t file_size = header->file_size_;
202 if (art::CompactDexFile::IsMagicValid(header->magic_)) {
203 // Compact dex files store the data section separately so that it can be shared.
204 // Therefore we need to extend the read memory range to include it.
205 // TODO: This might be wasteful as we might read data in between as well.
206 // In practice, this should be fine, as such sharing only happens on disk.
207 uint32_t computed_file_size;
208 if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) {
209 *ext_error_msg = new ExtDexFileString{
210 android::base::StringPrintf("Corrupt CompactDexFile header in '%s'", location)};
211 return false;
212 }
213 if (computed_file_size > file_size) {
214 file_size = computed_file_size;
215 }
216 } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) {
217 *ext_error_msg = new ExtDexFileString{
218 android::base::StringPrintf("Unrecognized dex file header in '%s'", location)};
219 return false;
220 }
221
222 if (*size < file_size) {
223 *size = file_size;
224 *ext_error_msg = nullptr;
225 return false;
226 }
227
228 std::string loc_str(location);
229 art::DexFileLoader loader;
230 std::string error_msg;
231 std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(addr),
232 *size,
233 loc_str,
234 header->checksum_,
235 /*oat_dex_file=*/nullptr,
236 /*verify=*/false,
237 /*verify_checksum=*/false,
238 &error_msg);
239 if (dex_file == nullptr) {
240 *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
241 return false;
242 }
243
244 *ext_dex_file = new ExtDexFile(std::move(dex_file));
245 return true;
246 }
247
ExtDexFileOpenFromFd(int fd,off_t offset,const char * location,const ExtDexFileString ** ext_error_msg,ExtDexFile ** ext_dex_file)248 int ExtDexFileOpenFromFd(int fd,
249 off_t offset,
250 const char* location,
251 /*out*/ const ExtDexFileString** ext_error_msg,
252 /*out*/ ExtDexFile** ext_dex_file) {
253 size_t length;
254 {
255 struct stat sbuf;
256 std::memset(&sbuf, 0, sizeof(sbuf));
257 if (fstat(fd, &sbuf) == -1) {
258 *ext_error_msg = new ExtDexFileString{
259 android::base::StringPrintf("fstat '%s' failed: %s", location, std::strerror(errno))};
260 return false;
261 }
262 if (S_ISDIR(sbuf.st_mode)) {
263 *ext_error_msg = new ExtDexFileString{
264 android::base::StringPrintf("Attempt to mmap directory '%s'", location)};
265 return false;
266 }
267 length = sbuf.st_size;
268 }
269
270 if (length < offset + sizeof(art::DexFile::Header)) {
271 *ext_error_msg = new ExtDexFileString{android::base::StringPrintf(
272 "Offset %" PRId64 " too large for '%s' of size %zu",
273 int64_t{offset},
274 location,
275 length)};
276 return false;
277 }
278
279 // Cannot use MemMap in libartbase here, because it pulls in dlopen which we
280 // can't have when being compiled statically.
281 std::unique_ptr<android::base::MappedFile> map =
282 android::base::MappedFile::FromFd(fd, offset, length, PROT_READ);
283 if (map == nullptr) {
284 *ext_error_msg = new ExtDexFileString{
285 android::base::StringPrintf("mmap '%s' failed: %s", location, std::strerror(errno))};
286 return false;
287 }
288
289 const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(map->data());
290 uint32_t file_size;
291 if (__builtin_add_overflow(offset, header->file_size_, &file_size)) {
292 *ext_error_msg =
293 new ExtDexFileString{android::base::StringPrintf("Corrupt header in '%s'", location)};
294 return false;
295 }
296 if (length < file_size) {
297 *ext_error_msg = new ExtDexFileString{
298 android::base::StringPrintf("Dex file '%s' too short: expected %" PRIu32 ", got %" PRIu64,
299 location,
300 file_size,
301 uint64_t{length})};
302 return false;
303 }
304
305 void* addr = map->data();
306 size_t size = map->size();
307 auto container = std::make_unique<art::MappedFileContainer>(std::move(map));
308
309 std::string loc_str(location);
310 std::string error_msg;
311 art::DexFileLoader loader;
312 std::unique_ptr<const art::DexFile> dex_file = loader.Open(reinterpret_cast<const uint8_t*>(addr),
313 size,
314 loc_str,
315 header->checksum_,
316 /*oat_dex_file=*/nullptr,
317 /*verify=*/false,
318 /*verify_checksum=*/false,
319 &error_msg,
320 std::move(container));
321 if (dex_file == nullptr) {
322 *ext_error_msg = new ExtDexFileString{std::move(error_msg)};
323 return false;
324 }
325 *ext_dex_file = new ExtDexFile(std::move(dex_file));
326 return true;
327 }
328
ExtDexFileGetMethodInfoForOffset(ExtDexFile * ext_dex_file,int64_t dex_offset,int with_signature,ExtDexFileMethodInfo * method_info)329 int ExtDexFileGetMethodInfoForOffset(ExtDexFile* ext_dex_file,
330 int64_t dex_offset,
331 int with_signature,
332 /*out*/ ExtDexFileMethodInfo* method_info) {
333 if (!ext_dex_file->dex_file_->IsInDataSection(ext_dex_file->dex_file_->Begin() + dex_offset)) {
334 return false; // The DEX offset is not within the bytecode of this dex file.
335 }
336
337 if (ext_dex_file->dex_file_->IsCompactDexFile()) {
338 // The data section of compact dex files might be shared.
339 // Check the subrange unique to this compact dex.
340 const art::CompactDexFile::Header& cdex_header =
341 ext_dex_file->dex_file_->AsCompactDexFile()->GetHeader();
342 uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin();
343 uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd();
344 if (dex_offset < begin || dex_offset >= end) {
345 return false; // The DEX offset is not within the bytecode of this dex file.
346 }
347 }
348
349 art::MethodCacheEntry* entry = ext_dex_file->GetMethodCacheEntryForOffset(dex_offset);
350 if (entry != nullptr) {
351 method_info->offset = entry->offset;
352 method_info->len = entry->len;
353 method_info->name =
354 new ExtDexFileString{ext_dex_file->dex_file_->PrettyMethod(entry->index, with_signature)};
355 return true;
356 }
357
358 return false;
359 }
360
ExtDexFileGetAllMethodInfos(ExtDexFile * ext_dex_file,int with_signature,ExtDexFileMethodInfoCallback * method_info_cb,void * user_data)361 void ExtDexFileGetAllMethodInfos(ExtDexFile* ext_dex_file,
362 int with_signature,
363 ExtDexFileMethodInfoCallback* method_info_cb,
364 void* user_data) {
365 for (art::ClassAccessor accessor : ext_dex_file->dex_file_->GetClasses()) {
366 for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
367 art::CodeItemInstructionAccessor code = method.GetInstructions();
368 if (!code.HasCodeItem()) {
369 continue;
370 }
371
372 ExtDexFileMethodInfo method_info;
373 method_info.offset = static_cast<int32_t>(reinterpret_cast<const uint8_t*>(code.Insns()) -
374 ext_dex_file->dex_file_->Begin());
375 method_info.len = code.InsnsSizeInBytes();
376 method_info.name = new ExtDexFileString{
377 ext_dex_file->dex_file_->PrettyMethod(method.GetIndex(), with_signature)};
378 method_info_cb(&method_info, user_data);
379 }
380 }
381 }
382
ExtDexFileFree(ExtDexFile * ext_dex_file)383 void ExtDexFileFree(ExtDexFile* ext_dex_file) { delete (ext_dex_file); }
384
385 } // extern "C"
386