1 //===-- UnwindPlan.cpp ----------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "lldb/Symbol/UnwindPlan.h"
10
11 #include "lldb/Target/Process.h"
12 #include "lldb/Target/RegisterContext.h"
13 #include "lldb/Target/Target.h"
14 #include "lldb/Target/Thread.h"
15 #include "lldb/Utility/ConstString.h"
16 #include "lldb/Utility/LLDBLog.h"
17 #include "lldb/Utility/Log.h"
18 #include "llvm/ADT/STLExtras.h"
19 #include "llvm/DebugInfo/DIContext.h"
20 #include "llvm/DebugInfo/DWARF/DWARFExpressionPrinter.h"
21 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
22 #include <optional>
23
24 using namespace lldb;
25 using namespace lldb_private;
26
operator ==(const UnwindPlan::Row::AbstractRegisterLocation & rhs) const27 bool UnwindPlan::Row::AbstractRegisterLocation::operator==(
28 const UnwindPlan::Row::AbstractRegisterLocation &rhs) const {
29 if (m_type == rhs.m_type) {
30 switch (m_type) {
31 case unspecified:
32 case undefined:
33 case same:
34 return true;
35
36 case atCFAPlusOffset:
37 case isCFAPlusOffset:
38 case atAFAPlusOffset:
39 case isAFAPlusOffset:
40 return m_location.offset == rhs.m_location.offset;
41
42 case inOtherRegister:
43 return m_location.reg_num == rhs.m_location.reg_num;
44
45 case atDWARFExpression:
46 case isDWARFExpression:
47 if (m_location.expr.length == rhs.m_location.expr.length)
48 return !memcmp(m_location.expr.opcodes, rhs.m_location.expr.opcodes,
49 m_location.expr.length);
50 break;
51 case isConstant:
52 return m_location.constant_value == rhs.m_location.constant_value;
53 }
54 }
55 return false;
56 }
57
58 // This function doesn't copy the dwarf expression bytes; they must remain in
59 // allocated memory for the lifespan of this UnwindPlan object.
SetAtDWARFExpression(const uint8_t * opcodes,uint32_t len)60 void UnwindPlan::Row::AbstractRegisterLocation::SetAtDWARFExpression(
61 const uint8_t *opcodes, uint32_t len) {
62 m_type = atDWARFExpression;
63 m_location.expr.opcodes = opcodes;
64 m_location.expr.length = len;
65 }
66
67 // This function doesn't copy the dwarf expression bytes; they must remain in
68 // allocated memory for the lifespan of this UnwindPlan object.
SetIsDWARFExpression(const uint8_t * opcodes,uint32_t len)69 void UnwindPlan::Row::AbstractRegisterLocation::SetIsDWARFExpression(
70 const uint8_t *opcodes, uint32_t len) {
71 m_type = isDWARFExpression;
72 m_location.expr.opcodes = opcodes;
73 m_location.expr.length = len;
74 }
75
76 static std::optional<std::pair<lldb::ByteOrder, uint32_t>>
GetByteOrderAndAddrSize(Thread * thread)77 GetByteOrderAndAddrSize(Thread *thread) {
78 if (!thread)
79 return std::nullopt;
80 ProcessSP process_sp = thread->GetProcess();
81 if (!process_sp)
82 return std::nullopt;
83 ArchSpec arch = process_sp->GetTarget().GetArchitecture();
84 return std::make_pair(arch.GetByteOrder(), arch.GetAddressByteSize());
85 }
86
DumpDWARFExpr(Stream & s,llvm::ArrayRef<uint8_t> expr,Thread * thread)87 static void DumpDWARFExpr(Stream &s, llvm::ArrayRef<uint8_t> expr, Thread *thread) {
88 if (auto order_and_width = GetByteOrderAndAddrSize(thread)) {
89 llvm::DataExtractor data(expr, order_and_width->first == eByteOrderLittle,
90 order_and_width->second);
91 llvm::DWARFExpression E(data, order_and_width->second,
92 llvm::dwarf::DWARF32);
93 printDwarfExpression(&E, s.AsRawOstream(), llvm::DIDumpOptions(), nullptr);
94 } else
95 s.PutCString("dwarf-expr");
96 }
97
Dump(Stream & s,const UnwindPlan * unwind_plan,const UnwindPlan::Row * row,Thread * thread,bool verbose) const98 void UnwindPlan::Row::AbstractRegisterLocation::Dump(
99 Stream &s, const UnwindPlan *unwind_plan, const UnwindPlan::Row *row,
100 Thread *thread, bool verbose) const {
101 switch (m_type) {
102 case unspecified:
103 if (verbose)
104 s.PutCString("=<unspec>");
105 else
106 s.PutCString("=!");
107 break;
108 case undefined:
109 if (verbose)
110 s.PutCString("=<undef>");
111 else
112 s.PutCString("=?");
113 break;
114 case same:
115 s.PutCString("= <same>");
116 break;
117
118 case atCFAPlusOffset:
119 case isCFAPlusOffset: {
120 s.PutChar('=');
121 if (m_type == atCFAPlusOffset)
122 s.PutChar('[');
123 s.Printf("CFA%+d", m_location.offset);
124 if (m_type == atCFAPlusOffset)
125 s.PutChar(']');
126 } break;
127
128 case atAFAPlusOffset:
129 case isAFAPlusOffset: {
130 s.PutChar('=');
131 if (m_type == atAFAPlusOffset)
132 s.PutChar('[');
133 s.Printf("AFA%+d", m_location.offset);
134 if (m_type == atAFAPlusOffset)
135 s.PutChar(']');
136 } break;
137
138 case inOtherRegister: {
139 const RegisterInfo *other_reg_info = nullptr;
140 if (unwind_plan)
141 other_reg_info = unwind_plan->GetRegisterInfo(thread, m_location.reg_num);
142 if (other_reg_info)
143 s.Printf("=%s", other_reg_info->name);
144 else
145 s.Printf("=reg(%u)", m_location.reg_num);
146 } break;
147
148 case atDWARFExpression:
149 case isDWARFExpression: {
150 s.PutChar('=');
151 if (m_type == atDWARFExpression)
152 s.PutChar('[');
153 DumpDWARFExpr(
154 s, llvm::ArrayRef(m_location.expr.opcodes, m_location.expr.length),
155 thread);
156 if (m_type == atDWARFExpression)
157 s.PutChar(']');
158 } break;
159 case isConstant:
160 s.Printf("=0x%" PRIx64, m_location.constant_value);
161 break;
162 }
163 }
164
DumpRegisterName(Stream & s,const UnwindPlan * unwind_plan,Thread * thread,uint32_t reg_num)165 static void DumpRegisterName(Stream &s, const UnwindPlan *unwind_plan,
166 Thread *thread, uint32_t reg_num) {
167 const RegisterInfo *reg_info = unwind_plan->GetRegisterInfo(thread, reg_num);
168 if (reg_info)
169 s.PutCString(reg_info->name);
170 else
171 s.Printf("reg(%u)", reg_num);
172 }
173
174 bool UnwindPlan::Row::FAValue::
operator ==(const UnwindPlan::Row::FAValue & rhs) const175 operator==(const UnwindPlan::Row::FAValue &rhs) const {
176 if (m_type == rhs.m_type) {
177 switch (m_type) {
178 case unspecified:
179 case isRaSearch:
180 return m_value.ra_search_offset == rhs.m_value.ra_search_offset;
181
182 case isRegisterPlusOffset:
183 return m_value.reg.offset == rhs.m_value.reg.offset;
184
185 case isRegisterDereferenced:
186 return m_value.reg.reg_num == rhs.m_value.reg.reg_num;
187
188 case isDWARFExpression:
189 if (m_value.expr.length == rhs.m_value.expr.length)
190 return !memcmp(m_value.expr.opcodes, rhs.m_value.expr.opcodes,
191 m_value.expr.length);
192 break;
193 case isConstant:
194 return m_value.constant == rhs.m_value.constant;
195 }
196 }
197 return false;
198 }
199
Dump(Stream & s,const UnwindPlan * unwind_plan,Thread * thread) const200 void UnwindPlan::Row::FAValue::Dump(Stream &s, const UnwindPlan *unwind_plan,
201 Thread *thread) const {
202 switch (m_type) {
203 case isRegisterPlusOffset:
204 DumpRegisterName(s, unwind_plan, thread, m_value.reg.reg_num);
205 s.Printf("%+3d", m_value.reg.offset);
206 break;
207 case isRegisterDereferenced:
208 s.PutChar('[');
209 DumpRegisterName(s, unwind_plan, thread, m_value.reg.reg_num);
210 s.PutChar(']');
211 break;
212 case isDWARFExpression:
213 DumpDWARFExpr(s, llvm::ArrayRef(m_value.expr.opcodes, m_value.expr.length),
214 thread);
215 break;
216 case unspecified:
217 s.PutCString("unspecified");
218 break;
219 case isRaSearch:
220 s.Printf("RaSearch@SP%+d", m_value.ra_search_offset);
221 break;
222 case isConstant:
223 s.Printf("0x%" PRIx64, m_value.constant);
224 }
225 }
226
Clear()227 void UnwindPlan::Row::Clear() {
228 m_cfa_value.SetUnspecified();
229 m_afa_value.SetUnspecified();
230 m_offset = 0;
231 m_unspecified_registers_are_undefined = false;
232 m_register_locations.clear();
233 }
234
Dump(Stream & s,const UnwindPlan * unwind_plan,Thread * thread,addr_t base_addr) const235 void UnwindPlan::Row::Dump(Stream &s, const UnwindPlan *unwind_plan,
236 Thread *thread, addr_t base_addr) const {
237 if (base_addr != LLDB_INVALID_ADDRESS)
238 s.Printf("0x%16.16" PRIx64 ": CFA=", base_addr + GetOffset());
239 else
240 s.Printf("%4" PRId64 ": CFA=", GetOffset());
241
242 m_cfa_value.Dump(s, unwind_plan, thread);
243
244 if (!m_afa_value.IsUnspecified()) {
245 s.Printf(" AFA=");
246 m_afa_value.Dump(s, unwind_plan, thread);
247 }
248
249 s.Printf(" => ");
250 for (collection::const_iterator idx = m_register_locations.begin();
251 idx != m_register_locations.end(); ++idx) {
252 DumpRegisterName(s, unwind_plan, thread, idx->first);
253 const bool verbose = false;
254 idx->second.Dump(s, unwind_plan, this, thread, verbose);
255 s.PutChar(' ');
256 }
257 }
258
Row()259 UnwindPlan::Row::Row() : m_cfa_value(), m_afa_value(), m_register_locations() {}
260
GetRegisterInfo(uint32_t reg_num,UnwindPlan::Row::AbstractRegisterLocation & register_location) const261 bool UnwindPlan::Row::GetRegisterInfo(
262 uint32_t reg_num,
263 UnwindPlan::Row::AbstractRegisterLocation ®ister_location) const {
264 collection::const_iterator pos = m_register_locations.find(reg_num);
265 if (pos != m_register_locations.end()) {
266 register_location = pos->second;
267 return true;
268 }
269 if (m_unspecified_registers_are_undefined) {
270 register_location.SetUndefined();
271 return true;
272 }
273 return false;
274 }
275
RemoveRegisterInfo(uint32_t reg_num)276 void UnwindPlan::Row::RemoveRegisterInfo(uint32_t reg_num) {
277 collection::const_iterator pos = m_register_locations.find(reg_num);
278 if (pos != m_register_locations.end()) {
279 m_register_locations.erase(pos);
280 }
281 }
282
SetRegisterInfo(uint32_t reg_num,const UnwindPlan::Row::AbstractRegisterLocation register_location)283 void UnwindPlan::Row::SetRegisterInfo(
284 uint32_t reg_num,
285 const UnwindPlan::Row::AbstractRegisterLocation register_location) {
286 m_register_locations[reg_num] = register_location;
287 }
288
SetRegisterLocationToAtCFAPlusOffset(uint32_t reg_num,int32_t offset,bool can_replace)289 bool UnwindPlan::Row::SetRegisterLocationToAtCFAPlusOffset(uint32_t reg_num,
290 int32_t offset,
291 bool can_replace) {
292 if (!can_replace &&
293 m_register_locations.find(reg_num) != m_register_locations.end())
294 return false;
295 AbstractRegisterLocation reg_loc;
296 reg_loc.SetAtCFAPlusOffset(offset);
297 m_register_locations[reg_num] = reg_loc;
298 return true;
299 }
300
SetRegisterLocationToIsCFAPlusOffset(uint32_t reg_num,int32_t offset,bool can_replace)301 bool UnwindPlan::Row::SetRegisterLocationToIsCFAPlusOffset(uint32_t reg_num,
302 int32_t offset,
303 bool can_replace) {
304 if (!can_replace &&
305 m_register_locations.find(reg_num) != m_register_locations.end())
306 return false;
307 AbstractRegisterLocation reg_loc;
308 reg_loc.SetIsCFAPlusOffset(offset);
309 m_register_locations[reg_num] = reg_loc;
310 return true;
311 }
312
SetRegisterLocationToUndefined(uint32_t reg_num,bool can_replace,bool can_replace_only_if_unspecified)313 bool UnwindPlan::Row::SetRegisterLocationToUndefined(
314 uint32_t reg_num, bool can_replace, bool can_replace_only_if_unspecified) {
315 collection::iterator pos = m_register_locations.find(reg_num);
316 collection::iterator end = m_register_locations.end();
317
318 if (pos != end) {
319 if (!can_replace)
320 return false;
321 if (can_replace_only_if_unspecified && !pos->second.IsUnspecified())
322 return false;
323 }
324 AbstractRegisterLocation reg_loc;
325 reg_loc.SetUndefined();
326 m_register_locations[reg_num] = reg_loc;
327 return true;
328 }
329
SetRegisterLocationToUnspecified(uint32_t reg_num,bool can_replace)330 bool UnwindPlan::Row::SetRegisterLocationToUnspecified(uint32_t reg_num,
331 bool can_replace) {
332 if (!can_replace &&
333 m_register_locations.find(reg_num) != m_register_locations.end())
334 return false;
335 AbstractRegisterLocation reg_loc;
336 reg_loc.SetUnspecified();
337 m_register_locations[reg_num] = reg_loc;
338 return true;
339 }
340
SetRegisterLocationToRegister(uint32_t reg_num,uint32_t other_reg_num,bool can_replace)341 bool UnwindPlan::Row::SetRegisterLocationToRegister(uint32_t reg_num,
342 uint32_t other_reg_num,
343 bool can_replace) {
344 if (!can_replace &&
345 m_register_locations.find(reg_num) != m_register_locations.end())
346 return false;
347 AbstractRegisterLocation reg_loc;
348 reg_loc.SetInRegister(other_reg_num);
349 m_register_locations[reg_num] = reg_loc;
350 return true;
351 }
352
SetRegisterLocationToSame(uint32_t reg_num,bool must_replace)353 bool UnwindPlan::Row::SetRegisterLocationToSame(uint32_t reg_num,
354 bool must_replace) {
355 if (must_replace &&
356 m_register_locations.find(reg_num) == m_register_locations.end())
357 return false;
358 AbstractRegisterLocation reg_loc;
359 reg_loc.SetSame();
360 m_register_locations[reg_num] = reg_loc;
361 return true;
362 }
363
SetRegisterLocationToIsDWARFExpression(uint32_t reg_num,const uint8_t * opcodes,uint32_t len,bool can_replace)364 bool UnwindPlan::Row::SetRegisterLocationToIsDWARFExpression(
365 uint32_t reg_num, const uint8_t *opcodes, uint32_t len, bool can_replace) {
366 if (!can_replace &&
367 m_register_locations.find(reg_num) != m_register_locations.end())
368 return false;
369 AbstractRegisterLocation reg_loc;
370 reg_loc.SetIsDWARFExpression(opcodes, len);
371 m_register_locations[reg_num] = reg_loc;
372 return true;
373 }
374
SetRegisterLocationToIsConstant(uint32_t reg_num,uint64_t constant,bool can_replace)375 bool UnwindPlan::Row::SetRegisterLocationToIsConstant(uint32_t reg_num,
376 uint64_t constant,
377 bool can_replace) {
378 if (!can_replace &&
379 m_register_locations.find(reg_num) != m_register_locations.end())
380 return false;
381 AbstractRegisterLocation reg_loc;
382 reg_loc.SetIsConstant(constant);
383 m_register_locations[reg_num] = reg_loc;
384 return true;
385 }
386
operator ==(const UnwindPlan::Row & rhs) const387 bool UnwindPlan::Row::operator==(const UnwindPlan::Row &rhs) const {
388 return m_offset == rhs.m_offset && m_cfa_value == rhs.m_cfa_value &&
389 m_afa_value == rhs.m_afa_value &&
390 m_unspecified_registers_are_undefined ==
391 rhs.m_unspecified_registers_are_undefined &&
392 m_register_locations == rhs.m_register_locations;
393 }
394
AppendRow(Row row)395 void UnwindPlan::AppendRow(Row row) {
396 if (m_row_list.empty() || m_row_list.back().GetOffset() != row.GetOffset())
397 m_row_list.push_back(std::move(row));
398 else
399 m_row_list.back() = std::move(row);
400 }
401
402 struct RowLess {
operator ()RowLess403 bool operator()(int64_t a, const UnwindPlan::Row &b) const {
404 return a < b.GetOffset();
405 }
operator ()RowLess406 bool operator()(const UnwindPlan::Row &a, int64_t b) const {
407 return a.GetOffset() < b;
408 }
409 };
410
InsertRow(Row row,bool replace_existing)411 void UnwindPlan::InsertRow(Row row, bool replace_existing) {
412 auto it = llvm::lower_bound(m_row_list, row.GetOffset(), RowLess());
413 if (it == m_row_list.end() || it->GetOffset() > row.GetOffset())
414 m_row_list.insert(it, std::move(row));
415 else {
416 assert(it->GetOffset() == row.GetOffset());
417 if (replace_existing)
418 *it = std::move(row);
419 }
420 }
421
422 const UnwindPlan::Row *
GetRowForFunctionOffset(std::optional<int64_t> offset) const423 UnwindPlan::GetRowForFunctionOffset(std::optional<int64_t> offset) const {
424 auto it = offset ? llvm::upper_bound(m_row_list, *offset, RowLess())
425 : m_row_list.end();
426 if (it == m_row_list.begin())
427 return nullptr;
428 // upper_bound returns the row strictly greater than our desired offset, which
429 // means that the row before it is a match.
430 return &*std::prev(it);
431 }
432
IsValidRowIndex(uint32_t idx) const433 bool UnwindPlan::IsValidRowIndex(uint32_t idx) const {
434 return idx < m_row_list.size();
435 }
436
GetRowAtIndex(uint32_t idx) const437 const UnwindPlan::Row *UnwindPlan::GetRowAtIndex(uint32_t idx) const {
438 if (idx < m_row_list.size())
439 return &m_row_list[idx];
440 LLDB_LOG(GetLog(LLDBLog::Unwind),
441 "error: UnwindPlan::GetRowAtIndex(idx = {0}) invalid index "
442 "(number rows is {1})",
443 idx, m_row_list.size());
444 return nullptr;
445 }
446
GetLastRow() const447 const UnwindPlan::Row *UnwindPlan::GetLastRow() const {
448 if (m_row_list.empty()) {
449 LLDB_LOG(GetLog(LLDBLog::Unwind),
450 "UnwindPlan::GetLastRow() when rows are empty");
451 return nullptr;
452 }
453 return &m_row_list.back();
454 }
455
PlanValidAtAddress(Address addr) const456 bool UnwindPlan::PlanValidAtAddress(Address addr) const {
457 // If this UnwindPlan has no rows, it is an invalid UnwindPlan.
458 if (GetRowCount() == 0) {
459 Log *log = GetLog(LLDBLog::Unwind);
460 if (log) {
461 StreamString s;
462 if (addr.Dump(&s, nullptr, Address::DumpStyleSectionNameOffset)) {
463 LLDB_LOGF(log,
464 "UnwindPlan is invalid -- no unwind rows for UnwindPlan "
465 "'%s' at address %s",
466 m_source_name.GetCString(), s.GetData());
467 } else {
468 LLDB_LOGF(log,
469 "UnwindPlan is invalid -- no unwind rows for UnwindPlan '%s'",
470 m_source_name.GetCString());
471 }
472 }
473 return false;
474 }
475
476 // If the 0th Row of unwind instructions is missing, or if it doesn't provide
477 // a register to use to find the Canonical Frame Address, this is not a valid
478 // UnwindPlan.
479 const Row *row0 = GetRowAtIndex(0);
480 if (!row0 ||
481 row0->GetCFAValue().GetValueType() == Row::FAValue::unspecified) {
482 Log *log = GetLog(LLDBLog::Unwind);
483 if (log) {
484 StreamString s;
485 if (addr.Dump(&s, nullptr, Address::DumpStyleSectionNameOffset)) {
486 LLDB_LOGF(log,
487 "UnwindPlan is invalid -- no CFA register defined in row 0 "
488 "for UnwindPlan '%s' at address %s",
489 m_source_name.GetCString(), s.GetData());
490 } else {
491 LLDB_LOGF(log,
492 "UnwindPlan is invalid -- no CFA register defined in row 0 "
493 "for UnwindPlan '%s'",
494 m_source_name.GetCString());
495 }
496 }
497 return false;
498 }
499
500 if (m_plan_valid_ranges.empty())
501 return true;
502
503 if (!addr.IsValid())
504 return true;
505
506 return llvm::any_of(m_plan_valid_ranges, [&](const AddressRange &range) {
507 return range.ContainsFileAddress(addr);
508 });
509 }
510
Dump(Stream & s,Thread * thread,lldb::addr_t base_addr) const511 void UnwindPlan::Dump(Stream &s, Thread *thread, lldb::addr_t base_addr) const {
512 if (!m_source_name.IsEmpty()) {
513 s.Printf("This UnwindPlan originally sourced from %s\n",
514 m_source_name.GetCString());
515 }
516 s.Printf("This UnwindPlan is sourced from the compiler: ");
517 switch (m_plan_is_sourced_from_compiler) {
518 case eLazyBoolYes:
519 s.Printf("yes.\n");
520 break;
521 case eLazyBoolNo:
522 s.Printf("no.\n");
523 break;
524 case eLazyBoolCalculate:
525 s.Printf("not specified.\n");
526 break;
527 }
528 s.Printf("This UnwindPlan is valid at all instruction locations: ");
529 switch (m_plan_is_valid_at_all_instruction_locations) {
530 case eLazyBoolYes:
531 s.Printf("yes.\n");
532 break;
533 case eLazyBoolNo:
534 s.Printf("no.\n");
535 break;
536 case eLazyBoolCalculate:
537 s.Printf("not specified.\n");
538 break;
539 }
540 s.Printf("This UnwindPlan is for a trap handler function: ");
541 switch (m_plan_is_for_signal_trap) {
542 case eLazyBoolYes:
543 s.Printf("yes.\n");
544 break;
545 case eLazyBoolNo:
546 s.Printf("no.\n");
547 break;
548 case eLazyBoolCalculate:
549 s.Printf("not specified.\n");
550 break;
551 }
552 if (!m_plan_valid_ranges.empty()) {
553 s.PutCString("Address range of this UnwindPlan: ");
554 TargetSP target_sp(thread->CalculateTarget());
555 for (const AddressRange &range : m_plan_valid_ranges)
556 range.Dump(&s, target_sp.get(), Address::DumpStyleSectionNameOffset);
557 s.EOL();
558 }
559 for (const auto &[index, row] : llvm::enumerate(m_row_list)) {
560 s.Format("row[{0}]: ", index);
561 row.Dump(s, this, thread, base_addr);
562 s << "\n";
563 }
564 }
565
SetSourceName(const char * source)566 void UnwindPlan::SetSourceName(const char *source) {
567 m_source_name = ConstString(source);
568 }
569
GetSourceName() const570 ConstString UnwindPlan::GetSourceName() const { return m_source_name; }
571
GetRegisterInfo(Thread * thread,uint32_t unwind_reg) const572 const RegisterInfo *UnwindPlan::GetRegisterInfo(Thread *thread,
573 uint32_t unwind_reg) const {
574 if (thread) {
575 RegisterContext *reg_ctx = thread->GetRegisterContext().get();
576 if (reg_ctx) {
577 uint32_t reg;
578 if (m_register_kind == eRegisterKindLLDB)
579 reg = unwind_reg;
580 else
581 reg = reg_ctx->ConvertRegisterKindToRegisterNumber(m_register_kind,
582 unwind_reg);
583 if (reg != LLDB_INVALID_REGNUM)
584 return reg_ctx->GetRegisterInfoAtIndex(reg);
585 }
586 }
587 return nullptr;
588 }
589