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