summaryrefslogtreecommitdiff
path: root/libunwindstack/DexFile.cpp
blob: cb2a28ac716a8ecff587b893d0d9c8e7edf48ff9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <memory>

#include <android-base/unique_fd.h>
#include <art_api/dex_file_support.h>

#include <unwindstack/Log.h>
#include <unwindstack/MapInfo.h>
#include <unwindstack/Memory.h>

#include "DexFile.h"
#include "MemoryBuffer.h"

namespace unwindstack {

std::map<DexFile::MappedFileKey, std::weak_ptr<DexFile::DexFileApi>> DexFile::g_mapped_dex_files;
std::mutex DexFile::g_lock;

static bool CheckDexSupport() {
  if (std::string err_msg; !art_api::dex::TryLoadLibdexfile(&err_msg)) {
    Log::Error("Failed to initialize DEX file support: %s", err_msg.c_str());
    return false;
  }
  return true;
}

std::shared_ptr<DexFile> DexFile::CreateFromDisk(uint64_t addr, uint64_t size, MapInfo* map) {
  if (map == nullptr || map->name().empty()) {
    return nullptr;  // MapInfo not backed by file.
  }
  if (!(map->start() <= addr && addr < map->end())) {
    return nullptr;  // addr is not in MapInfo range.
  }
  if (size > (map->end() - addr)) {
    return nullptr;  // size is past the MapInfo end.
  }
  uint64_t offset_in_file = (addr - map->start()) + map->offset();

  // Fast-path: Check if the dex file was already mapped from disk.
  std::lock_guard<std::mutex> guard(g_lock);
  MappedFileKey cache_key(map->name(), offset_in_file, size);
  std::weak_ptr<DexFileApi>& cache_entry = g_mapped_dex_files[cache_key];
  std::shared_ptr<DexFileApi> dex_api = cache_entry.lock();
  if (dex_api != nullptr) {
    return std::shared_ptr<DexFile>(new DexFile(addr, size, std::move(dex_api)));
  }

  // Load the file from disk and cache it.
  auto memory = Memory::CreateFileMemory(map->name(), offset_in_file, size);
  if (memory == nullptr) {
    return nullptr;  // failed to map the file.
  }
  std::unique_ptr<art_api::dex::DexFile> dex;
  art_api::dex::DexFile::Create(memory->GetPtr(), size, nullptr, map->name().c_str(), &dex);
  if (dex == nullptr) {
    return nullptr;  // invalid DEX file.
  }
  dex_api.reset(new DexFileApi{std::move(dex), memory, std::mutex()});
  cache_entry = dex_api;
  return std::shared_ptr<DexFile>(new DexFile(addr, size, std::move(dex_api)));
}

std::shared_ptr<DexFile> DexFile::Create(uint64_t base_addr, uint64_t file_size, Memory* memory,
                                         MapInfo* info) {
  static bool has_dex_support = CheckDexSupport();
  if (!has_dex_support || file_size == 0) {
    return nullptr;
  }

  // Do not try to open the DEX file if the file name ends with "(deleted)". It does not exist.
  // This happens when an app is background-optimized by ART and all of its files are replaced.
  // Furthermore, do NOT try to fallback to in-memory copy. It would work, but all apps tend to
  // be background-optimized at the same time, so it would lead to excessive memory use during
  // system-wide profiling (essentially copying all dex files for all apps: hundreds of MBs).
  // This will cause missing symbols in the backtrace, however, that outcome is inevitable
  // anyway, since we can not obtain mini-debug-info for the deleted .oat files.
  const std::string_view filename(info != nullptr ? info->name() : "");
  const std::string_view kDeleted("(deleted)");
  if (filename.size() >= kDeleted.size() &&
      filename.substr(filename.size() - kDeleted.size()) == kDeleted) {
    return nullptr;
  }

  std::shared_ptr<DexFile> dex_file = CreateFromDisk(base_addr, file_size, info);
  if (dex_file != nullptr) {
    return dex_file;
  }

  // Fallback: make copy in local buffer.
  std::unique_ptr<MemoryBuffer> copy(new MemoryBuffer);
  if (!copy->Resize(file_size)) {
    return nullptr;
  }
  if (!memory->ReadFully(base_addr, copy->GetPtr(0), file_size)) {
    return nullptr;
  }
  std::unique_ptr<art_api::dex::DexFile> dex;
  art_api::dex::DexFile::Create(copy->GetPtr(0), file_size, nullptr, "", &dex);
  if (dex == nullptr) {
    return nullptr;
  }
  std::shared_ptr<DexFileApi> api(new DexFileApi{std::move(dex), std::move(copy), std::mutex()});
  return std::shared_ptr<DexFile>(new DexFile(base_addr, file_size, std::move(api)));
}

bool DexFile::GetFunctionName(uint64_t dex_pc, SharedString* method_name, uint64_t* method_offset) {
  uint64_t dex_offset = dex_pc - base_addr_;  // Convert absolute PC to file-relative offset.

  // Lookup the function in the cache.
  std::lock_guard<std::mutex> guard(dex_api_->lock_);  // Protect both the symbols and the C API.
  auto it = symbols_.upper_bound(dex_offset);
  if (it == symbols_.end() || dex_offset < it->second.offset) {
    // Lookup the function in the underlying dex file.
    size_t found = dex_api_->dex_->FindMethodAtOffset(dex_offset, [&](const auto& method) {
      size_t code_size, name_size;
      uint32_t offset = method.GetCodeOffset(&code_size);
      const char* name = method.GetQualifiedName(/*with_params=*/false, &name_size);
      it = symbols_.emplace(offset + code_size, Info{offset, std::string(name, name_size)}).first;
    });
    if (found == 0) {
      return false;
    }
  }

  // Return the found function.
  *method_offset = dex_offset - it->second.offset;
  *method_name = it->second.name;
  return true;
}

}  // namespace unwindstack