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:
Georgia Kouveli 2020-02-12 11:45:31 +00:00 committed by Commit Bot
parent a046c5fcf9
commit 73f88b5f69
32 changed files with 972 additions and 307 deletions

View File

@ -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",

View File

@ -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();
}

View File

@ -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();

View File

@ -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

View File

@ -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 ------------------------------------------------------------------

View File

@ -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,

View File

@ -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;

View File

@ -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));

View File

@ -262,6 +262,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal

View File

@ -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

View File

@ -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);

View File

@ -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; }

View File

@ -217,6 +217,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal

View File

@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal

View File

@ -236,6 +236,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal

View File

@ -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

View File

@ -254,6 +254,8 @@ void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) {
UNREACHABLE();
}
void FrameDescription::SetPc(intptr_t pc) { pc_ = pc; }
#undef __
} // namespace internal

View File

@ -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

View File

@ -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,

View 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_

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View 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_

View 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_

View File

@ -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);
}

View File

@ -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.

View File

@ -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_

View File

@ -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();

View File

@ -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 {

View File

@ -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(&register_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, &register_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

View File

@ -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, &register_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(&register_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,
&register_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