aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp
blob: 721c4bcfe6342706856e2e123a72fbb82bb7f9ad (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
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
//===-- ArchitectureArm.cpp -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Plugins/Architecture/Arm/ArchitectureArm.h"
#include "Plugins/Process/Utility/ARMDefines.h"
#include "Plugins/Process/Utility/InstructionUtils.h"
#include "Utility/ARM_DWARF_Registers.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/RegisterNumber.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/UnwindLLDB.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RegisterValue.h"

using namespace lldb_private;
using namespace lldb;

LLDB_PLUGIN_DEFINE(ArchitectureArm)

void ArchitectureArm::Initialize() {
  PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                "Arm-specific algorithms",
                                &ArchitectureArm::Create);
}

void ArchitectureArm::Terminate() {
  PluginManager::UnregisterPlugin(&ArchitectureArm::Create);
}

std::unique_ptr<Architecture> ArchitectureArm::Create(const ArchSpec &arch) {
  if (arch.GetMachine() != llvm::Triple::arm)
    return nullptr;
  return std::unique_ptr<Architecture>(new ArchitectureArm());
}

void ArchitectureArm::OverrideStopInfo(Thread &thread) const {
  // We need to check if we are stopped in Thumb mode in a IT instruction and
  // detect if the condition doesn't pass. If this is the case it means we
  // won't actually execute this instruction. If this happens we need to clear
  // the stop reason to no thread plans think we are stopped for a reason and
  // the plans should keep going.
  //
  // We do this because when single stepping many ARM processes, debuggers
  // often use the BVR/BCR registers that says "stop when the PC is not equal
  // to its current value". This method of stepping means we can end up
  // stopping on instructions inside an if/then block that wouldn't get
  // executed. By fixing this we can stop the debugger from seeming like you
  // stepped through both the "if" _and_ the "else" clause when source level
  // stepping because the debugger stops regardless due to the BVR/BCR
  // triggering a stop.
  //
  // It also means we can set breakpoints on instructions inside an if/then
  // block and correctly skip them if we use the BKPT instruction. The ARM and
  // Thumb BKPT instructions are unconditional even when executed in a Thumb IT
  // block.
  //
  // If your debugger inserts software traps in ARM/Thumb code, it will need to
  // use 16 and 32 bit instruction for 16 and 32 bit thumb instructions
  // respectively. If your debugger inserts a 16 bit thumb trap on top of a 32
  // bit thumb instruction for an opcode that is inside an if/then, it will
  // change the it/then to conditionally execute your
  // 16 bit trap and then cause your program to crash if it executes the
  // trailing 16 bits (the second half of the 32 bit thumb instruction you
  // partially overwrote).

  RegisterContextSP reg_ctx_sp(thread.GetRegisterContext());
  if (!reg_ctx_sp)
    return;

  const uint32_t cpsr = reg_ctx_sp->GetFlags(0);
  if (cpsr == 0)
    return;

  // Read the J and T bits to get the ISETSTATE
  const uint32_t J = Bit32(cpsr, 24);
  const uint32_t T = Bit32(cpsr, 5);
  const uint32_t ISETSTATE = J << 1 | T;
  if (ISETSTATE == 0) {
// NOTE: I am pretty sure we want to enable the code below
// that detects when we stop on an instruction in ARM mode that is conditional
// and the condition doesn't pass. This can happen if you set a breakpoint on
// an instruction that is conditional. We currently will _always_ stop on the
// instruction which is bad. You can also run into this while single stepping
// and you could appear to run code in the "if" and in the "else" clause
// because it would stop at all of the conditional instructions in both. In
// such cases, we really don't want to stop at this location.
// I will check with the lldb-dev list first before I enable this.
#if 0
    // ARM mode: check for condition on instruction
    const addr_t pc = reg_ctx_sp->GetPC();
    Status error;
    // If we fail to read the opcode we will get UINT64_MAX as the result in
    // "opcode" which we can use to detect if we read a valid opcode.
    const uint64_t opcode = thread.GetProcess()->ReadUnsignedIntegerFromMemory(pc, 4, UINT64_MAX, error);
    if (opcode <= UINT32_MAX)
    {
        const uint32_t condition = Bits32((uint32_t)opcode, 31, 28);
        if (!ARMConditionPassed(condition, cpsr))
        {
            // We ARE stopped on an ARM instruction whose condition doesn't
            // pass so this instruction won't get executed. Regardless of why
            // it stopped, we need to clear the stop info
            thread.SetStopInfo (StopInfoSP());
        }
    }
#endif
  } else if (ISETSTATE == 1) {
    // Thumb mode
    const uint32_t ITSTATE = Bits32(cpsr, 15, 10) << 2 | Bits32(cpsr, 26, 25);
    if (ITSTATE != 0) {
      const uint32_t condition = Bits32(ITSTATE, 7, 4);
      if (!ARMConditionPassed(condition, cpsr)) {
        // We ARE stopped in a Thumb IT instruction on an instruction whose
        // condition doesn't pass so this instruction won't get executed.
        // Regardless of why it stopped, we need to clear the stop info
        thread.SetStopInfo(StopInfoSP());
      }
    }
  }
}

