Reland "[arm64] Protect return addresses stored on stack"
This is a reland of 137bfe47c9
Original change's description:
> [arm64] Protect return addresses stored on stack
>
> This change uses the Arm v8.3 pointer authentication instructions in
> order to protect return addresses stored on the stack. The generated
> code signs the return address before storing on the stack and
> authenticates it after loading it. This also changes the stack frame
> iterator in order to authenticate stored return addresses and re-sign
> them when needed, as well as the deoptimizer in order to sign saved
> return addresses when creating new frames. This offers a level of
> protection against ROP attacks.
>
> This functionality is enabled with the v8_control_flow_integrity flag
> that this CL introduces.
>
> The code size effect of this change is small for Octane (up to 2% in
> some cases but mostly much lower) and negligible for larger benchmarks,
> however code size measurements are rather noisy. The performance impact
> on current cores (where the instructions are NOPs) is single digit,
> around 1-2% for ARES-6 and Octane, and tends to be smaller for big
> cores than for little cores.
>
> Bug: v8:10026
> Change-Id: I0081f3938c56e2f24d8227e4640032749f4f8368
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1373782
> Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#66239}
Bug: v8:10026
Change-Id: Id1adfa2e6c713f6977d69aa467986e48fe67b3c2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2051958
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Georgia Kouveli <georgia.kouveli@arm.com>
Cr-Commit-Position: refs/heads/master@{#66254}
This commit is contained in:
parent
a046c5fcf9
commit
73f88b5f69
24
BUILD.gn
24
BUILD.gn
@ -214,6 +214,10 @@ declare_args() {
|
||||
|
||||
# Disable all snapshot compression.
|
||||
v8_enable_snapshot_compression = true
|
||||
|
||||
# Enable control-flow integrity features, such as pointer authentication for
|
||||
# ARM64.
|
||||
v8_control_flow_integrity = false
|
||||
}
|
||||
|
||||
# Derived defaults.
|
||||
@ -273,6 +277,9 @@ assert(!v8_disable_write_barriers || v8_enable_single_generation,
|
||||
assert(v8_current_cpu != "x86" || !v8_untrusted_code_mitigations,
|
||||
"Untrusted code mitigations are unsupported on ia32")
|
||||
|
||||
assert(v8_current_cpu == "arm64" || !v8_control_flow_integrity,
|
||||
"Control-flow integrity is only supported on arm64")
|
||||
|
||||
assert(
|
||||
!v8_enable_pointer_compression || !v8_enable_shared_ro_heap,
|
||||
"Pointer compression is not supported with shared read-only heap enabled")
|
||||
@ -507,6 +514,9 @@ config("features") {
|
||||
if (v8_enable_snapshot_compression) {
|
||||
defines += [ "V8_SNAPSHOT_COMPRESSION" ]
|
||||
}
|
||||
if (v8_control_flow_integrity) {
|
||||
defines += [ "V8_ENABLE_CONTROL_FLOW_INTEGRITY" ]
|
||||
}
|
||||
}
|
||||
|
||||
config("toolchain") {
|
||||
@ -549,6 +559,12 @@ config("toolchain") {
|
||||
}
|
||||
if (v8_current_cpu == "arm64") {
|
||||
defines += [ "V8_TARGET_ARCH_ARM64" ]
|
||||
if (v8_control_flow_integrity) {
|
||||
# TODO(v8:10026): Enable this in src/build.
|
||||
if (current_cpu == "arm64") {
|
||||
cflags += [ "-mbranch-protection=pac-ret" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Mips64el/mipsel simulators.
|
||||
@ -2243,6 +2259,7 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/execution/microtask-queue.h",
|
||||
"src/execution/off-thread-isolate.cc",
|
||||
"src/execution/off-thread-isolate.h",
|
||||
"src/execution/pointer-authentication.h",
|
||||
"src/execution/protectors-inl.h",
|
||||
"src/execution/protectors.cc",
|
||||
"src/execution/protectors.h",
|
||||
@ -3024,6 +3041,10 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/zone/zone.h",
|
||||
]
|
||||
|
||||
if (!v8_control_flow_integrity) {
|
||||
sources += [ "src/execution/pointer-authentication-dummy.h" ]
|
||||
}
|
||||
|
||||
if (v8_enable_third_party_heap) {
|
||||
sources += v8_third_party_heap_files
|
||||
}
|
||||
@ -3181,6 +3202,9 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/regexp/arm64/regexp-macro-assembler-arm64.h",
|
||||
"src/wasm/baseline/arm64/liftoff-assembler-arm64.h",
|
||||
]
|
||||
if (v8_control_flow_integrity) {
|
||||
sources += [ "src/execution/arm64/pointer-authentication-arm64.h" ]
|
||||
}
|
||||
if (is_win) {
|
||||
sources += [
|
||||
"src/diagnostics/unwinding-info-win64.cc",
|
||||
|
@ -1187,7 +1187,7 @@ void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
|
||||
// the frame (that is done below).
|
||||
__ Bind(&push_stack_frame);
|
||||
FrameScope frame_scope(masm, StackFrame::MANUAL);
|
||||
__ Push(lr, fp, cp, closure);
|
||||
__ Push<TurboAssembler::kSignLR>(lr, fp, cp, closure);
|
||||
__ Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
|
||||
|
||||
// Reset code age.
|
||||
@ -1672,7 +1672,7 @@ void Generate_ContinueToBuiltinHelper(MacroAssembler* masm,
|
||||
|
||||
// Restore fp, lr.
|
||||
__ Mov(sp, fp);
|
||||
__ Pop(fp, lr);
|
||||
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
|
||||
__ LoadEntryFromBuiltinIndex(builtin);
|
||||
__ Jump(builtin);
|
||||
@ -2053,7 +2053,7 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
namespace {
|
||||
|
||||
void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
|
||||
__ Push(lr, fp);
|
||||
__ Push<TurboAssembler::kSignLR>(lr, fp);
|
||||
__ Mov(x11, StackFrame::TypeToMarker(StackFrame::ARGUMENTS_ADAPTOR));
|
||||
__ Push(x11, x1); // x1: function
|
||||
__ SmiTag(x11, x0); // x0: number of arguments.
|
||||
@ -2069,7 +2069,7 @@ void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) {
|
||||
// then drop the parameters and the receiver.
|
||||
__ Ldr(x10, MemOperand(fp, ArgumentsAdaptorFrameConstants::kLengthOffset));
|
||||
__ Mov(sp, fp);
|
||||
__ Pop(fp, lr);
|
||||
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
|
||||
// Drop actual parameters and receiver.
|
||||
__ SmiUntag(x10);
|
||||
@ -3675,9 +3675,9 @@ void Builtins::Generate_DirectCEntry(MacroAssembler* masm) {
|
||||
// DirectCEntry places the return address on the stack (updated by the GC),
|
||||
// making the call GC safe. The irregexp backend relies on this.
|
||||
|
||||
__ Poke(lr, 0); // Store the return address.
|
||||
__ Poke<TurboAssembler::kSignLR>(lr, 0); // Store the return address.
|
||||
__ Blr(x10); // Call the C++ function.
|
||||
__ Peek(lr, 0); // Return to calling code.
|
||||
__ Peek<TurboAssembler::kAuthLR>(lr, 0); // Return to calling code.
|
||||
__ AssertFPCRState();
|
||||
__ Ret();
|
||||
}
|
||||
|
@ -1075,6 +1075,166 @@ void MacroAssembler::JumpIfNotSmi(Register value, Label* not_smi_label) {
|
||||
|
||||
void TurboAssembler::jmp(Label* L) { B(L); }
|
||||
|
||||
template <TurboAssembler::StoreLRMode lr_mode>
|
||||
void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
|
||||
const CPURegister& src2, const CPURegister& src3) {
|
||||
DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
|
||||
DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr) ||
|
||||
(src2 == lr) || (src3 == lr)));
|
||||
DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr) &&
|
||||
(src2 != lr) && (src3 != lr)));
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kSignLR) {
|
||||
Paciasp();
|
||||
}
|
||||
#endif
|
||||
|
||||
int count = 1 + src1.is_valid() + src2.is_valid() + src3.is_valid();
|
||||
int size = src0.SizeInBytes();
|
||||
DCHECK_EQ(0, (size * count) % 16);
|
||||
|
||||
PushHelper(count, size, src0, src1, src2, src3);
|
||||
}
|
||||
|
||||
template <TurboAssembler::StoreLRMode lr_mode>
|
||||
void TurboAssembler::Push(const Register& src0, const VRegister& src1) {
|
||||
DCHECK_IMPLIES((lr_mode == kSignLR), ((src0 == lr) || (src1 == lr)));
|
||||
DCHECK_IMPLIES((lr_mode == kDontStoreLR), ((src0 != lr) && (src1 != lr)));
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kSignLR) {
|
||||
Paciasp();
|
||||
}
|
||||
#endif
|
||||
|
||||
int size = src0.SizeInBytes() + src1.SizeInBytes();
|
||||
DCHECK_EQ(0, size % 16);
|
||||
|
||||
// Reserve room for src0 and push src1.
|
||||
str(src1, MemOperand(sp, -size, PreIndex));
|
||||
// Fill the gap with src0.
|
||||
str(src0, MemOperand(sp, src1.SizeInBytes()));
|
||||
}
|
||||
|
||||
template <TurboAssembler::LoadLRMode lr_mode>
|
||||
void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
|
||||
const CPURegister& dst2, const CPURegister& dst3) {
|
||||
// It is not valid to pop into the same register more than once in one
|
||||
// instruction, not even into the zero register.
|
||||
DCHECK(!AreAliased(dst0, dst1, dst2, dst3));
|
||||
DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
|
||||
DCHECK(dst0.is_valid());
|
||||
|
||||
int count = 1 + dst1.is_valid() + dst2.is_valid() + dst3.is_valid();
|
||||
int size = dst0.SizeInBytes();
|
||||
DCHECK_EQ(0, (size * count) % 16);
|
||||
|
||||
PopHelper(count, size, dst0, dst1, dst2, dst3);
|
||||
|
||||
DCHECK_IMPLIES((lr_mode == kAuthLR), ((dst0 == lr) || (dst1 == lr) ||
|
||||
(dst2 == lr) || (dst3 == lr)));
|
||||
DCHECK_IMPLIES((lr_mode == kDontLoadLR), ((dst0 != lr) && (dst1 != lr)) &&
|
||||
(dst2 != lr) && (dst3 != lr));
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kAuthLR) {
|
||||
Autiasp();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <TurboAssembler::StoreLRMode lr_mode>
|
||||
void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) {
|
||||
DCHECK_IMPLIES((lr_mode == kSignLR), (src == lr));
|
||||
DCHECK_IMPLIES((lr_mode == kDontStoreLR), (src != lr));
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kSignLR) {
|
||||
Paciasp();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (offset.IsImmediate()) {
|
||||
DCHECK_GE(offset.ImmediateValue(), 0);
|
||||
} else if (emit_debug_code()) {
|
||||
Cmp(xzr, offset);
|
||||
Check(le, AbortReason::kStackAccessBelowStackPointer);
|
||||
}
|
||||
|
||||
Str(src, MemOperand(sp, offset));
|
||||
}
|
||||
|
||||
template <TurboAssembler::LoadLRMode lr_mode>
|
||||
void TurboAssembler::Peek(const CPURegister& dst, const Operand& offset) {
|
||||
if (offset.IsImmediate()) {
|
||||
DCHECK_GE(offset.ImmediateValue(), 0);
|
||||
} else if (emit_debug_code()) {
|
||||
Cmp(xzr, offset);
|
||||
Check(le, AbortReason::kStackAccessBelowStackPointer);
|
||||
}
|
||||
|
||||
Ldr(dst, MemOperand(sp, offset));
|
||||
|
||||
DCHECK_IMPLIES((lr_mode == kAuthLR), (dst == lr));
|
||||
DCHECK_IMPLIES((lr_mode == kDontLoadLR), (dst != lr));
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kAuthLR) {
|
||||
Autiasp();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <TurboAssembler::StoreLRMode lr_mode>
|
||||
void TurboAssembler::PushCPURegList(CPURegList registers) {
|
||||
DCHECK_IMPLIES((lr_mode == kDontStoreLR), !registers.IncludesAliasOf(lr));
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kSignLR && registers.IncludesAliasOf(lr)) {
|
||||
Paciasp();
|
||||
}
|
||||
#endif
|
||||
|
||||
int size = registers.RegisterSizeInBytes();
|
||||
DCHECK_EQ(0, (size * registers.Count()) % 16);
|
||||
|
||||
// Push up to four registers at a time.
|
||||
while (!registers.IsEmpty()) {
|
||||
int count_before = registers.Count();
|
||||
const CPURegister& src0 = registers.PopHighestIndex();
|
||||
const CPURegister& src1 = registers.PopHighestIndex();
|
||||
const CPURegister& src2 = registers.PopHighestIndex();
|
||||
const CPURegister& src3 = registers.PopHighestIndex();
|
||||
int count = count_before - registers.Count();
|
||||
PushHelper(count, size, src0, src1, src2, src3);
|
||||
}
|
||||
}
|
||||
|
||||
template <TurboAssembler::LoadLRMode lr_mode>
|
||||
void TurboAssembler::PopCPURegList(CPURegList registers) {
|
||||
int size = registers.RegisterSizeInBytes();
|
||||
DCHECK_EQ(0, (size * registers.Count()) % 16);
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
bool contains_lr = registers.IncludesAliasOf(lr);
|
||||
DCHECK_IMPLIES((lr_mode == kDontLoadLR), !contains_lr);
|
||||
#endif
|
||||
|
||||
// Pop up to four registers at a time.
|
||||
while (!registers.IsEmpty()) {
|
||||
int count_before = registers.Count();
|
||||
const CPURegister& dst0 = registers.PopLowestIndex();
|
||||
const CPURegister& dst1 = registers.PopLowestIndex();
|
||||
const CPURegister& dst2 = registers.PopLowestIndex();
|
||||
const CPURegister& dst3 = registers.PopLowestIndex();
|
||||
int count = count_before - registers.Count();
|
||||
PopHelper(count, size, dst0, dst1, dst2, dst3);
|
||||
}
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
if (lr_mode == kAuthLR && contains_lr) {
|
||||
Autiasp();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TurboAssembler::Push(Handle<HeapObject> handle) {
|
||||
UseScratchRegisterScope temps(this);
|
||||
Register tmp = temps.AcquireX();
|
||||
|
@ -60,7 +60,7 @@ int TurboAssembler::PushCallerSaved(SaveFPRegsMode fp_mode,
|
||||
list.Remove(exclusion);
|
||||
list.Align();
|
||||
|
||||
PushCPURegList(list);
|
||||
PushCPURegList<kSignLR>(list);
|
||||
|
||||
int bytes = list.Count() * kXRegSizeInBits / 8;
|
||||
|
||||
@ -84,7 +84,7 @@ int TurboAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) {
|
||||
list.Remove(exclusion);
|
||||
list.Align();
|
||||
|
||||
PopCPURegList(list);
|
||||
PopCPURegList<kAuthLR>(list);
|
||||
bytes += list.Count() * kXRegSizeInBits / 8;
|
||||
|
||||
return bytes;
|
||||
@ -1045,17 +1045,6 @@ void TurboAssembler::Abs(const Register& rd, const Register& rm,
|
||||
|
||||
// Abstracted stack operations.
|
||||
|
||||
void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
|
||||
const CPURegister& src2, const CPURegister& src3) {
|
||||
DCHECK(AreSameSizeAndType(src0, src1, src2, src3));
|
||||
|
||||
int count = 1 + src1.is_valid() + src2.is_valid() + src3.is_valid();
|
||||
int size = src0.SizeInBytes();
|
||||
DCHECK_EQ(0, (size * count) % 16);
|
||||
|
||||
PushHelper(count, size, src0, src1, src2, src3);
|
||||
}
|
||||
|
||||
void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
|
||||
const CPURegister& src2, const CPURegister& src3,
|
||||
const CPURegister& src4, const CPURegister& src5,
|
||||
@ -1070,21 +1059,6 @@ void TurboAssembler::Push(const CPURegister& src0, const CPURegister& src1,
|
||||
PushHelper(count - 4, size, src4, src5, src6, src7);
|
||||
}
|
||||
|
||||
void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
|
||||
const CPURegister& dst2, const CPURegister& dst3) {
|
||||
// It is not valid to pop into the same register more than once in one
|
||||
// instruction, not even into the zero register.
|
||||
DCHECK(!AreAliased(dst0, dst1, dst2, dst3));
|
||||
DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3));
|
||||
DCHECK(dst0.is_valid());
|
||||
|
||||
int count = 1 + dst1.is_valid() + dst2.is_valid() + dst3.is_valid();
|
||||
int size = dst0.SizeInBytes();
|
||||
DCHECK_EQ(0, (size * count) % 16);
|
||||
|
||||
PopHelper(count, size, dst0, dst1, dst2, dst3);
|
||||
}
|
||||
|
||||
void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
|
||||
const CPURegister& dst2, const CPURegister& dst3,
|
||||
const CPURegister& dst4, const CPURegister& dst5,
|
||||
@ -1103,48 +1077,6 @@ void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1,
|
||||
PopHelper(count - 4, size, dst4, dst5, dst6, dst7);
|
||||
}
|
||||
|
||||
void TurboAssembler::Push(const Register& src0, const VRegister& src1) {
|
||||
int size = src0.SizeInBytes() + src1.SizeInBytes();
|
||||
DCHECK_EQ(0, size % 16);
|
||||
|
||||
// Reserve room for src0 and push src1.
|
||||
str(src1, MemOperand(sp, -size, PreIndex));
|
||||
// Fill the gap with src0.
|
||||
str(src0, MemOperand(sp, src1.SizeInBytes()));
|
||||
}
|
||||
|
||||
void TurboAssembler::PushCPURegList(CPURegList registers) {
|
||||
int size = registers.RegisterSizeInBytes();
|
||||
DCHECK_EQ(0, (size * registers.Count()) % 16);
|
||||
|
||||
// Push up to four registers at a time.
|
||||
while (!registers.IsEmpty()) {
|
||||
int count_before = registers.Count();
|
||||
const CPURegister& src0 = registers.PopHighestIndex();
|
||||
const CPURegister& src1 = registers.PopHighestIndex();
|
||||
const CPURegister& src2 = registers.PopHighestIndex();
|
||||
const CPURegister& src3 = registers.PopHighestIndex();
|
||||
int count = count_before - registers.Count();
|
||||
PushHelper(count, size, src0, src1, src2, src3);
|
||||
}
|
||||
}
|
||||
|
||||
void TurboAssembler::PopCPURegList(CPURegList registers) {
|
||||
int size = registers.RegisterSizeInBytes();
|
||||
DCHECK_EQ(0, (size * registers.Count()) % 16);
|
||||
|
||||
// Pop up to four registers at a time.
|
||||
while (!registers.IsEmpty()) {
|
||||
int count_before = registers.Count();
|
||||
const CPURegister& dst0 = registers.PopLowestIndex();
|
||||
const CPURegister& dst1 = registers.PopLowestIndex();
|
||||
const CPURegister& dst2 = registers.PopLowestIndex();
|
||||
const CPURegister& dst3 = registers.PopLowestIndex();
|
||||
int count = count_before - registers.Count();
|
||||
PopHelper(count, size, dst0, dst1, dst2, dst3);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) {
|
||||
UseScratchRegisterScope temps(this);
|
||||
Register temp = temps.AcquireSameSizeAs(count);
|
||||
@ -1249,28 +1181,6 @@ void TurboAssembler::PopHelper(int count, int size, const CPURegister& dst0,
|
||||
}
|
||||
}
|
||||
|
||||
void TurboAssembler::Poke(const CPURegister& src, const Operand& offset) {
|
||||
if (offset.IsImmediate()) {
|
||||
DCHECK_GE(offset.ImmediateValue(), 0);
|
||||
} else if (emit_debug_code()) {
|
||||
Cmp(xzr, offset);
|
||||
Check(le, AbortReason::kStackAccessBelowStackPointer);
|
||||
}
|
||||
|
||||
Str(src, MemOperand(sp, offset));
|
||||
}
|
||||
|
||||
void TurboAssembler::Peek(const CPURegister& dst, const Operand& offset) {
|
||||
if (offset.IsImmediate()) {
|
||||
DCHECK_GE(offset.ImmediateValue(), 0);
|
||||
} else if (emit_debug_code()) {
|
||||
Cmp(xzr, offset);
|
||||
Check(le, AbortReason::kStackAccessBelowStackPointer);
|
||||
}
|
||||
|
||||
Ldr(dst, MemOperand(sp, offset));
|
||||
}
|
||||
|
||||
void TurboAssembler::PokePair(const CPURegister& src1, const CPURegister& src2,
|
||||
int offset) {
|
||||
DCHECK(AreSameSizeAndType(src1, src2));
|
||||
@ -1286,50 +1196,61 @@ void MacroAssembler::PeekPair(const CPURegister& dst1, const CPURegister& dst2,
|
||||
}
|
||||
|
||||
void MacroAssembler::PushCalleeSavedRegisters() {
|
||||
// Ensure that the macro-assembler doesn't use any scratch registers.
|
||||
InstructionAccurateScope scope(this);
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
Paciasp();
|
||||
#endif
|
||||
|
||||
MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex);
|
||||
{
|
||||
// Ensure that the macro-assembler doesn't use any scratch registers.
|
||||
InstructionAccurateScope scope(this);
|
||||
|
||||
stp(d14, d15, tos);
|
||||
stp(d12, d13, tos);
|
||||
stp(d10, d11, tos);
|
||||
stp(d8, d9, tos);
|
||||
MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex);
|
||||
|
||||
STATIC_ASSERT(
|
||||
EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
|
||||
8 * kSystemPointerSize);
|
||||
stp(d14, d15, tos);
|
||||
stp(d12, d13, tos);
|
||||
stp(d10, d11, tos);
|
||||
stp(d8, d9, tos);
|
||||
|
||||
stp(x29, x30, tos); // fp, lr
|
||||
STATIC_ASSERT(
|
||||
EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
|
||||
8 * kSystemPointerSize);
|
||||
stp(x29, x30, tos); // fp, lr
|
||||
|
||||
STATIC_ASSERT(
|
||||
EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair ==
|
||||
10 * kSystemPointerSize);
|
||||
STATIC_ASSERT(
|
||||
EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair ==
|
||||
10 * kSystemPointerSize);
|
||||
|
||||
stp(x27, x28, tos);
|
||||
stp(x25, x26, tos);
|
||||
stp(x23, x24, tos);
|
||||
stp(x21, x22, tos);
|
||||
stp(x19, x20, tos);
|
||||
stp(x27, x28, tos);
|
||||
stp(x25, x26, tos);
|
||||
stp(x23, x24, tos);
|
||||
stp(x21, x22, tos);
|
||||
stp(x19, x20, tos);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroAssembler::PopCalleeSavedRegisters() {
|
||||
// Ensure that the macro-assembler doesn't use any scratch registers.
|
||||
InstructionAccurateScope scope(this);
|
||||
{
|
||||
// Ensure that the macro-assembler doesn't use any scratch registers.
|
||||
InstructionAccurateScope scope(this);
|
||||
|
||||
MemOperand tos(sp, 2 * kXRegSize, PostIndex);
|
||||
MemOperand tos(sp, 2 * kXRegSize, PostIndex);
|
||||
|
||||
ldp(x19, x20, tos);
|
||||
ldp(x21, x22, tos);
|
||||
ldp(x23, x24, tos);
|
||||
ldp(x25, x26, tos);
|
||||
ldp(x27, x28, tos);
|
||||
ldp(x29, x30, tos);
|
||||
ldp(x19, x20, tos);
|
||||
ldp(x21, x22, tos);
|
||||
ldp(x23, x24, tos);
|
||||
ldp(x25, x26, tos);
|
||||
ldp(x27, x28, tos);
|
||||
ldp(x29, x30, tos);
|
||||
|
||||
ldp(d8, d9, tos);
|
||||
ldp(d10, d11, tos);
|
||||
ldp(d12, d13, tos);
|
||||
ldp(d14, d15, tos);
|
||||
ldp(d8, d9, tos);
|
||||
ldp(d10, d11, tos);
|
||||
ldp(d12, d13, tos);
|
||||
ldp(d14, d15, tos);
|
||||
}
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
Autiasp();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TurboAssembler::AssertSpAligned() {
|
||||
@ -2017,18 +1938,22 @@ void TurboAssembler::StoreReturnAddressAndCall(Register target) {
|
||||
// GC, since the callee function will return to it.
|
||||
|
||||
UseScratchRegisterScope temps(this);
|
||||
Register scratch1 = temps.AcquireX();
|
||||
temps.Exclude(x16, x17);
|
||||
|
||||
Label return_location;
|
||||
Adr(scratch1, &return_location);
|
||||
Poke(scratch1, 0);
|
||||
Adr(x17, &return_location);
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
Add(x16, sp, kSystemPointerSize);
|
||||
Pacia1716();
|
||||
#endif
|
||||
Poke(x17, 0);
|
||||
|
||||
if (emit_debug_code()) {
|
||||
// Verify that the slot below fp[kSPOffset]-8 points to the return location.
|
||||
Register scratch2 = temps.AcquireX();
|
||||
Ldr(scratch2, MemOperand(fp, ExitFrameConstants::kSPOffset));
|
||||
Ldr(scratch2, MemOperand(scratch2, -static_cast<int64_t>(kXRegSize)));
|
||||
Cmp(scratch2, scratch1);
|
||||
// Verify that the slot below fp[kSPOffset]-8 points to the signed return
|
||||
// location.
|
||||
Ldr(x16, MemOperand(fp, ExitFrameConstants::kSPOffset));
|
||||
Ldr(x16, MemOperand(x16, -static_cast<int64_t>(kXRegSize)));
|
||||
Cmp(x16, x17);
|
||||
Check(eq, AbortReason::kReturnAddressNotFoundInFrame);
|
||||
}
|
||||
|
||||
@ -2096,8 +2021,7 @@ void TurboAssembler::PrepareForTailCall(Register callee_args_count,
|
||||
|
||||
// Restore caller's frame pointer and return address now as they will be
|
||||
// overwritten by the copying loop.
|
||||
Ldr(lr, MemOperand(fp, StandardFrameConstants::kCallerPCOffset));
|
||||
Ldr(fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
|
||||
RestoreFPAndLR();
|
||||
|
||||
// Now copy callee arguments to the caller frame going backwards to avoid
|
||||
// callee arguments corruption (source and destination areas could overlap).
|
||||
@ -2309,7 +2233,7 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
|
||||
TryConvertDoubleToInt64(result, double_input, &done);
|
||||
|
||||
// If we fell through then inline version didn't succeed - call stub instead.
|
||||
Push(lr, double_input);
|
||||
Push<TurboAssembler::kSignLR>(lr, double_input);
|
||||
|
||||
// DoubleToI preserves any registers it needs to clobber.
|
||||
if (stub_mode == StubCallMode::kCallWasmRuntimeStub) {
|
||||
@ -2322,7 +2246,8 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
|
||||
Ldr(result, MemOperand(sp, 0));
|
||||
|
||||
DCHECK_EQ(xzr.SizeInBytes(), double_input.SizeInBytes());
|
||||
Pop(xzr, lr); // xzr to drop the double input on the stack.
|
||||
// Pop into xzr here to drop the double input on the stack:
|
||||
Pop<TurboAssembler::kAuthLR>(xzr, lr);
|
||||
|
||||
Bind(&done);
|
||||
// Keep our invariant that the upper 32 bits are zero.
|
||||
@ -2330,7 +2255,7 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
|
||||
}
|
||||
|
||||
void TurboAssembler::Prologue() {
|
||||
Push(lr, fp, cp, x1);
|
||||
Push<TurboAssembler::kSignLR>(lr, fp, cp, x1);
|
||||
Add(fp, sp, StandardFrameConstants::kFixedFrameSizeFromFp);
|
||||
}
|
||||
|
||||
@ -2341,7 +2266,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) {
|
||||
Register type_reg = temps.AcquireX();
|
||||
Mov(type_reg, StackFrame::TypeToMarker(type));
|
||||
// type_reg pushed twice for alignment.
|
||||
Push(lr, fp, type_reg, type_reg);
|
||||
Push<TurboAssembler::kSignLR>(lr, fp, type_reg, type_reg);
|
||||
const int kFrameSize =
|
||||
TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize;
|
||||
Add(fp, sp, kFrameSize);
|
||||
@ -2354,7 +2279,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) {
|
||||
type == StackFrame::WASM_EXIT) {
|
||||
Register type_reg = temps.AcquireX();
|
||||
Mov(type_reg, StackFrame::TypeToMarker(type));
|
||||
Push(lr, fp);
|
||||
Push<TurboAssembler::kSignLR>(lr, fp);
|
||||
Mov(fp, sp);
|
||||
Push(type_reg, padreg);
|
||||
// sp[3] : lr
|
||||
@ -2368,7 +2293,7 @@ void TurboAssembler::EnterFrame(StackFrame::Type type) {
|
||||
|
||||
// Users of this frame type push a context pointer after the type field,
|
||||
// so do it here to keep the stack pointer aligned.
|
||||
Push(lr, fp, type_reg, cp);
|
||||
Push<TurboAssembler::kSignLR>(lr, fp, type_reg, cp);
|
||||
|
||||
// The context pointer isn't part of the fixed frame, so add an extra slot
|
||||
// to account for it.
|
||||
@ -2385,7 +2310,7 @@ void TurboAssembler::LeaveFrame(StackFrame::Type type) {
|
||||
// Drop the execution stack down to the frame pointer and restore
|
||||
// the caller frame pointer and return address.
|
||||
Mov(sp, fp);
|
||||
Pop(fp, lr);
|
||||
Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
}
|
||||
|
||||
void MacroAssembler::ExitFramePreserveFPRegs() {
|
||||
@ -2415,7 +2340,7 @@ void MacroAssembler::EnterExitFrame(bool save_doubles, const Register& scratch,
|
||||
frame_type == StackFrame::BUILTIN_EXIT);
|
||||
|
||||
// Set up the new stack frame.
|
||||
Push(lr, fp);
|
||||
Push<TurboAssembler::kSignLR>(lr, fp);
|
||||
Mov(fp, sp);
|
||||
Mov(scratch, StackFrame::TypeToMarker(frame_type));
|
||||
Push(scratch, xzr);
|
||||
@ -2498,7 +2423,7 @@ void MacroAssembler::LeaveExitFrame(bool restore_doubles,
|
||||
// fp -> fp[0]: CallerFP (old fp)
|
||||
// fp[...]: The rest of the frame.
|
||||
Mov(sp, fp);
|
||||
Pop(fp, lr);
|
||||
Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
}
|
||||
|
||||
void MacroAssembler::LoadGlobalProxy(Register dst) {
|
||||
@ -2751,25 +2676,19 @@ void MacroAssembler::RecordWriteField(Register object, int offset,
|
||||
|
||||
void TurboAssembler::SaveRegisters(RegList registers) {
|
||||
DCHECK_GT(NumRegs(registers), 0);
|
||||
CPURegList regs(lr);
|
||||
for (int i = 0; i < Register::kNumRegisters; ++i) {
|
||||
if ((registers >> i) & 1u) {
|
||||
regs.Combine(Register::XRegFromCode(i));
|
||||
}
|
||||
}
|
||||
|
||||
CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers);
|
||||
// If we were saving LR, we might need to sign it.
|
||||
DCHECK(!regs.IncludesAliasOf(lr));
|
||||
regs.Align();
|
||||
PushCPURegList(regs);
|
||||
}
|
||||
|
||||
void TurboAssembler::RestoreRegisters(RegList registers) {
|
||||
DCHECK_GT(NumRegs(registers), 0);
|
||||
CPURegList regs(lr);
|
||||
for (int i = 0; i < Register::kNumRegisters; ++i) {
|
||||
if ((registers >> i) & 1u) {
|
||||
regs.Combine(Register::XRegFromCode(i));
|
||||
}
|
||||
}
|
||||
|
||||
CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers);
|
||||
// If we were saving LR, we might need to sign it.
|
||||
DCHECK(!regs.IncludesAliasOf(lr));
|
||||
regs.Align();
|
||||
PopCPURegList(regs);
|
||||
}
|
||||
|
||||
@ -2924,11 +2843,11 @@ void MacroAssembler::RecordWrite(Register object, Operand offset,
|
||||
|
||||
// Record the actual write.
|
||||
if (lr_status == kLRHasNotBeenSaved) {
|
||||
Push(padreg, lr);
|
||||
Push<TurboAssembler::kSignLR>(padreg, lr);
|
||||
}
|
||||
CallRecordWriteStub(object, offset, remembered_set_action, fp_mode);
|
||||
if (lr_status == kLRHasNotBeenSaved) {
|
||||
Pop(lr, padreg);
|
||||
Pop<TurboAssembler::kAuthLR>(lr, padreg);
|
||||
}
|
||||
|
||||
Bind(&done);
|
||||
@ -3183,7 +3102,7 @@ void TurboAssembler::Printf(const char* format, CPURegister arg0,
|
||||
// Preserve all caller-saved registers as well as NZCV.
|
||||
// PushCPURegList asserts that the size of each list is a multiple of 16
|
||||
// bytes.
|
||||
PushCPURegList(saved_registers);
|
||||
PushCPURegList<kDontSignLR>(saved_registers);
|
||||
PushCPURegList(kCallerSavedV);
|
||||
|
||||
// We can use caller-saved registers as scratch values (except for argN).
|
||||
@ -3236,7 +3155,7 @@ void TurboAssembler::Printf(const char* format, CPURegister arg0,
|
||||
}
|
||||
|
||||
PopCPURegList(kCallerSavedV);
|
||||
PopCPURegList(saved_registers);
|
||||
PopCPURegList<kDontAuthLR>(saved_registers);
|
||||
|
||||
TmpList()->set_list(old_tmp_list);
|
||||
FPTmpList()->set_list(old_fp_tmp_list);
|
||||
@ -3274,6 +3193,35 @@ void TurboAssembler::ResetSpeculationPoisonRegister() {
|
||||
Mov(kSpeculationPoisonRegister, -1);
|
||||
}
|
||||
|
||||
void TurboAssembler::RestoreFPAndLR() {
|
||||
static_assert(StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
|
||||
StandardFrameConstants::kCallerPCOffset,
|
||||
"Offsets must be consecutive for ldp!");
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
// Make sure we can use x16 and x17.
|
||||
UseScratchRegisterScope temps(this);
|
||||
temps.Exclude(x16, x17);
|
||||
// We can load the return address directly into x17.
|
||||
Add(x16, fp, StandardFrameConstants::kCallerSPOffset);
|
||||
Ldp(fp, x17, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
|
||||
Autia1716();
|
||||
Mov(lr, x17);
|
||||
#else
|
||||
Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
|
||||
#endif
|
||||
}
|
||||
|
||||
void TurboAssembler::StoreReturnAddressInWasmExitFrame(Label* return_location) {
|
||||
UseScratchRegisterScope temps(this);
|
||||
temps.Exclude(x16, x17);
|
||||
Adr(x17, return_location);
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
Add(x16, fp, WasmExitFrameConstants::kCallingPCOffset + kSystemPointerSize);
|
||||
Pacia1716();
|
||||
#endif
|
||||
Str(x17, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -783,20 +783,33 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
|
||||
// The stack pointer must be aligned to 16 bytes on entry and the total size
|
||||
// of the specified registers must also be a multiple of 16 bytes.
|
||||
//
|
||||
// Other than the registers passed into Pop, the stack pointer and (possibly)
|
||||
// the system stack pointer, these methods do not modify any other registers.
|
||||
// Other than the registers passed into Pop, the stack pointer, (possibly)
|
||||
// the system stack pointer and (possibly) the link register, these methods
|
||||
// do not modify any other registers.
|
||||
//
|
||||
// Some of the methods take an optional LoadLRMode or StoreLRMode template
|
||||
// argument, which specifies whether we need to sign the link register at the
|
||||
// start of the operation, or authenticate it at the end of the operation,
|
||||
// when control flow integrity measures are enabled.
|
||||
// When the mode is kDontLoadLR or kDontStoreLR, LR must not be passed as an
|
||||
// argument to the operation.
|
||||
enum LoadLRMode { kAuthLR, kDontAuthLR, kDontLoadLR };
|
||||
enum StoreLRMode { kSignLR, kDontSignLR, kDontStoreLR };
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
void Push(const CPURegister& src0, const CPURegister& src1 = NoReg,
|
||||
const CPURegister& src2 = NoReg, const CPURegister& src3 = NoReg);
|
||||
void Push(const CPURegister& src0, const CPURegister& src1,
|
||||
const CPURegister& src2, const CPURegister& src3,
|
||||
const CPURegister& src4, const CPURegister& src5 = NoReg,
|
||||
const CPURegister& src6 = NoReg, const CPURegister& src7 = NoReg);
|
||||
template <LoadLRMode lr_mode = kDontLoadLR>
|
||||
void Pop(const CPURegister& dst0, const CPURegister& dst1 = NoReg,
|
||||
const CPURegister& dst2 = NoReg, const CPURegister& dst3 = NoReg);
|
||||
void Pop(const CPURegister& dst0, const CPURegister& dst1,
|
||||
const CPURegister& dst2, const CPURegister& dst3,
|
||||
const CPURegister& dst4, const CPURegister& dst5 = NoReg,
|
||||
const CPURegister& dst6 = NoReg, const CPURegister& dst7 = NoReg);
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
void Push(const Register& src0, const VRegister& src1);
|
||||
|
||||
// This is a convenience method for pushing a single Handle<Object>.
|
||||
@ -838,7 +851,15 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
|
||||
// kSRegSizeInBits are supported.
|
||||
//
|
||||
// Otherwise, (Push|Pop)(CPU|X|W|D|S)RegList is preferred.
|
||||
//
|
||||
// The methods take an optional LoadLRMode or StoreLRMode template argument.
|
||||
// When control flow integrity measures are enabled and the link register is
|
||||
// included in 'registers', passing kSignLR to PushCPURegList will sign the
|
||||
// link register before pushing the list, and passing kAuthLR to
|
||||
// PopCPURegList will authenticate it after popping the list.
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
void PushCPURegList(CPURegList registers);
|
||||
template <LoadLRMode lr_mode = kDontLoadLR>
|
||||
void PopCPURegList(CPURegList registers);
|
||||
|
||||
// Calculate how much stack space (in bytes) are required to store caller
|
||||
@ -1042,10 +1063,18 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
|
||||
|
||||
// Poke 'src' onto the stack. The offset is in bytes. The stack pointer must
|
||||
// be 16 byte aligned.
|
||||
// When the optional template argument is kSignLR and control flow integrity
|
||||
// measures are enabled, we sign the link register before poking it onto the
|
||||
// stack. 'src' must be lr in this case.
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
void Poke(const CPURegister& src, const Operand& offset);
|
||||
|
||||
// Peek at a value on the stack, and put it in 'dst'. The offset is in bytes.
|
||||
// The stack pointer must be aligned to 16 bytes.
|
||||
// When the optional template argument is kAuthLR and control flow integrity
|
||||
// measures are enabled, we authenticate the link register after peeking the
|
||||
// value. 'dst' must be lr in this case.
|
||||
template <LoadLRMode lr_mode = kDontLoadLR>
|
||||
void Peek(const CPURegister& dst, const Operand& offset);
|
||||
|
||||
// Poke 'src1' and 'src2' onto the stack. The values written will be adjacent
|
||||
@ -1297,6 +1326,12 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
|
||||
void DecompressAnyTagged(const Register& destination,
|
||||
const MemOperand& field_operand);
|
||||
|
||||
// Restore FP and LR from the values stored in the current frame. This will
|
||||
// authenticate the LR when pointer authentication is enabled.
|
||||
void RestoreFPAndLR();
|
||||
|
||||
void StoreReturnAddressInWasmExitFrame(Label* return_location);
|
||||
|
||||
protected:
|
||||
// The actual Push and Pop implementations. These don't generate any code
|
||||
// other than that required for the push or pop. This allows
|
||||
@ -1625,21 +1660,27 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
|
||||
tbx(vd, vn, vn2, vn3, vn4, vm);
|
||||
}
|
||||
|
||||
// For the 'lr_mode' template argument of the following methods, see
|
||||
// PushCPURegList/PopCPURegList.
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
inline void PushSizeRegList(
|
||||
RegList registers, unsigned reg_size,
|
||||
CPURegister::RegisterType type = CPURegister::kRegister) {
|
||||
PushCPURegList(CPURegList(type, reg_size, registers));
|
||||
PushCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
|
||||
}
|
||||
template <LoadLRMode lr_mode = kDontLoadLR>
|
||||
inline void PopSizeRegList(
|
||||
RegList registers, unsigned reg_size,
|
||||
CPURegister::RegisterType type = CPURegister::kRegister) {
|
||||
PopCPURegList(CPURegList(type, reg_size, registers));
|
||||
PopCPURegList<lr_mode>(CPURegList(type, reg_size, registers));
|
||||
}
|
||||
template <StoreLRMode lr_mode = kDontStoreLR>
|
||||
inline void PushXRegList(RegList regs) {
|
||||
PushSizeRegList(regs, kXRegSizeInBits);
|
||||
PushSizeRegList<lr_mode>(regs, kXRegSizeInBits);
|
||||
}
|
||||
template <LoadLRMode lr_mode = kDontLoadLR>
|
||||
inline void PopXRegList(RegList regs) {
|
||||
PopSizeRegList(regs, kXRegSizeInBits);
|
||||
PopSizeRegList<lr_mode>(regs, kXRegSizeInBits);
|
||||
}
|
||||
inline void PushWRegList(RegList regs) {
|
||||
PushSizeRegList(regs, kWRegSizeInBits);
|
||||
@ -1681,6 +1722,9 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
|
||||
// Floating-point registers are pushed before general-purpose registers, and
|
||||
// thus get higher addresses.
|
||||
//
|
||||
// When control flow integrity measures are enabled, this method signs the
|
||||
// link register before pushing it.
|
||||
//
|
||||
// Note that registers are not checked for invalid values. Use this method
|
||||
// only if you know that the GC won't try to examine the values on the stack.
|
||||
void PushCalleeSavedRegisters();
|
||||
@ -1691,6 +1735,9 @@ class V8_EXPORT_PRIVATE MacroAssembler : public TurboAssembler {
|
||||
// thus come from higher addresses.
|
||||
// Floating-point registers are popped after general-purpose registers, and
|
||||
// thus come from higher addresses.
|
||||
//
|
||||
// When control flow integrity measures are enabled, this method
|
||||
// authenticates the link register after popping it.
|
||||
void PopCalleeSavedRegisters();
|
||||
|
||||
// Helpers ------------------------------------------------------------------
|
||||
|
@ -291,7 +291,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode {
|
||||
frame()->DidAllocateDoubleRegisters() ? kSaveFPRegs : kDontSaveFPRegs;
|
||||
if (must_save_lr_) {
|
||||
// We need to save and restore lr if the frame was elided.
|
||||
__ Push(lr, padreg);
|
||||
__ Push<TurboAssembler::kSignLR>(lr, padreg);
|
||||
unwinding_info_writer_->MarkLinkRegisterOnTopOfStack(__ pc_offset(), sp);
|
||||
}
|
||||
if (mode_ == RecordWriteMode::kValueIsEphemeronKey) {
|
||||
@ -307,7 +307,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode {
|
||||
save_fp_mode);
|
||||
}
|
||||
if (must_save_lr_) {
|
||||
__ Pop(padreg, lr);
|
||||
__ Pop<TurboAssembler::kAuthLR>(padreg, lr);
|
||||
unwinding_info_writer_->MarkPopLinkRegisterFromTopOfStack(__ pc_offset());
|
||||
}
|
||||
}
|
||||
@ -496,18 +496,14 @@ void EmitMaybePoisonedFPLoad(CodeGenerator* codegen, InstructionCode opcode,
|
||||
|
||||
void CodeGenerator::AssembleDeconstructFrame() {
|
||||
__ Mov(sp, fp);
|
||||
__ Pop(fp, lr);
|
||||
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
|
||||
unwinding_info_writer_.MarkFrameDeconstructed(__ pc_offset());
|
||||
}
|
||||
|
||||
void CodeGenerator::AssemblePrepareTailCall() {
|
||||
if (frame_access_state()->has_frame()) {
|
||||
static_assert(
|
||||
StandardFrameConstants::kCallerFPOffset + kSystemPointerSize ==
|
||||
StandardFrameConstants::kCallerPCOffset,
|
||||
"Offsets must be consecutive for ldp!");
|
||||
__ Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
|
||||
__ RestoreFPAndLR();
|
||||
}
|
||||
frame_access_state()->SetFrameAccessToSP();
|
||||
}
|
||||
@ -779,10 +775,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
Label return_location;
|
||||
if (linkage()->GetIncomingDescriptor()->IsWasmCapiFunction()) {
|
||||
// Put the return address in a stack slot.
|
||||
Register scratch = x8;
|
||||
__ Adr(scratch, &return_location);
|
||||
__ Str(scratch,
|
||||
MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
|
||||
__ StoreReturnAddressInWasmExitFrame(&return_location);
|
||||
}
|
||||
|
||||
if (instr->InputAt(0)->IsImmediate()) {
|
||||
@ -2812,7 +2805,7 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
if (call_descriptor->IsJSFunctionCall()) {
|
||||
__ Prologue();
|
||||
} else {
|
||||
__ Push(lr, fp);
|
||||
__ Push<TurboAssembler::kSignLR>(lr, fp);
|
||||
__ Mov(fp, sp);
|
||||
}
|
||||
unwinding_info_writer_.MarkFrameConstructed(__ pc_offset());
|
||||
@ -2962,7 +2955,7 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
// TODO(palfia): TF save list is not in sync with
|
||||
// CPURegList::GetCalleeSaved(): x30 is missing.
|
||||
// DCHECK(saves.list() == CPURegList::GetCalleeSaved().list());
|
||||
__ PushCPURegList(saves);
|
||||
__ PushCPURegList<TurboAssembler::kSignLR>(saves);
|
||||
|
||||
if (returns != 0) {
|
||||
__ Claim(returns);
|
||||
@ -2981,7 +2974,7 @@ void CodeGenerator::AssembleReturn(InstructionOperand* pop) {
|
||||
// Restore registers.
|
||||
CPURegList saves = CPURegList(CPURegister::kRegister, kXRegSizeInBits,
|
||||
call_descriptor->CalleeSavedRegisters());
|
||||
__ PopCPURegList(saves);
|
||||
__ PopCPURegList<TurboAssembler::kAuthLR>(saves);
|
||||
|
||||
// Restore fp registers.
|
||||
CPURegList saves_fp = CPURegList(CPURegister::kVRegister, kDRegSizeInBits,
|
||||
|
@ -9,6 +9,9 @@ namespace v8 {
|
||||
namespace internal {
|
||||
namespace compiler {
|
||||
|
||||
// TODO(v8:10026): When using CFI, we need to generate unwinding info to tell
|
||||
// the unwinder that return addresses are signed.
|
||||
|
||||
void UnwindingInfoWriter::BeginInstructionBlock(int pc_offset,
|
||||
const InstructionBlock* block) {
|
||||
if (!enabled()) return;
|
||||
|
@ -38,7 +38,7 @@ void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
|
||||
__ Ldr(x1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
|
||||
|
||||
__ Mov(sp, fp);
|
||||
__ Pop(fp, lr); // Frame, Return address.
|
||||
__ Pop<TurboAssembler::kAuthLR>(fp, lr);
|
||||
|
||||
__ LoadTaggedPointerField(
|
||||
x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
|
||||
|
@ -262,6 +262,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "src/codegen/safepoint-table.h"
|
||||
#include "src/deoptimizer/deoptimizer.h"
|
||||
#include "src/execution/frame-constants.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -288,6 +289,9 @@ void Deoptimizer::GenerateDeoptimizationEntries(MacroAssembler* masm,
|
||||
__ Ldr(continuation, MemOperand(last_output_frame,
|
||||
FrameDescription::continuation_offset()));
|
||||
__ Ldr(lr, MemOperand(last_output_frame, FrameDescription::pc_offset()));
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
__ Autiasp();
|
||||
#endif
|
||||
__ Br(continuation);
|
||||
}
|
||||
|
||||
@ -297,6 +301,14 @@ Float32 RegisterValues::GetFloatRegister(unsigned n) const {
|
||||
}
|
||||
|
||||
void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
|
||||
// TODO(v8:10026): check that the pointer is still in the list of allowed
|
||||
// builtins.
|
||||
Address new_context =
|
||||
static_cast<Address>(GetTop()) + offset + kPCOnStackSize;
|
||||
uint64_t old_context = GetTop() + GetFrameSize();
|
||||
PointerAuthentication::ReplaceContext(reinterpret_cast<Address*>(&value),
|
||||
old_context, new_context);
|
||||
|
||||
SetFrameSlot(offset, value);
|
||||
}
|
||||
|
||||
@ -309,6 +321,12 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) {
|
||||
// TODO(v8:10026): we should only accept a specific list of allowed builtins
|
||||
// here.
|
||||
pc_ = PointerAuthentication::SignPCWithSP(pc, GetTop());
|
||||
}
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "src/codegen/register-configuration.h"
|
||||
#include "src/diagnostics/disasm.h"
|
||||
#include "src/execution/frames-inl.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
#include "src/execution/v8threads.h"
|
||||
#include "src/handles/global-handles.h"
|
||||
#include "src/heap/heap-inl.h"
|
||||
@ -252,7 +253,10 @@ class ActivationsFinder : public ThreadVisitor {
|
||||
int trampoline_pc = safepoint.trampoline_pc();
|
||||
DCHECK_IMPLIES(code == topmost_, safe_to_deopt_);
|
||||
// Replace the current pc on the stack with the trampoline.
|
||||
it.frame()->set_pc(code.raw_instruction_start() + trampoline_pc);
|
||||
// TODO(v8:10026): avoid replacing a signed pointer.
|
||||
Address* pc_addr = it.frame()->pc_address();
|
||||
Address new_pc = code.raw_instruction_start() + trampoline_pc;
|
||||
PointerAuthentication::ReplacePC(pc_addr, new_pc, kSystemPointerSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -690,6 +694,13 @@ void Deoptimizer::DoComputeOutputFrames() {
|
||||
caller_fp_ = Memory<intptr_t>(fp_address);
|
||||
caller_pc_ =
|
||||
Memory<intptr_t>(fp_address + CommonFrameConstants::kCallerPCOffset);
|
||||
// Sign caller_pc_ with caller_frame_top_ to be consistent with everything
|
||||
// else here.
|
||||
uint64_t sp = stack_fp_ + StandardFrameConstants::kCallerSPOffset;
|
||||
// TODO(v8:10026): avoid replacing a signed pointer.
|
||||
PointerAuthentication::ReplaceContext(
|
||||
reinterpret_cast<Address*>(&caller_pc_), sp, caller_frame_top_);
|
||||
|
||||
input_frame_context_ = Memory<intptr_t>(
|
||||
fp_address + CommonFrameConstants::kContextOrFrameTypeOffset);
|
||||
|
||||
|
@ -712,7 +712,7 @@ class FrameDescription {
|
||||
void SetTop(intptr_t top) { top_ = top; }
|
||||
|
||||
intptr_t GetPc() const { return pc_; }
|
||||
void SetPc(intptr_t pc) { pc_ = pc; }
|
||||
void SetPc(intptr_t pc);
|
||||
|
||||
intptr_t GetFp() const { return fp_; }
|
||||
void SetFp(intptr_t fp) { fp_ = fp; }
|
||||
|
@ -217,6 +217,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -258,6 +258,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
SetFrameSlot(offset, value);
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -254,6 +254,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -221,18 +221,10 @@ Float32 RegisterValues::GetFloatRegister(unsigned n) const {
|
||||
}
|
||||
|
||||
void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) {
|
||||
if (kPCOnStackSize == 2 * kSystemPointerSize) {
|
||||
// Zero out the high-32 bit of PC for x32 port.
|
||||
SetFrameSlot(offset + kSystemPointerSize, 0);
|
||||
}
|
||||
SetFrameSlot(offset, value);
|
||||
}
|
||||
|
||||
void FrameDescription::SetCallerFp(unsigned offset, intptr_t value) {
|
||||
if (kFPOnStackSize == 2 * kSystemPointerSize) {
|
||||
// Zero out the high-32 bit of FP for x32 port.
|
||||
SetFrameSlot(offset + kSystemPointerSize, 0);
|
||||
}
|
||||
SetFrameSlot(offset, value);
|
||||
}
|
||||
|
||||
@ -241,6 +233,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
|
||||
|
||||
#undef __
|
||||
|
||||
} // namespace internal
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "include/v8.h"
|
||||
#include "src/common/globals.h"
|
||||
#include "src/execution/frame-constants.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
@ -87,8 +88,9 @@ void* GetReturnAddressFromFP(void* fp, void* pc,
|
||||
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
|
||||
}
|
||||
#endif
|
||||
return reinterpret_cast<void*>(
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
|
||||
i::Address ret_addr =
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
|
||||
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
|
||||
}
|
||||
|
||||
void* GetReturnAddressFromFP(void* fp, void* pc,
|
||||
@ -99,8 +101,9 @@ void* GetReturnAddressFromFP(void* fp, void* pc,
|
||||
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
|
||||
}
|
||||
#endif
|
||||
return reinterpret_cast<void*>(
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
|
||||
i::Address ret_addr =
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
|
||||
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
|
||||
}
|
||||
|
||||
void* GetCallerFPFromFP(void* fp, void* pc,
|
||||
|
164
src/execution/arm64/pointer-authentication-arm64.h
Normal file
164
src/execution/arm64/pointer-authentication-arm64.h
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright 2019 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
|
||||
#define V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
|
||||
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
|
||||
#include "src/common/globals.h"
|
||||
#include "src/execution/arm64/simulator-arm64.h"
|
||||
|
||||
// TODO(v8:10026): Replace hints with instruction aliases, when supported.
|
||||
#define AUTIA1716 "hint #12"
|
||||
#define PACIA1716 "hint #8"
|
||||
#define XPACLRI "hint #7"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// The following functions execute on the host and therefore need a different
|
||||
// path based on whether we are simulating arm64 or not.
|
||||
|
||||
// clang-format fails to detect this file as C++, turn it off.
|
||||
// clang-format off
|
||||
|
||||
// Authenticate the address stored in {pc_address}. {offset_from_sp} is the
|
||||
// offset between {pc_address} and the pointer used as a context for signing.
|
||||
V8_INLINE Address PointerAuthentication::AuthenticatePC(
|
||||
Address* pc_address, unsigned offset_from_sp) {
|
||||
uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
|
||||
uint64_t pc = reinterpret_cast<uint64_t>(*pc_address);
|
||||
#ifdef USE_SIMULATOR
|
||||
pc = Simulator::AuthPAC(pc, sp, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
#else
|
||||
asm volatile(
|
||||
" mov x17, %[pc]\n"
|
||||
" mov x16, %[stack_ptr]\n"
|
||||
" " AUTIA1716 "\n"
|
||||
" ldr xzr, [x17]\n"
|
||||
" mov %[pc], x17\n"
|
||||
: [pc] "+r"(pc)
|
||||
: [stack_ptr] "r"(sp)
|
||||
: "x16", "x17");
|
||||
#endif
|
||||
return pc;
|
||||
}
|
||||
|
||||
// Strip Pointer Authentication Code (PAC) from {pc} and return the raw value.
|
||||
V8_INLINE Address PointerAuthentication::StripPAC(Address pc) {
|
||||
#ifdef USE_SIMULATOR
|
||||
return Simulator::StripPAC(pc, Simulator::kInstructionPointer);
|
||||
#else
|
||||
asm volatile(
|
||||
" mov x16, lr\n"
|
||||
" mov lr, %[pc]\n"
|
||||
" " XPACLRI "\n"
|
||||
" mov %[pc], lr\n"
|
||||
" mov lr, x16\n"
|
||||
: [pc] "+r"(pc)
|
||||
:
|
||||
: "x16", "lr");
|
||||
return pc;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Sign {pc} using {sp}.
|
||||
V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
|
||||
#ifdef USE_SIMULATOR
|
||||
return Simulator::AddPAC(pc, sp, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
#else
|
||||
asm volatile(
|
||||
" mov x17, %[pc]\n"
|
||||
" mov x16, %[sp]\n"
|
||||
" " PACIA1716 "\n"
|
||||
" mov %[pc], x17\n"
|
||||
: [pc] "+r"(pc)
|
||||
: [sp] "r"(sp)
|
||||
: "x16", "x17");
|
||||
return pc;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Authenticate the address stored in {pc_address} and replace it with
|
||||
// {new_pc}, after signing it. {offset_from_sp} is the offset between
|
||||
// {pc_address} and the pointer used as a context for signing.
|
||||
V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
|
||||
Address new_pc,
|
||||
int offset_from_sp) {
|
||||
uint64_t sp = reinterpret_cast<uint64_t>(pc_address) + offset_from_sp;
|
||||
uint64_t old_pc = reinterpret_cast<uint64_t>(*pc_address);
|
||||
#ifdef USE_SIMULATOR
|
||||
uint64_t auth_old_pc = Simulator::AuthPAC(old_pc, sp, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
uint64_t raw_old_pc =
|
||||
Simulator::StripPAC(old_pc, Simulator::kInstructionPointer);
|
||||
// Verify that the old address is authenticated.
|
||||
CHECK_EQ(auth_old_pc, raw_old_pc);
|
||||
new_pc = Simulator::AddPAC(new_pc, sp, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
#else
|
||||
// Only store newly signed address after we have verified that the old
|
||||
// address is authenticated.
|
||||
asm volatile(
|
||||
" mov x17, %[new_pc]\n"
|
||||
" mov x16, %[sp]\n"
|
||||
" " PACIA1716 "\n"
|
||||
" mov %[new_pc], x17\n"
|
||||
" mov x17, %[old_pc]\n"
|
||||
" " AUTIA1716 "\n"
|
||||
" ldr xzr, [x17]\n"
|
||||
: [new_pc] "+&r"(new_pc)
|
||||
: [sp] "r"(sp), [old_pc] "r"(old_pc)
|
||||
: "x16", "x17");
|
||||
#endif
|
||||
*pc_address = new_pc;
|
||||
}
|
||||
|
||||
// Authenticate the address stored in {pc_address} based on {old_context} and
|
||||
// replace it with the same address signed with {new_context} instead.
|
||||
V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
|
||||
Address old_context,
|
||||
Address new_context) {
|
||||
uint64_t old_signed_pc = static_cast<uint64_t>(*pc_address);
|
||||
uint64_t new_pc;
|
||||
#ifdef USE_SIMULATOR
|
||||
uint64_t auth_pc =
|
||||
Simulator::AuthPAC(old_signed_pc, old_context, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
uint64_t raw_pc =
|
||||
Simulator::StripPAC(auth_pc, Simulator::kInstructionPointer);
|
||||
// Verify that the old address is authenticated.
|
||||
CHECK_EQ(raw_pc, auth_pc);
|
||||
new_pc = Simulator::AddPAC(raw_pc, new_context, Simulator::kPACKeyIA,
|
||||
Simulator::kInstructionPointer);
|
||||
#else
|
||||
// Only store newly signed address after we have verified that the old
|
||||
// address is authenticated.
|
||||
asm volatile(
|
||||
" mov x17, %[old_pc]\n"
|
||||
" mov x16, %[old_ctx]\n"
|
||||
" " AUTIA1716 "\n"
|
||||
" mov x16, %[new_ctx]\n"
|
||||
" " PACIA1716 "\n"
|
||||
" mov %[new_pc], x17\n"
|
||||
" mov x17, %[old_pc]\n"
|
||||
" mov x16, %[old_ctx]\n"
|
||||
" " AUTIA1716 "\n"
|
||||
" ldr xzr, [x17]\n"
|
||||
: [new_pc] "=&r"(new_pc)
|
||||
: [old_pc] "r"(old_signed_pc), [old_ctx] "r"(old_context),
|
||||
[new_ctx] "r"(new_context)
|
||||
: "x16", "x17");
|
||||
#endif
|
||||
*pc_address = new_pc;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
#endif // V8_EXECUTION_ARM64_POINTER_AUTHENTICATION_ARM64_H_
|
@ -9,6 +9,7 @@
|
||||
#include "src/execution/frame-constants.h"
|
||||
#include "src/execution/frames.h"
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
#include "src/objects/objects-inl.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -69,6 +70,16 @@ inline StackHandler* StackFrame::top_handler() const {
|
||||
return iterator_->handler();
|
||||
}
|
||||
|
||||
inline Address StackFrame::callee_pc() const {
|
||||
return state_.callee_pc_address ? ReadPC(state_.callee_pc_address)
|
||||
: kNullAddress;
|
||||
}
|
||||
|
||||
inline Address StackFrame::pc() const { return ReadPC(pc_address()); }
|
||||
|
||||
inline Address StackFrame::ReadPC(Address* pc_address) {
|
||||
return PointerAuthentication::AuthenticatePC(pc_address, kSystemPointerSize);
|
||||
}
|
||||
|
||||
inline Address* StackFrame::ResolveReturnAddressLocation(Address* pc_address) {
|
||||
if (return_address_location_resolver_ == nullptr) {
|
||||
|
@ -491,16 +491,18 @@ Code StackFrame::LookupCode() const {
|
||||
|
||||
void StackFrame::IteratePc(RootVisitor* v, Address* pc_address,
|
||||
Address* constant_pool_address, Code holder) {
|
||||
Address pc = *pc_address;
|
||||
Address old_pc = ReadPC(pc_address);
|
||||
DCHECK(ReadOnlyHeap::Contains(holder) ||
|
||||
holder.GetHeap()->GcSafeCodeContains(holder, pc));
|
||||
unsigned pc_offset = static_cast<unsigned>(pc - holder.InstructionStart());
|
||||
holder.GetHeap()->GcSafeCodeContains(holder, old_pc));
|
||||
unsigned pc_offset =
|
||||
static_cast<unsigned>(old_pc - holder.InstructionStart());
|
||||
Object code = holder;
|
||||
v->VisitRootPointer(Root::kTop, nullptr, FullObjectSlot(&code));
|
||||
if (code == holder) return;
|
||||
holder = Code::unchecked_cast(code);
|
||||
pc = holder.InstructionStart() + pc_offset;
|
||||
*pc_address = pc;
|
||||
Address pc = holder.InstructionStart() + pc_offset;
|
||||
// TODO(v8:10026): avoid replacing a signed pointer.
|
||||
PointerAuthentication::ReplacePC(pc_address, pc, kSystemPointerSize);
|
||||
if (FLAG_enable_embedded_constant_pool && constant_pool_address) {
|
||||
*constant_pool_address = holder.constant_pool();
|
||||
}
|
||||
@ -521,6 +523,7 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
|
||||
kSystemPointerSize);
|
||||
intptr_t marker = Memory<intptr_t>(
|
||||
state->fp + CommonFrameConstants::kContextOrFrameTypeOffset);
|
||||
Address pc = StackFrame::ReadPC(state->pc_address);
|
||||
if (!iterator->can_access_heap_objects_) {
|
||||
// TODO(titzer): "can_access_heap_objects" is kind of bogus. It really
|
||||
// means that we are being called from the profiler, which can interrupt
|
||||
@ -535,15 +538,13 @@ StackFrame::Type StackFrame::ComputeType(const StackFrameIteratorBase* iterator,
|
||||
if (!StackFrame::IsTypeMarker(marker)) {
|
||||
if (maybe_function.IsSmi()) {
|
||||
return NATIVE;
|
||||
} else if (IsInterpreterFramePc(iterator->isolate(), *(state->pc_address),
|
||||
state)) {
|
||||
} else if (IsInterpreterFramePc(iterator->isolate(), pc, state)) {
|
||||
return INTERPRETED;
|
||||
} else {
|
||||
return OPTIMIZED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Address pc = *(state->pc_address);
|
||||
// If the {pc} does not point into WebAssembly code we can rely on the
|
||||
// returned {wasm_code} to be null and fall back to {GetContainingCode}.
|
||||
wasm::WasmCodeRefScope code_ref_scope;
|
||||
|
@ -215,9 +215,7 @@ class StackFrame {
|
||||
// Accessors.
|
||||
Address sp() const { return state_.sp; }
|
||||
Address fp() const { return state_.fp; }
|
||||
Address callee_pc() const {
|
||||
return state_.callee_pc_address ? *state_.callee_pc_address : kNullAddress;
|
||||
}
|
||||
inline Address callee_pc() const;
|
||||
Address caller_sp() const { return GetCallerStackPointer(); }
|
||||
|
||||
// If this frame is optimized and was dynamically aligned return its old
|
||||
@ -225,8 +223,7 @@ class StackFrame {
|
||||
// up one word and become unaligned.
|
||||
Address UnpaddedFP() const;
|
||||
|
||||
Address pc() const { return *pc_address(); }
|
||||
void set_pc(Address pc) { *pc_address() = pc; }
|
||||
inline Address pc() const;
|
||||
|
||||
Address constant_pool() const { return *constant_pool_address(); }
|
||||
void set_constant_pool(Address constant_pool) {
|
||||
@ -265,6 +262,8 @@ class StackFrame {
|
||||
static void SetReturnAddressLocationResolver(
|
||||
ReturnAddressLocationResolver resolver);
|
||||
|
||||
static inline Address ReadPC(Address* pc_address);
|
||||
|
||||
// Resolves pc_address through the resolution address function if one is set.
|
||||
static inline Address* ResolveReturnAddressLocation(Address* pc_address);
|
||||
|
||||
|
56
src/execution/pointer-authentication-dummy.h
Normal file
56
src/execution/pointer-authentication-dummy.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
|
||||
#define V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
|
||||
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
|
||||
#include "include/v8.h"
|
||||
#include "src/base/macros.h"
|
||||
#include "src/common/globals.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// Dummy implementation of the PointerAuthentication class methods, to be used
|
||||
// when CFI is not enabled.
|
||||
|
||||
// Load return address from {pc_address} and return it.
|
||||
V8_INLINE Address PointerAuthentication::AuthenticatePC(
|
||||
Address* pc_address, unsigned offset_from_sp) {
|
||||
USE(offset_from_sp);
|
||||
return *pc_address;
|
||||
}
|
||||
|
||||
// Return {pc} unmodified.
|
||||
V8_INLINE Address PointerAuthentication::StripPAC(Address pc) { return pc; }
|
||||
|
||||
// Return {pc} unmodified.
|
||||
V8_INLINE Address PointerAuthentication::SignPCWithSP(Address pc, Address sp) {
|
||||
USE(sp);
|
||||
return pc;
|
||||
}
|
||||
|
||||
// Store {new_pc} to {pc_address} without signing.
|
||||
V8_INLINE void PointerAuthentication::ReplacePC(Address* pc_address,
|
||||
Address new_pc,
|
||||
int offset_from_sp) {
|
||||
USE(offset_from_sp);
|
||||
*pc_address = new_pc;
|
||||
}
|
||||
|
||||
// Do nothing.
|
||||
V8_INLINE void PointerAuthentication::ReplaceContext(Address* pc_address,
|
||||
Address old_context,
|
||||
Address new_context) {
|
||||
USE(pc_address);
|
||||
USE(old_context);
|
||||
USE(new_context);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_EXECUTION_POINTER_AUTHENTICATION_DUMMY_H_
|
65
src/execution/pointer-authentication.h
Normal file
65
src/execution/pointer-authentication.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2019 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_EXECUTION_POINTER_AUTHENTICATION_H_
|
||||
#define V8_EXECUTION_POINTER_AUTHENTICATION_H_
|
||||
|
||||
#include "include/v8.h"
|
||||
#include "src/base/macros.h"
|
||||
#include "src/common/globals.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class PointerAuthentication : public AllStatic {
|
||||
public:
|
||||
// When CFI is enabled, authenticate the address stored in {pc_address} and
|
||||
// return the authenticated address. {offset_from_sp} is the offset between
|
||||
// {pc_address} and the pointer used as a context for signing.
|
||||
// When CFI is not enabled, simply load return address from {pc_address} and
|
||||
// return it.
|
||||
V8_INLINE static Address AuthenticatePC(Address* pc_address,
|
||||
unsigned offset_from_sp);
|
||||
|
||||
// When CFI is enabled, strip Pointer Authentication Code (PAC) from {pc} and
|
||||
// return the raw value.
|
||||
// When CFI is not enabled, return {pc} unmodified.
|
||||
V8_INLINE static Address StripPAC(Address pc);
|
||||
|
||||
// When CFI is enabled, sign {pc} using {sp} and return the signed value.
|
||||
// When CFI is not enabled, return {pc} unmodified.
|
||||
V8_INLINE static Address SignPCWithSP(Address pc, Address sp);
|
||||
|
||||
// When CFI is enabled, authenticate the address stored in {pc_address} and
|
||||
// replace it with {new_pc}, after signing it. {offset_from_sp} is the offset
|
||||
// between {pc_address} and the pointer used as a context for signing.
|
||||
// When CFI is not enabled, store {new_pc} to {pc_address} without signing.
|
||||
V8_INLINE static void ReplacePC(Address* pc_address, Address new_pc,
|
||||
int offset_from_sp);
|
||||
|
||||
// When CFI is enabled, authenticate the address stored in {pc_address} based
|
||||
// on {old_context} and replace it with the same address signed with
|
||||
// {new_context} instead.
|
||||
// When CFI is not enabled, do nothing.
|
||||
V8_INLINE static void ReplaceContext(Address* pc_address, Address old_context,
|
||||
Address new_context);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
|
||||
#ifndef V8_TARGET_ARCH_ARM64
|
||||
#error "V8_ENABLE_CONTROL_FLOW_INTEGRITY should imply V8_TARGET_ARCH_ARM64"
|
||||
#endif
|
||||
#include "src/execution/arm64/pointer-authentication-arm64.h"
|
||||
|
||||
#else
|
||||
|
||||
#include "src/execution/pointer-authentication-dummy.h"
|
||||
|
||||
#endif
|
||||
|
||||
#endif // V8_EXECUTION_POINTER_AUTHENTICATION_H_
|
@ -740,7 +740,8 @@ Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) {
|
||||
DCHECK_EQ(11, kCalleeSaved.Count());
|
||||
registers_to_retain.Combine(lr);
|
||||
|
||||
__ PushCPURegList(registers_to_retain);
|
||||
DCHECK(registers_to_retain.IncludesAliasOf(lr));
|
||||
__ PushCPURegList<TurboAssembler::kSignLR>(registers_to_retain);
|
||||
__ PushCPURegList(argument_registers);
|
||||
|
||||
// Set frame pointer in place.
|
||||
@ -1035,7 +1036,7 @@ Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) {
|
||||
__ Mov(sp, fp);
|
||||
|
||||
// Restore registers.
|
||||
__ PopCPURegList(registers_to_retain);
|
||||
__ PopCPURegList<TurboAssembler::kAuthLR>(registers_to_retain);
|
||||
|
||||
__ Ret();
|
||||
|
||||
@ -1585,14 +1586,14 @@ void RegExpMacroAssemblerARM64::CallIf(Label* to, Condition condition) {
|
||||
|
||||
|
||||
void RegExpMacroAssemblerARM64::RestoreLinkRegister() {
|
||||
__ Pop(lr, xzr);
|
||||
__ Pop<TurboAssembler::kAuthLR>(padreg, lr);
|
||||
__ Add(lr, lr, Operand(masm_->CodeObject()));
|
||||
}
|
||||
|
||||
|
||||
void RegExpMacroAssemblerARM64::SaveLinkRegister() {
|
||||
__ Sub(lr, lr, Operand(masm_->CodeObject()));
|
||||
__ Push(xzr, lr);
|
||||
__ Push<TurboAssembler::kSignLR>(lr, padreg);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "src/codegen/assembler.h"
|
||||
#include "src/execution/isolate-inl.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
#include "src/execution/simulator.h"
|
||||
#include "src/regexp/regexp-stack.h"
|
||||
#include "src/strings/unicode-inl.h"
|
||||
@ -149,9 +150,10 @@ int NativeRegExpMacroAssembler::CheckStackGuardState(
|
||||
Address* return_address, Code re_code, Address* subject,
|
||||
const byte** input_start, const byte** input_end) {
|
||||
DisallowHeapAllocation no_gc;
|
||||
Address old_pc = PointerAuthentication::AuthenticatePC(return_address, 0);
|
||||
DCHECK_LE(re_code.raw_instruction_start(), old_pc);
|
||||
DCHECK_LE(old_pc, re_code.raw_instruction_end());
|
||||
|
||||
DCHECK(re_code.raw_instruction_start() <= *return_address);
|
||||
DCHECK(*return_address <= re_code.raw_instruction_end());
|
||||
StackLimitCheck check(isolate);
|
||||
bool js_has_overflowed = check.JsHasOverflowed();
|
||||
|
||||
@ -193,9 +195,11 @@ int NativeRegExpMacroAssembler::CheckStackGuardState(
|
||||
}
|
||||
|
||||
if (*code_handle != re_code) { // Return address no longer valid
|
||||
intptr_t delta = code_handle->address() - re_code.address();
|
||||
// Overwrite the return address on the stack.
|
||||
*return_address += delta;
|
||||
intptr_t delta = code_handle->address() - re_code.address();
|
||||
Address new_pc = old_pc + delta;
|
||||
// TODO(v8:10026): avoid replacing a signed pointer.
|
||||
PointerAuthentication::ReplacePC(return_address, new_pc, 0);
|
||||
}
|
||||
|
||||
// If we continue, we need to update the subject string addresses.
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "src/codegen/register-configuration.h"
|
||||
#include "src/debug/debug-interface.h"
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/execution/simulator.h"
|
||||
#include "src/flags/flags.h"
|
||||
#include "src/heap/factory.h"
|
||||
#include "src/init/v8.h"
|
||||
@ -735,4 +736,65 @@ class TestPlatform : public v8::Platform {
|
||||
DISALLOW_COPY_AND_ASSIGN(TestPlatform);
|
||||
};
|
||||
|
||||
#if defined(USE_SIMULATOR)
|
||||
class SimulatorHelper {
|
||||
public:
|
||||
inline bool Init(v8::Isolate* isolate) {
|
||||
simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
|
||||
->thread_local_top()
|
||||
->simulator_;
|
||||
// Check if there is active simulator.
|
||||
return simulator_ != nullptr;
|
||||
}
|
||||
|
||||
inline void FillRegisters(v8::RegisterState* state) {
|
||||
#if V8_TARGET_ARCH_ARM
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::r11));
|
||||
state->lr = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::lr));
|
||||
#elif V8_TARGET_ARCH_ARM64
|
||||
if (simulator_->sp() == 0 || simulator_->fp() == 0) {
|
||||
// It's possible that the simulator is interrupted while it is updating
|
||||
// the sp or fp register. ARM64 simulator does this in two steps:
|
||||
// first setting it to zero and then setting it to a new value.
|
||||
// Bailout if sp/fp doesn't contain the new value.
|
||||
return;
|
||||
}
|
||||
state->pc = reinterpret_cast<void*>(simulator_->pc());
|
||||
state->sp = reinterpret_cast<void*>(simulator_->sp());
|
||||
state->fp = reinterpret_cast<void*>(simulator_->fp());
|
||||
state->lr = reinterpret_cast<void*>(simulator_->lr());
|
||||
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
state->lr = reinterpret_cast<void*>(simulator_->get_lr());
|
||||
#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
state->lr = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::ra));
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
v8::internal::Simulator* simulator_;
|
||||
};
|
||||
#endif // USE_SIMULATOR
|
||||
|
||||
#endif // ifndef CCTEST_H_
|
||||
|
@ -12227,7 +12227,8 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
|
||||
case PushPopByFour:
|
||||
// Push high-numbered registers first (to the highest addresses).
|
||||
for (i = reg_count; i >= 4; i -= 4) {
|
||||
__ Push(r[i-1], r[i-2], r[i-3], r[i-4]);
|
||||
__ Push<TurboAssembler::kDontSignLR>(r[i - 1], r[i - 2], r[i - 3],
|
||||
r[i - 4]);
|
||||
}
|
||||
// Finish off the leftovers.
|
||||
switch (i) {
|
||||
@ -12240,7 +12241,7 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
|
||||
}
|
||||
break;
|
||||
case PushPopRegList:
|
||||
__ PushSizeRegList(list, reg_size);
|
||||
__ PushSizeRegList<TurboAssembler::kDontSignLR>(list, reg_size);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -12251,7 +12252,8 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
|
||||
case PushPopByFour:
|
||||
// Pop low-numbered registers first (from the lowest addresses).
|
||||
for (i = 0; i <= (reg_count-4); i += 4) {
|
||||
__ Pop(r[i], r[i+1], r[i+2], r[i+3]);
|
||||
__ Pop<TurboAssembler::kDontAuthLR>(r[i], r[i + 1], r[i + 2],
|
||||
r[i + 3]);
|
||||
}
|
||||
// Finish off the leftovers.
|
||||
switch (reg_count - i) {
|
||||
@ -12264,7 +12266,7 @@ static void PushPopSimpleHelper(int reg_count, int reg_size,
|
||||
}
|
||||
break;
|
||||
case PushPopRegList:
|
||||
__ PopSizeRegList(list, reg_size);
|
||||
__ PopSizeRegList<TurboAssembler::kDontAuthLR>(list, reg_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -12597,8 +12599,8 @@ TEST(push_pop) {
|
||||
__ PopXRegList(0);
|
||||
// Don't push/pop x18 (platform register) or xzr (for alignment)
|
||||
RegList all_regs = 0xFFFFFFFF & ~(x18.bit() | x31.bit());
|
||||
__ PushXRegList(all_regs);
|
||||
__ PopXRegList(all_regs);
|
||||
__ PushXRegList<TurboAssembler::kDontSignLR>(all_regs);
|
||||
__ PopXRegList<TurboAssembler::kDontAuthLR>(all_regs);
|
||||
__ Drop(12);
|
||||
|
||||
END();
|
||||
@ -14597,13 +14599,13 @@ TEST(near_call_no_relocation) {
|
||||
|
||||
__ Bind(&test);
|
||||
__ Mov(x0, 0x0);
|
||||
__ Push(lr, xzr);
|
||||
__ Push<TurboAssembler::kDontSignLR>(lr, xzr);
|
||||
{
|
||||
Assembler::BlockConstPoolScope scope(&masm);
|
||||
int offset = (function.pos() - __ pc_offset()) / kInstrSize;
|
||||
__ near_call(offset, RelocInfo::NONE);
|
||||
}
|
||||
__ Pop(xzr, lr);
|
||||
__ Pop<TurboAssembler::kDontAuthLR>(xzr, lr);
|
||||
END();
|
||||
|
||||
RUN();
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "include/v8.h"
|
||||
#include "src/execution/simulator.h"
|
||||
#include "src/flags/flags.h"
|
||||
#include "test/cctest/cctest.h"
|
||||
|
||||
@ -31,68 +30,6 @@ class Sample {
|
||||
};
|
||||
|
||||
|
||||
#if defined(USE_SIMULATOR)
|
||||
class SimulatorHelper {
|
||||
public:
|
||||
inline bool Init(v8::Isolate* isolate) {
|
||||
simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
|
||||
->thread_local_top()
|
||||
->simulator_;
|
||||
// Check if there is active simulator.
|
||||
return simulator_ != nullptr;
|
||||
}
|
||||
|
||||
inline void FillRegisters(v8::RegisterState* state) {
|
||||
#if V8_TARGET_ARCH_ARM
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::r11));
|
||||
state->lr = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::lr));
|
||||
#elif V8_TARGET_ARCH_ARM64
|
||||
if (simulator_->sp() == 0 || simulator_->fp() == 0) {
|
||||
// It's possible that the simulator is interrupted while it is updating
|
||||
// the sp or fp register. ARM64 simulator does this in two steps:
|
||||
// first setting it to zero and then setting it to a new value.
|
||||
// Bailout if sp/fp doesn't contain the new value.
|
||||
return;
|
||||
}
|
||||
state->pc = reinterpret_cast<void*>(simulator_->pc());
|
||||
state->sp = reinterpret_cast<void*>(simulator_->sp());
|
||||
state->fp = reinterpret_cast<void*>(simulator_->fp());
|
||||
state->lr = reinterpret_cast<void*>(simulator_->lr());
|
||||
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
state->lr = reinterpret_cast<void*>(simulator_->get_lr());
|
||||
#elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
|
||||
state->pc = reinterpret_cast<void*>(simulator_->get_pc());
|
||||
state->sp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::sp));
|
||||
state->fp = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::fp));
|
||||
state->lr = reinterpret_cast<void*>(
|
||||
simulator_->get_register(v8::internal::Simulator::ra));
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
v8::internal::Simulator* simulator_;
|
||||
};
|
||||
#endif // USE_SIMULATOR
|
||||
|
||||
|
||||
class SamplingTestHelper {
|
||||
public:
|
||||
struct CodeEventEntry {
|
||||
|
@ -591,6 +591,80 @@ TEST(PCIsInV8_LargeCodeObject_CodePagesAPI) {
|
||||
CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
|
||||
}
|
||||
|
||||
#ifdef USE_SIMULATOR
|
||||
// TODO(v8:10026): Make this also work without the simulator. The part that
|
||||
// needs modifications is getting the RegisterState.
|
||||
class UnwinderTestHelper {
|
||||
public:
|
||||
explicit UnwinderTestHelper(const std::string& test_function)
|
||||
: isolate_(CcTest::isolate()) {
|
||||
CHECK(!instance_);
|
||||
instance_ = this;
|
||||
v8::HandleScope scope(isolate_);
|
||||
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
|
||||
global->Set(v8_str("TryUnwind"),
|
||||
v8::FunctionTemplate::New(isolate_, TryUnwind));
|
||||
LocalContext env(isolate_, nullptr, global);
|
||||
CompileRun(v8_str(test_function.c_str()));
|
||||
}
|
||||
|
||||
~UnwinderTestHelper() { instance_ = nullptr; }
|
||||
|
||||
private:
|
||||
static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
instance_->DoTryUnwind();
|
||||
}
|
||||
|
||||
void DoTryUnwind() {
|
||||
// Set up RegisterState.
|
||||
v8::RegisterState register_state;
|
||||
SimulatorHelper simulator_helper;
|
||||
if (!simulator_helper.Init(isolate_)) return;
|
||||
simulator_helper.FillRegisters(®ister_state);
|
||||
// At this point, the PC will point to a Redirection object, which is not
|
||||
// in V8 as far as the unwinder is concerned. To make this work, point to
|
||||
// the return address, which is in V8, instead.
|
||||
register_state.pc = register_state.lr;
|
||||
|
||||
JSEntryStubs entry_stubs = isolate_->GetJSEntryStubs();
|
||||
MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
|
||||
size_t pages_length =
|
||||
isolate_->CopyCodePages(arraysize(code_pages), code_pages);
|
||||
CHECK_LE(pages_length, arraysize(code_pages));
|
||||
|
||||
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(
|
||||
entry_stubs, pages_length, code_pages, ®ister_state, stack_base);
|
||||
// Check that we have successfully unwound past js_entry_sp.
|
||||
CHECK(unwound);
|
||||
CHECK_GT(register_state.sp,
|
||||
reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
|
||||
static UnwinderTestHelper* instance_;
|
||||
};
|
||||
|
||||
UnwinderTestHelper* UnwinderTestHelper::instance_;
|
||||
|
||||
TEST(Unwind_TwoNestedFunctions_CodePagesAPI) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
const char* test_script =
|
||||
"function test_unwinder_api_inner() {"
|
||||
" TryUnwind();"
|
||||
" return 0;"
|
||||
"}"
|
||||
"function test_unwinder_api_outer() {"
|
||||
" return test_unwinder_api_inner();"
|
||||
"}"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_inner);"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_outer);"
|
||||
"test_unwinder_api_outer();";
|
||||
|
||||
UnwinderTestHelper helper(test_script);
|
||||
}
|
||||
#endif
|
||||
} // namespace test_unwinder_code_pages
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
#include "src/heap/spaces.h"
|
||||
#include "src/objects/code-inl.h"
|
||||
#include "test/cctest/cctest.h"
|
||||
@ -38,6 +39,11 @@ TEST(Unwind_BadState_Fail) {
|
||||
CHECK_NULL(register_state.pc);
|
||||
}
|
||||
|
||||
void StorePc(uintptr_t stack[], int index, uintptr_t pc) {
|
||||
Address sp = reinterpret_cast<Address>(&stack[index]) + kSystemPointerSize;
|
||||
stack[index] = PointerAuthentication::SignPCWithSP(pc, sp);
|
||||
}
|
||||
|
||||
TEST(Unwind_BuiltinPCInMiddle_Success) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
@ -49,7 +55,7 @@ TEST(Unwind_BuiltinPCInMiddle_Success) {
|
||||
uintptr_t stack[3];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
stack[1] = 202; // Return address into C++ code.
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
@ -93,9 +99,9 @@ TEST(Unwind_BuiltinPCAtStart_Success) {
|
||||
stack[0] = 101;
|
||||
// Return address into JS code. It doesn't matter that this is not actually in
|
||||
// JSEntry, because we only check that for the top frame.
|
||||
stack[1] = reinterpret_cast<uintptr_t>(code + 10);
|
||||
StorePc(stack, 1, reinterpret_cast<uintptr_t>(code + 10));
|
||||
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
|
||||
stack[3] = 303; // Return address into C++ code.
|
||||
StorePc(stack, 3, 303); // Return address into C++ code.
|
||||
stack[4] = 404;
|
||||
stack[5] = 505;
|
||||
|
||||
@ -145,7 +151,7 @@ TEST(Unwind_CodeObjectPCInMiddle_Success) {
|
||||
uintptr_t stack[3];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
stack[1] = 202; // Return address into C++ code.
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
@ -213,7 +219,7 @@ TEST(Unwind_JSEntryBeforeFrame_Fail) {
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = 151;
|
||||
stack[6] = 100; // Return address into C++ code.
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
@ -267,7 +273,7 @@ TEST(Unwind_OneJSFrame_Success) {
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
stack[6] = 100; // Return address into C++ code.
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
@ -311,10 +317,10 @@ TEST(Unwind_TwoJSFrames_Success) {
|
||||
stack[1] = 111;
|
||||
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
|
||||
// The fake return address is in the JS code range.
|
||||
stack[3] = reinterpret_cast<uintptr_t>(code + 10);
|
||||
StorePc(stack, 3, reinterpret_cast<uintptr_t>(code + 10));
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
stack[6] = 100; // Return address into C++ code.
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
@ -371,7 +377,7 @@ TEST(Unwind_StackBounds_Basic) {
|
||||
|
||||
uintptr_t stack[3];
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
stack[1] = 202; // Return address into C++ code.
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
@ -414,12 +420,12 @@ TEST(Unwind_StackBounds_WithUnwinding) {
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
stack[6] = reinterpret_cast<uintptr_t>(code + 20); // JS code.
|
||||
StorePc(stack, 6, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = reinterpret_cast<uintptr_t>(stack) +
|
||||
(12 * sizeof(uintptr_t)); // saved FP (OOB).
|
||||
stack[10] = reinterpret_cast<uintptr_t>(code + 20); // JS code.
|
||||
StorePc(stack, 10, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack + 5;
|
||||
@ -435,7 +441,7 @@ TEST(Unwind_StackBounds_WithUnwinding) {
|
||||
// Change the return address so that it is not in range. We will not range
|
||||
// check the stack[9] FP value because we have finished unwinding and the
|
||||
// contents of rbp does not necessarily have to be the FP in this case.
|
||||
stack[10] = 202;
|
||||
StorePc(stack, 10, 202);
|
||||
unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(unwound);
|
||||
@ -549,6 +555,76 @@ TEST(PCIsInV8_LargeCodeObject) {
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
#ifdef USE_SIMULATOR
|
||||
// TODO(v8:10026): Make this also work without the simulator. The part that
|
||||
// needs modifications is getting the RegisterState.
|
||||
class UnwinderTestHelper {
|
||||
public:
|
||||
explicit UnwinderTestHelper(const std::string& test_function)
|
||||
: isolate_(CcTest::isolate()) {
|
||||
CHECK(!instance_);
|
||||
instance_ = this;
|
||||
v8::HandleScope scope(isolate_);
|
||||
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
|
||||
global->Set(v8_str("TryUnwind"),
|
||||
v8::FunctionTemplate::New(isolate_, TryUnwind));
|
||||
LocalContext env(isolate_, nullptr, global);
|
||||
CompileRun(v8_str(test_function.c_str()));
|
||||
}
|
||||
|
||||
~UnwinderTestHelper() { instance_ = nullptr; }
|
||||
|
||||
private:
|
||||
static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
instance_->DoTryUnwind();
|
||||
}
|
||||
|
||||
void DoTryUnwind() {
|
||||
// Set up RegisterState.
|
||||
v8::RegisterState register_state;
|
||||
SimulatorHelper simulator_helper;
|
||||
if (!simulator_helper.Init(isolate_)) return;
|
||||
simulator_helper.FillRegisters(®ister_state);
|
||||
// At this point, the PC will point to a Redirection object, which is not
|
||||
// in V8 as far as the unwinder is concerned. To make this work, point to
|
||||
// the return address, which is in V8, instead.
|
||||
register_state.pc = register_state.lr;
|
||||
|
||||
UnwindState unwind_state = isolate_->GetUnwindState();
|
||||
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state,
|
||||
®ister_state, stack_base);
|
||||
// Check that we have successfully unwound past js_entry_sp.
|
||||
CHECK(unwound);
|
||||
CHECK_GT(register_state.sp,
|
||||
reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
|
||||
static UnwinderTestHelper* instance_;
|
||||
};
|
||||
|
||||
UnwinderTestHelper* UnwinderTestHelper::instance_;
|
||||
|
||||
TEST(Unwind_TwoNestedFunctions) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
const char* test_script =
|
||||
"function test_unwinder_api_inner() {"
|
||||
" TryUnwind();"
|
||||
" return 0;"
|
||||
"}"
|
||||
"function test_unwinder_api_outer() {"
|
||||
" return test_unwinder_api_inner();"
|
||||
"}"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_inner);"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_outer);"
|
||||
"test_unwinder_api_outer();";
|
||||
|
||||
UnwinderTestHelper helper(test_script);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user