// Get the size of an instruction in bytes. // Return 0 if the instruction is not handled. static uint32_t GetInstructionSize(const uint8_t* pc, size_t bytes) { #define FETCH_OR_SKIP_BYTE(assignment) \ do { \ if (bytes == 0u) { \ return0u; \
} \
(assignment); \
++pc; \
--bytes; \
} while (0) #define FETCH_BYTE(var) FETCH_OR_SKIP_BYTE((var) = *pc) #define SKIP_BYTE() FETCH_OR_SKIP_BYTE((void)0)
// For null checks in compiled code we insert a stack map that is immediately // after the load/store instruction that might cause the fault and we need to // pass the return PC to the handler. For null checks in Nterp, we similarly // need the return PC to recognize that this was a null check in Nterp, so // that the handler can get the needed data from the Nterp frame.
// Note: Allowing nested faults if `IsValidMethod()` returned a false positive. // Note: The `ArtMethod::GetOatQuickMethodHeader()` can acquire locks, which is // essentially unsafe in a signal handler, but we allow that here just like in // `NullPointerHandler::IsValidReturnPc()`. For more details see comments there.
uintptr_t pc = uc->CTX_EIP; const OatQuickMethodHeader* method_header = method->GetOatQuickMethodHeader(pc); if (method_header == nullptr) {
VLOG(signals) << "No method header."; returnfalse;
} const uint8_t* pc_ptr = reinterpret_cast<const uint8_t*>(pc);
size_t offset = pc_ptr - method_header->GetCode();
size_t code_size = method_header->GetCodeSize();
CHECK_LT(offset, code_size);
size_t max_instr_size = code_size - offset;
uint32_t instr_size = GetInstructionSize(pc_ptr, max_instr_size); if (instr_size == 0u) { // Unknown instruction (can't really happen) or not enough bytes until end of method code. returnfalse;
}
// Push the return PC and fault address onto the stack.
uintptr_t* next_sp = reinterpret_cast<uintptr_t*>(sp) - 2;
next_sp[1] = return_pc;
next_sp[0] = fault_address;
uc->CTX_ESP = reinterpret_cast<uintptr_t>(next_sp);
// Arrange for the signal handler to return to the NPE entrypoint.
uc->CTX_EIP = reinterpret_cast<uintptr_t>(
art_quick_throw_null_pointer_exception_from_signal);
VLOG(signals) << "Generating null pointer exception"; returntrue;
}
// A suspend check is done using the following instruction sequence: // (x86) // 0xf720f1df: 648B058C000000 mov eax, fs:[0x8c] ; suspend_trigger // .. some intervening instructions. // 0xf720f1e6: 8500 test eax, [eax] // (x86_64) // 0x7f579de45d9e: 65488B0425A8000000 movq rax, gs:[0xa8] ; suspend_trigger // .. some intervening instructions. // 0x7f579de45da7: 8500 test eax, [eax]
// The offset from fs is Thread::ThreadSuspendTriggerOffset(). // To check for a suspend check, we examine the instructions that caused // the fault. bool SuspensionHandler::Action(int, siginfo_t*, void* context) { // These are the instructions to check for. The first one is the mov eax, fs:[xxx] // where xxx is the offset of the suspend trigger.
uint32_t trigger = Thread::ThreadSuspendTriggerOffset<kRuntimePointerSize>().Int32Value();
if (pc[0] != checkinst2[0] || pc[1] != checkinst2[1]) { // Second instruction is not correct (test eax,[eax]).
VLOG(signals) << "Not a suspension point"; returnfalse;
}
// The first instruction can be a little bit up the stream due to load hoisting // in the compiler.
uint8_t* limit = pc - 100; // Compiler will hoist to a max of 20 instructions.
uint8_t* ptr = pc - sizeof(checkinst1); bool found = false; while (ptr > limit) { if (memcmp(ptr, checkinst1, sizeof(checkinst1)) == 0) {
found = true; break;
}
ptr -= 1;
}
if (found) {
VLOG(signals) << "suspend check match";
// We need to arrange for the signal handler to return to the null pointer // exception generator. The return address must be the address of the // next instruction (this instruction + 2). The return address // is on the stack at the top address of the current frame.
// Now remove the suspend trigger that caused this fault.
Thread::Current()->RemoveSuspendTrigger();
VLOG(signals) << "removed suspend trigger invoking test suspend"; returntrue;
}
VLOG(signals) << "Not a suspend check match, first instruction mismatch"; returnfalse;
}
// The stack overflow check is done using the following instruction: // test eax, [esp+ -xxx] // where 'xxx' is the size of the overflow area. // // This is done before any frame is established in the method. The return // address for the previous method is on the stack at ESP.
// Check that the fault address is the value expected for a stack overflow. if (fault_addr != overflow_addr) {
VLOG(signals) << "Not a stack overflow"; returnfalse;
}
VLOG(signals) << "Stack overflow found";
// Since the compiler puts the implicit overflow // check before the callee save instructions, the SP is already pointing to // the previous frame.
// Now arrange for the signal handler to return to art_quick_throw_stack_overflow.
uc->CTX_EIP = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow);
returntrue;
}
} // namespace art
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.