addr_t ArchitectureArm::GetCallableLoadAddress(addr_t code_addr,
                                               AddressClass addr_class) const {
  bool is_alternate_isa = false;

  switch (addr_class) {
  case AddressClass::eData:
  case AddressClass::eDebug:
    return LLDB_INVALID_ADDRESS;
  case AddressClass::eCodeAlternateISA:
    is_alternate_isa = true;
    break;
  default: break;
  }

  if ((code_addr & 2u) || is_alternate_isa)
    return code_addr | 1u;
  return code_addr;
}

addr_t ArchitectureArm::GetOpcodeLoadAddress(addr_t opcode_addr,
                                             AddressClass addr_class) const {
  switch (addr_class) {
  case AddressClass::eData:
  case AddressClass::eDebug:
    return LLDB_INVALID_ADDRESS;
  default: break;
  }
  return opcode_addr & ~(1ull);
}

// The ARM M-Profile Armv7-M Architecture Reference Manual,
// subsection "B1.5 Armv7-M exception model", see the parts
// describing "Exception entry behavior" and "Exception
// return behavior".
// When an exception happens on this processor, certain registers are
// saved below the stack pointer, the stack pointer is decremented,
// a special value is put in the link register to indicate the
// exception has been taken, and an exception handler function
// is invoked.
//
// Detect that special value in $lr, and if present, add
// unwind rules for the registers that were saved above this
// stack frame's CFA.  Overwrite any register locations that
// the current_unwindplan has for these registers; they are
// not correct when we're invoked this way.
UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan(
    Thread &thread, RegisterContextUnwind *regctx,
    std::shared_ptr<const UnwindPlan> current_unwindplan) {

  ProcessSP process_sp = thread.GetProcess();
  if (!process_sp)
    return {};

  const ArchSpec arch = process_sp->GetTarget().GetArchitecture();
  if (!arch.GetTriple().isArmMClass() || arch.GetAddressByteSize() != 4)
    return {};

  // Get the caller's LR value from regctx (the LR value
  // at function entry to this function).
  RegisterNumber ra_regnum(thread, eRegisterKindGeneric,
                           LLDB_REGNUM_GENERIC_RA);
  uint32_t ra_regnum_lldb = ra_regnum.GetAsKind(eRegisterKindLLDB);

  if (ra_regnum_lldb == LLDB_INVALID_REGNUM)
    return {};

  UnwindLLDB::ConcreteRegisterLocation regloc = {};
  bool got_concrete_location = false;
  if (regctx->SavedLocationForRegister(ra_regnum_lldb, regloc) ==
      UnwindLLDB::RegisterSearchResult::eRegisterFound) {
    got_concrete_location = true;
  } else {
    RegisterNumber pc_regnum(thread, eRegisterKindGeneric,
                             LLDB_REGNUM_GENERIC_PC);
    uint32_t pc_regnum_lldb = pc_regnum.GetAsKind(eRegisterKindLLDB);
    if (regctx->SavedLocationForRegister(pc_regnum_lldb, regloc) ==
        UnwindLLDB::RegisterSearchResult::eRegisterFound)
      got_concrete_location = true;
  }

  if (!got_concrete_location)
    return {};

  addr_t callers_return_address = LLDB_INVALID_ADDRESS;
  const RegisterInfo *reg_info = regctx->GetRegisterInfoAtIndex(ra_regnum_lldb);
  if (reg_info) {
    RegisterValue reg_value;
    if (regctx->ReadRegisterValueFromRegisterLocation(regloc, reg_info,
                                                      reg_value)) {
      callers_return_address = reg_value.GetAsUInt32();
    }
  }

  if (callers_return_address == LLDB_INVALID_ADDRESS)
    return {};

  // ARMv7-M ARM says that the LR will be set to
  // one of these values when an exception has taken
  // place:
  //    if HaveFPExt() then
  //      if CurrentMode==Mode_Handler then
  //        LR = Ones(27):NOT(CONTROL.FPCA):'0001';
  //      else
  //        LR = Ones(27):NOT(CONTROL.FPCA):'1':CONTROL.SPSEL:'01';
  //    else
  //      if CurrentMode==Mode_Handler then
  //        LR = Ones(28):'0001';
  //      else
  //        LR = Ones(29):CONTROL.SPSEL:'01';

  // Top 27 bits are set for an exception return.
  const uint32_t exception_return = -1U & ~0b11111U;
  // Bit4 is 1 if only GPRs were saved.
  const uint32_t gprs_only = 0b10000;
  // Bit<1:0> are '01'.
  const uint32_t lowbits = 0b01;

  if ((callers_return_address & exception_return) != exception_return)
    return {};
  if ((callers_return_address & lowbits) != lowbits)
    return {};

  const bool fp_regs_saved = !(callers_return_address & gprs_only);

  const RegisterKind plan_regkind = current_unwindplan->GetRegisterKind();
  UnwindPlanSP new_plan = std::make_shared<UnwindPlan>(plan_regkind);
  new_plan->SetSourceName("Arm Cortex-M exception return UnwindPlan");
  new_plan->SetSourcedFromCompiler(eLazyBoolNo);
  new_plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
  new_plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);

  int stored_regs_size = fp_regs_saved ? 0x68 : 0x20;

  uint32_t gpr_regs[] = {dwarf_r0,  dwarf_r1, dwarf_r2, dwarf_r3,
                         dwarf_r12, dwarf_lr, dwarf_pc, dwarf_cpsr};
  const int gpr_reg_count = std::size(gpr_regs);
  uint32_t fpr_regs[] = {dwarf_s0,  dwarf_s1,  dwarf_s2,  dwarf_s3,
                         dwarf_s4,  dwarf_s5,  dwarf_s6,  dwarf_s7,
                         dwarf_s8,  dwarf_s9,  dwarf_s10, dwarf_s11,
                         dwarf_s12, dwarf_s13, dwarf_s14, dwarf_s15};
  const int fpr_reg_count = std::size(fpr_regs);

  RegisterContextSP reg_ctx_sp = thread.GetRegisterContext();
  std::vector<uint32_t> saved_regs;
  for (int i = 0; i < gpr_reg_count; i++) {
    uint32_t regno = gpr_regs[i];
    reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, gpr_regs[i],
                                            plan_regkind, regno);
    saved_regs.push_back(regno);
  }
  if (fp_regs_saved) {
    for (int i = 0; i < fpr_reg_count; i++) {
      uint32_t regno = fpr_regs[i];
      reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, fpr_regs[i],
                                              plan_regkind, regno);
      saved_regs.push_back(regno);
    }
  }

  addr_t cfa;
  if (!regctx->GetCFA(cfa))
    return {};

  // The CPSR value saved to stack is actually (from Armv7-M ARM)
  //   "XPSR<31:10>:frameptralign:XPSR<8:0>"
  // Bit 9 indicates that the stack pointer was aligned (to
  // an 8-byte alignment) when the exception happened, and we must
  // account for that when restoring the original stack pointer value.
  Status error;
  uint32_t callers_xPSR =
      process_sp->ReadUnsignedIntegerFromMemory(cfa + 0x1c, 4, 0, error);
  const bool align_stack = callers_xPSR & (1U << 9);
  uint32_t callers_sp = cfa + stored_regs_size;
  if (align_stack)
    callers_sp |= 4;

  Log *log = GetLog(LLDBLog::Unwind);
  LLDB_LOGF(log,
            "ArchitectureArm::GetArchitectureUnwindPlan found caller return "
            "addr of 0x%" PRIx64 ", for frame with CFA 0x%" PRIx64
            ", fp_regs_saved %d, stored_regs_size 0x%x, align stack %d",
            callers_return_address, cfa, fp_regs_saved, stored_regs_size,
            align_stack);

  uint32_t sp_regnum = dwarf_sp;
  reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, dwarf_sp,
                                          plan_regkind, sp_regnum);

  const int row_count = current_unwindplan->GetRowCount();
  for (int i = 0; i < row_count; i++) {
    UnwindPlan::Row row = *current_unwindplan->GetRowAtIndex(i);
    uint32_t offset = 0;
    const size_t saved_reg_count = saved_regs.size();
    for (size_t j = 0; j < saved_reg_count; j++) {
      // The locations could be set with
      // SetRegisterLocationToIsConstant(regno, cfa+offset)
      // expressing it in terms of CFA addr+offset - this UnwindPlan
      // is only used once, with this specific CFA.  I'm not sure
      // which will be clearer for someone reading the unwind log.
      row.SetRegisterLocationToAtCFAPlusOffset(saved_regs[j], offset, true);
      offset += 4;
    }
    row.SetRegisterLocationToIsCFAPlusOffset(sp_regnum, callers_sp - cfa, true);
    new_plan->AppendRow(row);
  }
  return new_plan;
}