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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
/*
* Copyright (C) 2017 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 <cstddef>
#include <atomic>
#include <deque>
#include <map>
#include <memory>
#include <unordered_set>
#include <vector>
#include <unwindstack/Elf.h>
#include <unwindstack/JitDebug.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Memory.h>
#if !defined(NO_LIBDEXFILE_SUPPORT)
#include <DexFile.h>
#endif
// This implements the JIT Compilation Interface.
// See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html
namespace unwindstack {
// 32-bit platforms may differ in alignment of uint64_t.
struct Uint64_P {
uint64_t value;
} __attribute__((packed));
struct Uint64_A {
uint64_t value;
} __attribute__((aligned(8)));
// Wrapper around other memory object which protects us against data races.
// It will check seqlock after every read, and fail if the seqlock changed.
// This ensues that the read memory has not been partially modified.
struct JitMemory : public Memory {
size_t Read(uint64_t addr, void* dst, size_t size) override;
Memory* parent_ = nullptr;
uint64_t seqlock_addr_ = 0;
uint32_t expected_seqlock_ = 0;
bool failed_due_to_race_ = false;
};
template <typename Symfile>
struct JitCacheEntry {
// PC memory range described by this entry.
uint64_t addr_ = 0;
uint64_t size_ = 0;
std::unique_ptr<Symfile> symfile_;
bool Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t size);
};
template <typename Symfile, typename PointerT, typename Uint64_T>
class JitDebugImpl : public JitDebug<Symfile>, public Global {
public:
static constexpr const char* kDescriptorExtMagic = "Android1";
static constexpr int kMaxRaceRetries = 16;
struct JITCodeEntry {
PointerT next;
PointerT prev;
PointerT symfile_addr;
Uint64_T symfile_size;
};
struct JITDescriptor {
uint32_t version;
uint32_t action_flag;
PointerT relevant_entry;
PointerT first_entry;
};
// Android-specific extensions.
struct JITDescriptorExt {
JITDescriptor desc;
uint8_t magic[8];
uint32_t flags;
uint32_t sizeof_descriptor;
uint32_t sizeof_entry;
uint32_t action_seqlock;
uint64_t action_timestamp;
};
JitDebugImpl(ArchEnum arch, std::shared_ptr<Memory>& memory,
std::vector<std::string>& search_libs)
: Global(memory, search_libs) {
SetArch(arch);
}
Symfile* Get(Maps* maps, uint64_t pc) override;
virtual bool ReadVariableData(uint64_t offset);
virtual void ProcessArch() {}
bool Update(Maps* maps);
bool Read(Maps* maps, JitMemory* memory);
bool initialized_ = false;
uint64_t descriptor_addr_ = 0; // Non-zero if we have found (non-empty) descriptor.
uint64_t seqlock_addr_ = 0; // Re-read entries if the value at this address changes.
uint32_t last_seqlock_ = ~0u; // The value of seqlock when we last read the entries.
std::deque<JitCacheEntry<Symfile>> entries_;
std::mutex lock_;
};
template <typename Symfile>
std::unique_ptr<JitDebug<Symfile>> JitDebug<Symfile>::Create(ArchEnum arch,
std::shared_ptr<Memory>& memory,
std::vector<std::string> search_libs) {
typedef JitDebugImpl<Symfile, uint32_t, Uint64_P> JitDebugImpl32P;
typedef JitDebugImpl<Symfile, uint32_t, Uint64_A> JitDebugImpl32A;
typedef JitDebugImpl<Symfile, uint64_t, Uint64_A> JitDebugImpl64A;
switch (arch) {
case ARCH_X86:
static_assert(sizeof(typename JitDebugImpl32P::JITCodeEntry) == 20, "layout");
static_assert(sizeof(typename JitDebugImpl32P::JITDescriptor) == 16, "layout");
static_assert(sizeof(typename JitDebugImpl32P::JITDescriptorExt) == 48, "layout");
return std::unique_ptr<JitDebug>(new JitDebugImpl32P(arch, memory, search_libs));
break;
case ARCH_ARM:
case ARCH_MIPS:
static_assert(sizeof(typename JitDebugImpl32A::JITCodeEntry) == 24, "layout");
static_assert(sizeof(typename JitDebugImpl32A::JITDescriptor) == 16, "layout");
static_assert(sizeof(typename JitDebugImpl32A::JITDescriptorExt) == 48, "layout");
return std::unique_ptr<JitDebug>(new JitDebugImpl32A(arch, memory, search_libs));
break;
case ARCH_ARM64:
case ARCH_X86_64:
case ARCH_MIPS64:
static_assert(sizeof(typename JitDebugImpl64A::JITCodeEntry) == 32, "layout");
static_assert(sizeof(typename JitDebugImpl64A::JITDescriptor) == 24, "layout");
static_assert(sizeof(typename JitDebugImpl64A::JITDescriptorExt) == 56, "layout");
return std::unique_ptr<JitDebug>(new JitDebugImpl64A(arch, memory, search_libs));
break;
default:
abort();
}
}
size_t JitMemory::Read(uint64_t addr, void* dst, size_t size) {
if (!parent_->ReadFully(addr, dst, size)) {
return 0;
}
// This is required for memory synchronization if the we are working with local memory.
// For other types of memory (e.g. remote) this is no-op and has no significant effect.
std::atomic_thread_fence(std::memory_order_acquire);
uint32_t seen_seqlock;
if (!parent_->Read32(seqlock_addr_, &seen_seqlock)) {
return 0;
}
if (seen_seqlock != expected_seqlock_) {
failed_due_to_race_ = true;
return 0;
}
return size;
}
template <typename Symfile, typename PointerT, typename Uint64_T>
bool JitDebugImpl<Symfile, PointerT, Uint64_T>::ReadVariableData(uint64_t addr) {
JITDescriptor desc;
if (!this->memory_->ReadFully(addr, &desc, sizeof(desc))) {
return false;
}
if (desc.version != 1) {
return false;
}
if (desc.first_entry == 0) {
return false; // There could be multiple descriptors. Ignore empty ones.
}
descriptor_addr_ = addr;
JITDescriptorExt desc_ext;
if (this->memory_->ReadFully(addr, &desc_ext, sizeof(desc_ext)) &&
memcmp(desc_ext.magic, kDescriptorExtMagic, 8) == 0) {
seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptorExt, action_seqlock);
} else {
// In the absence of Android-specific fields, use the head pointer instead.
seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptor, first_entry);
}
return true;
}
template <typename Symfile>
static const char* GetDescriptorName();
template <>
const char* GetDescriptorName<Elf>() {
return "__jit_debug_descriptor";
}
template <typename Symfile, typename PointerT, typename Uint64_T>
Symfile* JitDebugImpl<Symfile, PointerT, Uint64_T>::Get(Maps* maps, uint64_t pc) {
std::lock_guard<std::mutex> guard(lock_);
if (!initialized_) {
FindAndReadVariable(maps, GetDescriptorName<Symfile>());
initialized_ = true;
}
if (descriptor_addr_ == 0) {
return nullptr;
}
if (!Update(maps)) {
return nullptr;
}
Symfile* fallback = nullptr;
for (auto& entry : entries_) {
// Skip entries which are obviously not relevant (if we know the PC range).
if (entry.size_ == 0 || (entry.addr_ <= pc && (pc - entry.addr_) < entry.size_)) {
// Double check the entry contains the PC in case there are overlapping entries.
// This is might happen for native-code due to GC and for DEX due to data sharing.
std::string method_name;
uint64_t method_offset;
if (entry.symfile_->GetFunctionName(pc, &method_name, &method_offset)) {
return entry.symfile_.get();
}
fallback = entry.symfile_.get(); // Tests don't have any symbols.
}
}
return fallback; // Not found.
}
// Update JIT entries if needed. It will retry if there are data races.
template <typename Symfile, typename PointerT, typename Uint64_T>
bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Update(Maps* maps) {
// We might need to retry the whole read in the presence of data races.
for (int i = 0; i < kMaxRaceRetries; i++) {
// Read the seqlock (counter which is incremented before and after any modification).
uint32_t seqlock = 0;
if (!this->memory_->Read32(seqlock_addr_, &seqlock)) {
return false; // Failed to read seqlock.
}
// Check if anything changed since the last time we checked.
if (last_seqlock_ != seqlock) {
// Create memory wrapper to allow us to read the entries safely even in a live process.
JitMemory safe_memory;
safe_memory.parent_ = this->memory_.get();
safe_memory.seqlock_addr_ = seqlock_addr_;
safe_memory.expected_seqlock_ = seqlock;
std::atomic_thread_fence(std::memory_order_acquire);
// Add all entries to our cache.
if (!Read(maps, &safe_memory)) {
if (safe_memory.failed_due_to_race_) {
sleep(0);
continue; // Try again (there was a data race).
} else {
return false; // Proper failure (we could not read the data).
}
}
last_seqlock_ = seqlock;
}
return true;
}
return false; // Too many retries.
}
// Read all JIT entries. It might randomly fail due to data races.
template <typename Symfile, typename PointerT, typename Uint64_T>
bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Read(Maps* maps, JitMemory* memory) {
std::unordered_set<uint64_t> seen_entry_addr;
// Read and verify the descriptor (must be after we have read the initial seqlock).
JITDescriptor desc;
if (!(memory->ReadFully(descriptor_addr_, &desc, sizeof(desc)))) {
return false;
}
entries_.clear();
JITCodeEntry entry;
for (uint64_t entry_addr = desc.first_entry; entry_addr != 0; entry_addr = entry.next) {
// Check for infinite loops in the lined list.
if (!seen_entry_addr.emplace(entry_addr).second) {
return true; // TODO: Fail when seening infinite loop.
}
// Read the entry (while checking for data races).
if (!memory->ReadFully(entry_addr, &entry, sizeof(entry))) {
return false;
}
// Copy and load the symfile.
entries_.emplace_back(JitCacheEntry<Symfile>());
if (!entries_.back().Init(maps, memory, entry.symfile_addr, entry.symfile_size.value)) {
return false;
}
}
return true;
}
// Copy and load ELF file.
template <>
bool JitCacheEntry<Elf>::Init(Maps*, JitMemory* memory, uint64_t addr, uint64_t size) {
// Make a copy of the in-memory symbol file (while checking for data races).
std::unique_ptr<MemoryBuffer> buffer(new MemoryBuffer());
buffer->Resize(size);
if (!memory->ReadFully(addr, buffer->GetPtr(0), buffer->Size())) {
return false;
}
// Load and validate the ELF file.
symfile_.reset(new Elf(buffer.release()));
symfile_->Init();
if (!symfile_->valid()) {
return false;
}
symfile_->GetTextRange(&addr_, &size_);
return true;
}
template std::unique_ptr<JitDebug<Elf>> JitDebug<Elf>::Create(ArchEnum, std::shared_ptr<Memory>&,
std::vector<std::string>);
#if !defined(NO_LIBDEXFILE_SUPPORT)
template <>
const char* GetDescriptorName<DexFile>() {
return "__dex_debug_descriptor";
}
// Copy and load DEX file.
template <>
bool JitCacheEntry<DexFile>::Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t) {
MapInfo* info = maps->Find(addr);
if (info == nullptr) {
return false;
}
symfile_ = DexFile::Create(addr, memory, info);
if (symfile_ == nullptr) {
return false;
}
return true;
}
template std::unique_ptr<JitDebug<DexFile>> JitDebug<DexFile>::Create(ArchEnum,
std::shared_ptr<Memory>&,
std::vector<std::string>);
#endif
} // namespace unwindstack
|