summaryrefslogtreecommitdiffstats
path: root/runtime/arch/arm/fault_handler_arm.cc
blob: e22c56ec693aff298d194de5ccd11181f36d0fd8 (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
/*
 * Copyright (C) 2008 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 "fault_handler.h"
#include <sys/ucontext.h>
#include "base/macros.h"
#include "base/hex_dump.h"
#include "globals.h"
#include "base/logging.h"
#include "base/hex_dump.h"
#include "instruction_set.h"
#include "mirror/art_method.h"
#include "mirror/art_method-inl.h"
#include "thread.h"
#include "thread-inl.h"

//
// ARM specific fault handler functions.
//

namespace art {

extern "C" void art_quick_throw_null_pointer_exception();
extern "C" void art_quick_throw_stack_overflow_from_signal();
extern "C" void art_quick_implicit_suspend();

// Get the size of a thumb2 instruction in bytes.
static uint32_t GetInstructionSize(uint8_t* pc) {
  uint16_t instr = pc[0] | pc[1] << 8;
  bool is_32bit = ((instr & 0xF000) == 0xF000) || ((instr & 0xF800) == 0xE800);
  uint32_t instr_size = is_32bit ? 4 : 2;
  return instr_size;
}

void FaultManager::GetMethodAndReturnPCAndSP(siginfo_t* siginfo, void* context,
                                             mirror::ArtMethod** out_method,
                                             uintptr_t* out_return_pc, uintptr_t* out_sp) {
  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
  *out_sp = static_cast<uintptr_t>(sc->arm_sp);
  VLOG(signals) << "sp: " << *out_sp;
  if (*out_sp == 0) {
    return;
  }

  // In the case of a stack overflow, the stack is not valid and we can't
  // get the method from the top of the stack.  However it's in r0.
  uintptr_t* fault_addr = reinterpret_cast<uintptr_t*>(sc->fault_address);
  uintptr_t* overflow_addr = reinterpret_cast<uintptr_t*>(
      reinterpret_cast<uint8_t*>(*out_sp) - kArmStackOverflowReservedBytes);
  if (overflow_addr == fault_addr) {
    *out_method = reinterpret_cast<mirror::ArtMethod*>(sc->arm_r0);
  } else {
    // The method is at the top of the stack.
    *out_method = reinterpret_cast<mirror::ArtMethod*>(reinterpret_cast<uintptr_t*>(*out_sp)[0]);
  }

  // Work out the return PC.  This will be the address of the instruction
  // following the faulting ldr/str instruction.  This is in thumb mode so
  // the instruction might be a 16 or 32 bit one.  Also, the GC map always
  // has the bottom bit of the PC set so we also need to set that.

  // Need to work out the size of the instruction that caused the exception.
  uint8_t* ptr = reinterpret_cast<uint8_t*>(sc->arm_pc);
  VLOG(signals) << "pc: " << std::hex << static_cast<void*>(ptr);
  uint32_t instr_size = GetInstructionSize(ptr);

  *out_return_pc = (sc->arm_pc + instr_size) | 1;
}

bool NullPointerHandler::Action(int sig, siginfo_t* info, void* context) {
  // The code that looks for the catch location needs to know the value of the
  // ARM PC at the point of call.  For Null checks we insert a GC map that is immediately after
  // the load/store instruction that might cause the fault.  However the mapping table has
  // the low bits set for thumb mode so we need to set the bottom bit for the LR
  // register in order to find the mapping.

  // Need to work out the size of the instruction that caused the exception.
  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
  uint8_t* ptr = reinterpret_cast<uint8_t*>(sc->arm_pc);

  uint32_t instr_size = GetInstructionSize(ptr);
  sc->arm_lr = (sc->arm_pc + instr_size) | 1;      // LR needs to point to gc map location
  sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception);
  VLOG(signals) << "Generating null pointer exception";
  return true;
}

// A suspend check is done using the following instruction sequence:
// 0xf723c0b2: f8d902c0  ldr.w   r0, [r9, #704]  ; suspend_trigger_
// .. some intervening instruction
// 0xf723c0b6: 6800      ldr     r0, [r0, #0]

// The offset from r9 is Thread::ThreadSuspendTriggerOffset().
// To check for a suspend check, we examine the instructions that caused
// the fault (at PC-4 and PC).
bool SuspensionHandler::Action(int sig, siginfo_t* info, void* context) {
  // These are the instructions to check for.  The first one is the ldr r0,[r9,#xxx]
  // where xxx is the offset of the suspend trigger.
  uint32_t checkinst1 = 0xf8d90000 + Thread::ThreadSuspendTriggerOffset<4>().Int32Value();
  uint16_t checkinst2 = 0x6800;

  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
  uint8_t* ptr2 = reinterpret_cast<uint8_t*>(sc->arm_pc);
  uint8_t* ptr1 = ptr2 - 4;
  VLOG(signals) << "checking suspend";

  uint16_t inst2 = ptr2[0] | ptr2[1] << 8;
  VLOG(signals) << "inst2: " << std::hex << inst2 << " checkinst2: " << checkinst2;
  if (inst2 != checkinst2) {
    // Second instruction is not good, not ours.
    return false;
  }

  // The first instruction can a little bit up the stream due to load hoisting
  // in the compiler.
  uint8_t* limit = ptr1 - 40;   // Compiler will hoist to a max of 20 instructions.
  bool found = false;
  while (ptr1 > limit) {
    uint32_t inst1 = ((ptr1[0] | ptr1[1] << 8) << 16) | (ptr1[2] | ptr1[3] << 8);
    VLOG(signals) << "inst1: " << std::hex << inst1 << " checkinst1: " << checkinst1;
    if (inst1 == checkinst1) {
      found = true;
      break;
    }
    ptr1 -= 2;      // Min instruction size is 2 bytes.
  }
  if (found) {
    VLOG(signals) << "suspend check match";
    // This is a suspend check.  Arrange for the signal handler to return to
    // art_quick_implicit_suspend.  Also set LR so that after the suspend check it
    // will resume the instruction (current PC + 2).  PC points to the
    // ldr r0,[r0,#0] instruction (r0 will be 0, set by the trigger).

    // NB: remember that we need to set the bottom bit of the LR register
    // to switch to thumb mode.
    VLOG(signals) << "arm lr: " << std::hex << sc->arm_lr;
    VLOG(signals) << "arm pc: " << std::hex << sc->arm_pc;
    sc->arm_lr = sc->arm_pc + 3;      // +2 + 1 (for thumb)
    sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_implicit_suspend);

    // Now remove the suspend trigger that caused this fault.
    Thread::Current()->RemoveSuspendTrigger();
    VLOG(signals) << "removed suspend trigger invoking test suspend";
    return true;
  }
  return false;
}

// Stack overflow fault handler.
//
// This checks that the fault address is equal to the current stack pointer
// minus the overflow region size (16K typically).  The instruction sequence
// that generates this signal is:
//
// sub r12,sp,#16384
// ldr.w r12,[r12,#0]
//
// The second instruction will fault if r12 is inside the protected region
// on the stack.
//
// If we determine this is a stack overflow we need to move the stack pointer
// to the overflow region below the protected region.

bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) {
  struct ucontext* uc = reinterpret_cast<struct ucontext*>(context);
  struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
  VLOG(signals) << "stack overflow handler with sp at " << std::hex << &uc;
  VLOG(signals) << "sigcontext: " << std::hex << sc;

  uintptr_t sp = sc->arm_sp;
  VLOG(signals) << "sp: " << std::hex << sp;

  uintptr_t fault_addr = sc->fault_address;
  VLOG(signals) << "fault_addr: " << std::hex << fault_addr;
  VLOG(signals) << "checking for stack overflow, sp: " << std::hex << sp <<
    ", fault_addr: " << fault_addr;

  uintptr_t overflow_addr = sp - kArmStackOverflowReservedBytes;

  Thread* self = reinterpret_cast<Thread*>(sc->arm_r9);
  CHECK_EQ(self, Thread::Current());
  uintptr_t pregion = reinterpret_cast<uintptr_t>(self->GetStackEnd()) -
      Thread::kStackOverflowProtectedSize;

  // Check that the fault address is the value expected for a stack overflow.
  if (fault_addr != overflow_addr) {
    VLOG(signals) << "Not a stack overflow";
    return false;
  }

  // We know this is a stack overflow.  We need to move the sp to the overflow region
  // that exists below the protected region.  Determine the address of the next
  // available valid address below the protected region.
  uintptr_t prevsp = sp;
  sp = pregion;
  VLOG(signals) << "setting sp to overflow region at " << std::hex << sp;

  // Since the compiler puts the implicit overflow
  // check before the callee save instructions, the SP is already pointing to
  // the previous frame.
  VLOG(signals) << "previous frame: " << std::hex << prevsp;

  // Now establish the stack pointer for the signal return.
  sc->arm_sp = prevsp;

  // Tell the stack overflow code where the new stack pointer should be.
  sc->arm_ip = sp;      // aka r12

  // Now arrange for the signal handler to return to art_quick_throw_stack_overflow_from_signal.
  // The value of LR must be the same as it was when we entered the code that
  // caused this fault.  This will be inserted into a callee save frame by
  // the function to which this handler returns (art_quick_throw_stack_overflow_from_signal).
  sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow_from_signal);

  // The kernel will now return to the address in sc->arm_pc.
  return true;
}
}       // namespace art