1 /*
2 * Copyright (C) 2021 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 <err.h>
18 #include <errno.h>
19 #include <inttypes.h>
20 #include <string.h>
21
22 #include <cstddef>
23 #include <filesystem>
24 #include <fstream>
25 #include <iostream>
26 #include <memory>
27 #include <regex>
28 #include <sstream>
29 #include <string>
30 #include <tuple>
31 #include <utility>
32 #include <vector>
33
34 #include <android-base/file.h>
35 #include <android-base/strings.h>
36 #include <zlib.h>
37
38 #include <unwindstack/Arch.h>
39 #include <unwindstack/JitDebug.h>
40 #include <unwindstack/MachineArm.h>
41 #include <unwindstack/MachineArm64.h>
42 #include <unwindstack/MachineRiscv64.h>
43 #include <unwindstack/MachineX86.h>
44 #include <unwindstack/MachineX86_64.h>
45 #include <unwindstack/Maps.h>
46 #include <unwindstack/Regs.h>
47 #include <unwindstack/RegsArm.h>
48 #include <unwindstack/RegsArm64.h>
49 #include <unwindstack/RegsRiscv64.h>
50 #include <unwindstack/RegsX86.h>
51 #include <unwindstack/RegsX86_64.h>
52 #include <unwindstack/Unwinder.h>
53
54 #include "Check.h"
55 #include "MemoryOffline.h"
56 #include "utils/MemoryFake.h"
57
58 #include "OfflineUnwindUtils.h"
59
60 namespace unwindstack {
61
DecompressFiles(const std::string & directory)62 void DecompressFiles(const std::string& directory) {
63 namespace fs = std::filesystem;
64 for (const auto& file : fs::recursive_directory_iterator(directory)) {
65 fs::path src_path = file.path();
66 if (src_path.extension() == ".gz") {
67 fs::path dst_path = fs::path(src_path).replace_extension(); // Remove .gz extension.
68 if (!fs::exists(dst_path) || fs::last_write_time(src_path) > fs::last_write_time(dst_path)) {
69 gzFile src = gzopen(src_path.c_str(), "rb");
70 CHECK(src != nullptr);
71 fs::path tmp_path = fs::path(src_path).replace_extension("." + std::to_string(getpid()));
72 std::ofstream tmp(tmp_path); // Temporary file to avoid races between unit tests.
73 char buffer[1024];
74 int size;
75 while ((size = gzread(src, buffer, sizeof(buffer))) > 0) {
76 tmp.write(buffer, size);
77 }
78 tmp.close();
79 gzclose(src);
80 fs::rename(tmp_path, dst_path);
81 }
82 }
83 }
84 }
85
CreateLinks(const std::string & directory)86 void CreateLinks(const std::string& directory) {
87 namespace fs = std::filesystem;
88 for (const auto& file : fs::recursive_directory_iterator(directory)) {
89 fs::path src_path = file.path();
90 if (fs::is_regular_file(src_path) && src_path.filename() == "links.txt") {
91 std::string contents;
92 if (!android::base::ReadFileToString(src_path.c_str(), &contents)) {
93 errx(1, "Unable to read file: %s", src_path.c_str());
94 }
95 fs::path parent_path = src_path.parent_path();
96 std::vector<std::string> lines(android::base::Split(contents, "\n"));
97 for (auto line : lines) {
98 std::string trimmed_line(android::base::Trim(line));
99 if (trimmed_line.empty()) {
100 continue;
101 }
102
103 std::vector<std::string> values(android::base::Split(trimmed_line, " "));
104 if (values.size() != 2) {
105 errx(1, "Invalid line in %s: line %s", src_path.c_str(), line.c_str());
106 }
107
108 // Create the symlink if it doesn't already exist.
109 fs::path target(parent_path);
110 target /= fs::path(values[0]);
111 fs::path source(parent_path);
112 source /= fs::path(values[1]);
113 if (!fs::exists(source)) {
114 // Ignore any errors, if this is running at the same time
115 // in multiple processes, then this might fail.
116 std::error_code ec;
117 fs::create_symlink(target, source, ec);
118 }
119 }
120 }
121 }
122 }
123
GetOfflineFilesDirectory()124 std::string GetOfflineFilesDirectory() {
125 std::string path = android::base::GetExecutableDirectory() + "/offline_files/";
126 DecompressFiles(path);
127 CreateLinks(path);
128 return path;
129 }
130
DumpFrames(const Unwinder & unwinder)131 std::string DumpFrames(const Unwinder& unwinder) {
132 std::string str;
133 for (size_t i = 0; i < unwinder.NumFrames(); i++) {
134 str += unwinder.FormatFrame(i) + "\n";
135 }
136 return str;
137 }
138
AddMemory(std::string file_name,MemoryOfflineParts * parts,std::string * error_msg)139 bool AddMemory(std::string file_name, MemoryOfflineParts* parts, std::string* error_msg) {
140 MemoryOffline* memory = new MemoryOffline;
141 if (!memory->Init(file_name.c_str(), 0)) {
142 std::stringstream err_stream;
143 err_stream << "Failed to add stack '" << file_name << "' to stack memory.";
144 *error_msg = err_stream.str();
145 return false;
146 }
147 parts->Add(memory);
148
149 return true;
150 }
151
GetRegs(const std::string & initial_sample_name) const152 Regs* OfflineUnwindUtils::GetRegs(const std::string& initial_sample_name) const {
153 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
154 std::string error_msg;
155 if (!IsValidUnwindSample(sample_name, &error_msg)) {
156 std::cerr << error_msg;
157 return nullptr;
158 }
159 return samples_.at(sample_name).regs.get();
160 }
161
GetMaps(const std::string & initial_sample_name) const162 Maps* OfflineUnwindUtils::GetMaps(const std::string& initial_sample_name) const {
163 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
164 std::string error_msg;
165 if (!IsValidUnwindSample(sample_name, &error_msg)) {
166 std::cerr << error_msg;
167 return nullptr;
168 }
169 return samples_.at(sample_name).maps.get();
170 }
171
GetProcessMemory(const std::string & initial_sample_name) const172 std::shared_ptr<Memory> OfflineUnwindUtils::GetProcessMemory(
173 const std::string& initial_sample_name) const {
174 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
175 std::string error_msg;
176 if (!IsValidUnwindSample(sample_name, &error_msg)) {
177 std::cerr << error_msg;
178 return nullptr;
179 }
180 return samples_.at(sample_name).process_memory;
181 }
182
GetJitDebug(const std::string & initial_sample_name) const183 JitDebug* OfflineUnwindUtils::GetJitDebug(const std::string& initial_sample_name) const {
184 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
185 std::string error_msg;
186 if (!IsValidUnwindSample(sample_name, &error_msg)) {
187 std::cerr << error_msg;
188 return nullptr;
189 }
190 return samples_.at(sample_name).jit_debug.get();
191 }
192
GetOfflineFilesPath(const std::string & initial_sample_name) const193 const std::string* OfflineUnwindUtils::GetOfflineFilesPath(
194 const std::string& initial_sample_name) const {
195 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
196 std::string error_msg;
197 if (!IsValidUnwindSample(sample_name, &error_msg)) {
198 std::cerr << error_msg;
199 return nullptr;
200 }
201 return &samples_.at(sample_name).offline_files_path;
202 }
203
GetFrameInfoFilepath(const std::string & initial_sample_name) const204 const std::string* OfflineUnwindUtils::GetFrameInfoFilepath(
205 const std::string& initial_sample_name) const {
206 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
207 std::string error_msg;
208 if (!IsValidUnwindSample(sample_name, &error_msg)) {
209 std::cerr << error_msg;
210 return nullptr;
211 }
212 return &samples_.at(sample_name).frame_info_filepath;
213 }
214
Init(const std::vector<UnwindSampleInfo> & sample_infos,std::string * error_msg)215 bool OfflineUnwindUtils::Init(const std::vector<UnwindSampleInfo>& sample_infos,
216 std::string* error_msg) {
217 // Save the current path so the caller can switch back to it later.
218 cwd_ = std::filesystem::current_path();
219
220 // Fill in the unwind samples.
221 std::stringstream err_stream;
222 for (const auto& sample_info : sample_infos) {
223 std::string offline_files_full_path =
224 GetOfflineFilesDirectory() + sample_info.offline_files_dir;
225 if (!std::filesystem::exists(offline_files_full_path)) {
226 err_stream << "Offline files directory '" << offline_files_full_path << "' does not exist.";
227 *error_msg = err_stream.str();
228 return false;
229 }
230 std::string frame_info_filepath = offline_files_full_path + sample_info.frame_info_filename;
231
232 std::string map_buffer;
233 if (!android::base::ReadFileToString((offline_files_full_path + "maps.txt"), &map_buffer)) {
234 err_stream << "Failed to read from '" << offline_files_full_path << "maps.txt' into memory.";
235 *error_msg = err_stream.str();
236 return false;
237 }
238
239 // CreateMaps, CreatRegs, and Create*Memory may need to be called later by the client. So we
240 // need to create the sample now in case the flags are set to call these methods in Init.
241 const std::string& sample_name = sample_info.offline_files_dir;
242 samples_.emplace(sample_name, (UnwindSample){
243 std::move(offline_files_full_path),
244 std::move(frame_info_filepath), std::move(map_buffer),
245 nullptr, // regs
246 nullptr, // maps
247 std::make_shared<MemoryFake>(), // process_memory
248 nullptr, // jit_debug
249 });
250 UnwindSample& sample = samples_.at(sample_name);
251
252 if (sample_info.create_maps) {
253 if (!CreateMaps(error_msg, sample_name)) return false;
254 }
255 if (!CreateRegs(sample_info.arch, error_msg, sample_name)) return false;
256
257 switch (sample_info.memory_flag) {
258 case ProcessMemoryFlag::kNone: {
259 if (!CreateProcessMemory(error_msg, sample_name)) return false;
260 break;
261 }
262 case ProcessMemoryFlag::kIncludeJitMemory: {
263 if (!CreateProcessMemory(error_msg, sample_name)) return false;
264 sample.jit_debug = CreateJitDebug(sample.regs->Arch(), sample.process_memory);
265 break;
266 }
267 case ProcessMemoryFlag::kNoMemory: {
268 break;
269 }
270 default: {
271 std::stringstream err_stream;
272 err_stream << "Unknown memory type for sample '" << sample_name << "'.";
273 *error_msg = err_stream.str();
274 return false;
275 }
276 }
277 }
278 initted_ = true;
279 return true;
280 }
281
Init(const UnwindSampleInfo & sample_info,std::string * error_msg)282 bool OfflineUnwindUtils::Init(const UnwindSampleInfo& sample_info, std::string* error_msg) {
283 if (Init(std::vector<UnwindSampleInfo>{sample_info}, error_msg)) {
284 if (!ChangeToSampleDirectory(error_msg)) return false;
285 return true;
286 }
287 return false;
288 }
289
ChangeToSampleDirectory(std::string * error_msg,const std::string & initial_sample_name) const290 bool OfflineUnwindUtils::ChangeToSampleDirectory(std::string* error_msg,
291 const std::string& initial_sample_name) const {
292 if (!initted_) {
293 *error_msg =
294 "Cannot change to sample directory because OfflineUnwindUtils::Init has not been called.";
295 return false;
296 }
297 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
298 if (!IsValidUnwindSample(sample_name, error_msg)) return false;
299
300 std::filesystem::current_path(std::filesystem::path(samples_.at(sample_name).offline_files_path));
301 return true;
302 }
303
GetExpectedNumFrames(size_t * expected_num_frames,std::string * error_msg,const std::string & initial_sample_name) const304 bool OfflineUnwindUtils::GetExpectedNumFrames(size_t* expected_num_frames, std::string* error_msg,
305 const std::string& initial_sample_name) const {
306 if (!initted_) {
307 *error_msg =
308 "Cannot get expected number of frames of a sample because OfflineUnwindUtils::Init has not "
309 "been called.";
310 return false;
311 }
312 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
313 if (!IsValidUnwindSample(sample_name, error_msg)) return false;
314
315 const std::string& sample_frames_path = samples_.at(sample_name).frame_info_filepath;
316 if (!std::filesystem::exists(sample_frames_path)) {
317 std::stringstream err_stream;
318 err_stream << "Offline files directory '" << sample_frames_path << "' does not exist.";
319 *error_msg = err_stream.str();
320 return false;
321 }
322
323 std::ifstream in(sample_frames_path);
324 in.unsetf(std::ios_base::skipws); // Ensure that we do not skip newlines.
325 *expected_num_frames =
326 std::count(std::istream_iterator<char>(in), std::istream_iterator<char>(), '\n');
327
328 return true;
329 }
330
CreateMaps(std::string * error_msg,const std::string & initial_sample_name)331 bool OfflineUnwindUtils::CreateMaps(std::string* error_msg,
332 const std::string& initial_sample_name) {
333 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
334 if (!IsValidUnwindSample(sample_name, error_msg)) return false;
335 UnwindSample& sample = samples_.at(sample_name);
336
337 sample.maps.reset(new BufferMaps(sample.map_buffer.c_str()));
338 if (!sample.maps->Parse()) {
339 *error_msg = "Failed to parse offline maps.";
340 return false;
341 }
342 return true;
343 }
344
CreateProcessMemory(std::string * error_msg,const std::string & initial_sample_name)345 bool OfflineUnwindUtils::CreateProcessMemory(std::string* error_msg,
346 const std::string& initial_sample_name) {
347 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
348 if (!IsValidUnwindSample(sample_name, error_msg)) return false;
349 UnwindSample& sample = samples_.at(sample_name);
350
351 // Construct process memory from all descriptor, stack, entry, and jit files
352 auto memory = std::make_unique<MemoryOfflineParts>();
353 bool data_files_found = false;
354 for (const auto& file : std::filesystem::directory_iterator(sample.offline_files_path)) {
355 std::string filename = file.path().string();
356 if (std::regex_match(filename,
357 std::regex("^(.+)\\/(descriptor|stack|entry|jit)(\\d*)\\.data$"))) {
358 data_files_found = true;
359 if (!AddMemory(filename, memory.get(), error_msg)) return false;
360 }
361 }
362 if (!data_files_found) {
363 *error_msg = "No memory (stack, JIT, etc.) data files found.";
364 return false;
365 }
366
367 sample.process_memory.reset(memory.release());
368 return true;
369 }
370
371 namespace {
372 template <typename AddressType>
ReadRegs(RegsImpl<AddressType> * regs,const std::unordered_map<std::string,uint32_t> & name_to_reg,std::string * error_msg,const std::string & offline_files_path)373 bool ReadRegs(RegsImpl<AddressType>* regs,
374 const std::unordered_map<std::string, uint32_t>& name_to_reg, std::string* error_msg,
375 const std::string& offline_files_path) {
376 std::stringstream err_stream;
377 FILE* fp = fopen((offline_files_path + "regs.txt").c_str(), "r");
378 if (fp == nullptr) {
379 err_stream << "Error opening file '" << offline_files_path << "regs.txt': " << strerror(errno);
380 *error_msg = err_stream.str();
381 return false;
382 }
383
384 while (!feof(fp)) {
385 uint64_t value;
386 char reg_name[100];
387 if (fscanf(fp, "%[^:]: %" SCNx64 "\n", reg_name, &value) != 2) {
388 err_stream << "Failed to read in register name/values from '" << offline_files_path
389 << "regs.txt'.";
390 *error_msg = err_stream.str();
391 return false;
392 }
393 std::string name(reg_name);
394 auto entry = name_to_reg.find(name);
395 if (entry == name_to_reg.end()) {
396 err_stream << "Unknown register named " << name;
397 *error_msg = err_stream.str();
398 return false;
399 }
400 (*regs)[entry->second] = value;
401 }
402 fclose(fp);
403 return true;
404 }
405 } // namespace
406
CreateRegs(ArchEnum arch,std::string * error_msg,const std::string & initial_sample_name)407 bool OfflineUnwindUtils::CreateRegs(ArchEnum arch, std::string* error_msg,
408 const std::string& initial_sample_name) {
409 const std::string& sample_name = GetAdjustedSampleName(initial_sample_name);
410 if (!IsValidUnwindSample(sample_name, error_msg)) return false;
411 auto& regs = samples_.at(sample_name).regs;
412 const auto& offline_files_path = samples_.at(sample_name).offline_files_path;
413
414 switch (arch) {
415 case ARCH_ARM: {
416 RegsArm* regs_impl = new RegsArm;
417 regs.reset(regs_impl);
418 if (!ReadRegs<uint32_t>(regs_impl, arm_regs_, error_msg, offline_files_path)) return false;
419 break;
420 }
421 case ARCH_ARM64: {
422 RegsArm64* regs_impl = new RegsArm64;
423 regs.reset(regs_impl);
424 if (!ReadRegs<uint64_t>(regs_impl, arm64_regs_, error_msg, offline_files_path)) return false;
425 break;
426 }
427 case ARCH_RISCV64: {
428 RegsRiscv64* regs_impl = new RegsRiscv64;
429 regs.reset(regs_impl);
430 if (!ReadRegs<uint64_t>(regs_impl, riscv64_regs_, error_msg, offline_files_path))
431 return false;
432 break;
433 }
434 case ARCH_X86: {
435 RegsX86* regs_impl = new RegsX86;
436 regs.reset(regs_impl);
437 if (!ReadRegs<uint32_t>(regs_impl, x86_regs_, error_msg, offline_files_path)) return false;
438 break;
439 }
440 case ARCH_X86_64: {
441 RegsX86_64* regs_impl = new RegsX86_64;
442 regs.reset(regs_impl);
443 if (!ReadRegs<uint64_t>(regs_impl, x86_64_regs_, error_msg, offline_files_path)) return false;
444 break;
445 }
446 default:
447 *error_msg = "Unknown architechture " + std::to_string(arch);
448 return false;
449 }
450
451 return true;
452 }
453
GetAdjustedSampleName(const std::string & initial_sample_name) const454 const std::string& OfflineUnwindUtils::GetAdjustedSampleName(
455 const std::string& initial_sample_name) const {
456 // Only return the first entry in the sample map if this is the single unwind use case.
457 // Otherwise return the inputted sample name so we can check if that is a valid sample name.
458 if (initial_sample_name == kSingleSample && samples_.size() == 1) {
459 return samples_.begin()->first;
460 }
461 return initial_sample_name;
462 }
463
IsValidUnwindSample(const std::string & sample_name,std::string * error_msg) const464 bool OfflineUnwindUtils::IsValidUnwindSample(const std::string& sample_name,
465 std::string* error_msg) const {
466 if (samples_.find(sample_name) == samples_.end()) {
467 std::stringstream err_stream;
468 err_stream << "Invalid sample name (offline file directory) '" << sample_name << "'.";
469 if (sample_name == kSingleSample) {
470 err_stream << " An explicit sample name must be provided for the multiple unwind use case "
471 "of OfflineUnwindUtils (i.e. should not use the default sample name).";
472 }
473 *error_msg = err_stream.str();
474 return false;
475 }
476 return true;
477 }
478
479 std::unordered_map<std::string, uint32_t> OfflineUnwindUtils::arm_regs_ = {
480 {"r0", ARM_REG_R0}, {"r1", ARM_REG_R1}, {"r2", ARM_REG_R2}, {"r3", ARM_REG_R3},
481 {"r4", ARM_REG_R4}, {"r5", ARM_REG_R5}, {"r6", ARM_REG_R6}, {"r7", ARM_REG_R7},
482 {"r8", ARM_REG_R8}, {"r9", ARM_REG_R9}, {"r10", ARM_REG_R10}, {"r11", ARM_REG_R11},
483 {"ip", ARM_REG_R12}, {"sp", ARM_REG_SP}, {"lr", ARM_REG_LR}, {"pc", ARM_REG_PC},
484 };
485
486 std::unordered_map<std::string, uint32_t> OfflineUnwindUtils::arm64_regs_ = {
487 {"x0", ARM64_REG_R0}, {"x1", ARM64_REG_R1}, {"x2", ARM64_REG_R2},
488 {"x3", ARM64_REG_R3}, {"x4", ARM64_REG_R4}, {"x5", ARM64_REG_R5},
489 {"x6", ARM64_REG_R6}, {"x7", ARM64_REG_R7}, {"x8", ARM64_REG_R8},
490 {"x9", ARM64_REG_R9}, {"x10", ARM64_REG_R10}, {"x11", ARM64_REG_R11},
491 {"x12", ARM64_REG_R12}, {"x13", ARM64_REG_R13}, {"x14", ARM64_REG_R14},
492 {"x15", ARM64_REG_R15}, {"x16", ARM64_REG_R16}, {"x17", ARM64_REG_R17},
493 {"x18", ARM64_REG_R18}, {"x19", ARM64_REG_R19}, {"x20", ARM64_REG_R20},
494 {"x21", ARM64_REG_R21}, {"x22", ARM64_REG_R22}, {"x23", ARM64_REG_R23},
495 {"x24", ARM64_REG_R24}, {"x25", ARM64_REG_R25}, {"x26", ARM64_REG_R26},
496 {"x27", ARM64_REG_R27}, {"x28", ARM64_REG_R28}, {"x29", ARM64_REG_R29},
497 {"sp", ARM64_REG_SP}, {"lr", ARM64_REG_LR}, {"pc", ARM64_REG_PC},
498 {"pst", ARM64_REG_PSTATE},
499 };
500
501 std::unordered_map<std::string, uint32_t> OfflineUnwindUtils::riscv64_regs_ = {
502 {"pc", RISCV64_REG_PC}, {"ra", RISCV64_REG_RA}, {"sp", RISCV64_REG_SP},
503 {"gp", RISCV64_REG_GP}, {"tp", RISCV64_REG_TP}, {"a0", RISCV64_REG_A0},
504 {"a1", RISCV64_REG_A1}, {"a2", RISCV64_REG_A2}, {"a3", RISCV64_REG_A3},
505 {"a4", RISCV64_REG_A4}, {"a5", RISCV64_REG_A5}, {"a6", RISCV64_REG_A6},
506 {"a7", RISCV64_REG_A7}, {"s0", RISCV64_REG_S0}, {"s1", RISCV64_REG_S1},
507 {"s2", RISCV64_REG_S2}, {"s3", RISCV64_REG_S3}, {"s4", RISCV64_REG_S4},
508 {"s5", RISCV64_REG_S5}, {"s6", RISCV64_REG_S6}, {"s7", RISCV64_REG_S7},
509 {"s8", RISCV64_REG_S8}, {"s9", RISCV64_REG_S9}, {"s10", RISCV64_REG_S10},
510 {"s11", RISCV64_REG_S11}, {"t0", RISCV64_REG_T0}, {"t1", RISCV64_REG_T1},
511 {"t2", RISCV64_REG_T2}, {"t3", RISCV64_REG_T3}, {"t4", RISCV64_REG_T4},
512 {"t5", RISCV64_REG_T5}, {"t6", RISCV64_REG_T6}, {"vlenb", RISCV64_REG_VLENB},
513 };
514
515 std::unordered_map<std::string, uint32_t> OfflineUnwindUtils::x86_regs_ = {
516 {"eax", X86_REG_EAX}, {"ebx", X86_REG_EBX}, {"ecx", X86_REG_ECX},
517 {"edx", X86_REG_EDX}, {"ebp", X86_REG_EBP}, {"edi", X86_REG_EDI},
518 {"esi", X86_REG_ESI}, {"esp", X86_REG_ESP}, {"eip", X86_REG_EIP},
519 };
520
521 std::unordered_map<std::string, uint32_t> OfflineUnwindUtils::x86_64_regs_ = {
522 {"rax", X86_64_REG_RAX}, {"rbx", X86_64_REG_RBX}, {"rcx", X86_64_REG_RCX},
523 {"rdx", X86_64_REG_RDX}, {"r8", X86_64_REG_R8}, {"r9", X86_64_REG_R9},
524 {"r10", X86_64_REG_R10}, {"r11", X86_64_REG_R11}, {"r12", X86_64_REG_R12},
525 {"r13", X86_64_REG_R13}, {"r14", X86_64_REG_R14}, {"r15", X86_64_REG_R15},
526 {"rdi", X86_64_REG_RDI}, {"rsi", X86_64_REG_RSI}, {"rbp", X86_64_REG_RBP},
527 {"rsp", X86_64_REG_RSP}, {"rip", X86_64_REG_RIP},
528 };
529
530 } // namespace unwindstack
531