Skip to content

Commit c108631

Browse files
mlippautzCommit Bot
authored andcommitted
cppgc: Conservative stack scanning
Adds support for conservative stack scanning on x64. The trampolines saving callee-saved registers are compiled using clang (non-Windows) and MASM (Windows). This is using the default toolchain for assembly in Chromium/V8. This differs from Oilpan in Chromium where x86 and x64 are compiled using NASM [1]. V8 does not yet require this dependency and building the trampolines natively avoids it. (NASM also requires separate blocks for x64 Windows and non-Windows.) On non-x86/x64 platforms Chromium also uses clang, so there's little benefit in keeping the dependency. The trampolines are tested when building with clang. Other platforms follow in separate CLs. [1] https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/asm/SaveRegisters_x86.asm Change-Id: Ice2e23e44391aa94147abe75ee0b5afac458b8f8 Bug: chromium:1056170 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2124319 Commit-Queue: Michael Lippautz <[email protected]> Reviewed-by: Omer Katz <[email protected]> Reviewed-by: Hannes Payer <[email protected]> Reviewed-by: Ulan Degenbaev <[email protected]> Cr-Commit-Position: refs/heads/master@{#66913}
1 parent 35c21ba commit c108631

8 files changed

Lines changed: 560 additions & 0 deletions

File tree

BUILD.gn

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,8 +3946,19 @@ v8_source_set("cppgc_base") {
39463946
"src/heap/cppgc/heap.cc",
39473947
"src/heap/cppgc/heap.h",
39483948
"src/heap/cppgc/platform.cc",
3949+
"src/heap/cppgc/sanitizers.h",
3950+
"src/heap/cppgc/stack.cc",
3951+
"src/heap/cppgc/stack.h",
39493952
]
39503953

3954+
if (target_cpu == "x64") {
3955+
if (is_win) {
3956+
sources += [ "src/heap/cppgc/asm/x64/push_registers_win.S" ]
3957+
} else {
3958+
sources += [ "src/heap/cppgc/asm/x64/push_registers.S" ]
3959+
}
3960+
}
3961+
39513962
configs = [ ":internal_config" ]
39523963

39533964
public_deps = [ ":v8_libbase" ]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2020 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
.att_syntax
6+
7+
.text
8+
9+
#ifdef V8_TARGET_OS_MACOSX
10+
11+
.globl _PushAllRegistersAndIterateStack
12+
_PushAllRegistersAndIterateStack:
13+
14+
#else // !V8_TARGET_OS_MACOSX
15+
16+
.type PushAllRegistersAndIterateStack, %function
17+
.global PushAllRegistersAndIterateStack
18+
.hidden PushAllRegistersAndIterateStack
19+
PushAllRegistersAndIterateStack:
20+
21+
#endif // !V8_TARGET_OS_MACOSX
22+
23+
// Push all callee-saved registers to get them on the stack for conservative
24+
// stack scanning.
25+
//
26+
// We maintain 16-byte alignment at calls. There is an 8-byte return address
27+
// on the stack and we push 56 bytes which maintains 16-byte stack alignment
28+
// at the call.
29+
// Source: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
30+
push $0xCDCDCD // Dummy for alignment.
31+
push %rbx
32+
push %rbp
33+
push %r12
34+
push %r13
35+
push %r14
36+
push %r15
37+
// Pass 1st parameter (rdi) unchanged (this).
38+
// Pass 2nd parameter (rsi) unchanged (StackVisitor*).
39+
// Save 3rd parameter (rdx; callback)
40+
mov %rdx, %r8
41+
// Pass 3rd parameter as rsp (stack pointer).
42+
mov %rsp, %rdx
43+
// Call the callback.
44+
call *%r8
45+
// Pop the callee-saved registers. None of them were modified so no
46+
// restoring is needed.
47+
add $56, %rsp
48+
ret
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
;; Copyright 2020 the V8 project authors. All rights reserved.
2+
;; Use of this source code is governed by a BSD-style license that can be
3+
;; found in the LICENSE file.
4+
5+
;; MASM syntax
6+
;; https://docs.microsoft.com/en-us/cpp/assembler/masm/microsoft-macro-assembler-reference?view=vs-2019
7+
8+
public PushAllRegistersAndIterateStack
9+
10+
.code
11+
PushAllRegistersAndIterateStack:
12+
;; Push all callee-saved registers to get them on the stack for conservative
13+
;; stack scanning.
14+
;;
15+
;; We maintain 16-byte alignment at calls. There is an 8-byte return address
16+
;; on the stack and we push 72 bytes which maintains 16-byte stack alignment
17+
;; at the call.
18+
;; Source: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
19+
push 0CDCDCDh ;; Dummy for alignment.
20+
push rsi
21+
push rdi
22+
push rbx
23+
push rbp
24+
push r12
25+
push r13
26+
push r14
27+
push r15
28+
;; Pass 1st parameter (rcx) unchanged (this).
29+
;; Pass 2nd parameter (rdx) unchanged (StackVisitor*).
30+
;; Save 3rd parameter (r8; callback)
31+
mov r9, r8
32+
;; Pass 3rd parameter as rsp (stack pointer).
33+
mov r8, rsp
34+
;; Call the callback.
35+
call r9
36+
;; Pop the callee-saved registers. None of them were modified so no
37+
;; restoring is needed.
38+
add rsp, 72
39+
ret
40+
41+
end

src/heap/cppgc/sanitizers.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2020 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef V8_HEAP_CPPGC_SANITIZERS_H_
6+
#define V8_HEAP_CPPGC_SANITIZERS_H_
7+
8+
#include "src/base/macros.h"
9+
10+
//
11+
// TODO(chromium:1056170): Find a place in base for sanitizer support.
12+
//
13+
14+
#ifdef V8_USE_ADDRESS_SANITIZER
15+
16+
#include <sanitizer/asan_interface.h>
17+
18+
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
19+
20+
#else // !V8_USE_ADDRESS_SANITIZER
21+
22+
#define NO_SANITIZE_ADDRESS
23+
24+
#endif // V8_USE_ADDRESS_SANITIZER
25+
26+
#ifdef V8_USE_MEMORY_SANITIZER
27+
28+
#include <sanitizer/msan_interface.h>
29+
30+
#define MSAN_UNPOISON(addr, size) __msan_unpoison(addr, size)
31+
32+
#else // !V8_USE_MEMORY_SANITIZER
33+
34+
#define MSAN_UNPOISON(addr, size) ((void)(addr), (void)(size))
35+
36+
#endif // V8_USE_MEMORY_SANITIZER
37+
38+
#endif // V8_HEAP_CPPGC_SANITIZERS_H_

src/heap/cppgc/stack.cc

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2020 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "src/heap/cppgc/stack.h"
6+
7+
#include <limits>
8+
9+
#include "src/base/platform/platform.h"
10+
#include "src/heap/cppgc/globals.h"
11+
#include "src/heap/cppgc/sanitizers.h"
12+
13+
namespace cppgc {
14+
namespace internal {
15+
16+
using IterateStackCallback = void (Stack::*)(StackVisitor*, intptr_t*) const;
17+
extern "C" void PushAllRegistersAndIterateStack(const Stack*, StackVisitor*,
18+
IterateStackCallback);
19+
20+
Stack::Stack(const void* stack_start) : stack_start_(stack_start) {}
21+
22+
bool Stack::IsOnStack(void* slot) const {
23+
void* raw_slot = v8::base::Stack::GetStackSlot(slot);
24+
return v8::base::Stack::GetCurrentStackPosition() <= raw_slot &&
25+
raw_slot <= stack_start_;
26+
}
27+
28+
namespace {
29+
30+
#ifdef V8_USE_ADDRESS_SANITIZER
31+
32+
// No ASAN support as accessing fake frames otherwise results in
33+
// "stack-use-after-scope" warnings.
34+
NO_SANITIZE_ADDRESS
35+
void IterateAsanFakeFrameIfNecessary(StackVisitor* visitor,
36+
void* asan_fake_stack,
37+
const void* stack_start,
38+
const void* stack_end, void* address) {
39+
// When using ASAN fake stack a pointer to the fake frame is kept on the
40+
// native frame. In case |addr| points to a fake frame of the current stack
41+
// iterate the fake frame. Frame layout see
42+
// https://github.com/google/sanitizers/wiki/AddressSanitizerUseAfterReturn
43+
if (asan_fake_stack) {
44+
void* fake_frame_begin;
45+
void* fake_frame_end;
46+
void* real_stack_frame = __asan_addr_is_in_fake_stack(
47+
asan_fake_stack, address, &fake_frame_begin, &fake_frame_end);
48+
if (real_stack_frame) {
49+
// |address| points to a fake frame. Check that the fake frame is part
50+
// of this stack.
51+
if (stack_start >= real_stack_frame && real_stack_frame >= stack_end) {
52+
// Iterate the fake frame.
53+
for (void** current = reinterpret_cast<void**>(fake_frame_begin);
54+
current < fake_frame_end; ++current) {
55+
void* addr = *current;
56+
if (addr == nullptr) continue;
57+
visitor->VisitPointer(addr);
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
#endif // V8_USE_ADDRESS_SANITIZER
65+
66+
#ifdef V8_TARGET_ARCH_X64
67+
68+
void IterateSafeStackIfNecessary(StackVisitor* visitor) {
69+
#if defined(__has_feature)
70+
#if __has_feature(safe_stack)
71+
// Source:
72+
// https://github.com/llvm/llvm-project/blob/master/compiler-rt/lib/safestack/safestack.cpp
73+
constexpr size_t kSafeStackAlignmentBytes = 16;
74+
void* stack_end = __builtin___get_unsafe_stack_ptr();
75+
void* stack_start = __builtin___get_unsafe_stack_top();
76+
CHECK_GT(stack_start, stack_end);
77+
CHECK_EQ(0u, reinterpret_cast<uintptr_t>(stack_end) &
78+
(kSafeStackAlignmentBytes - 1));
79+
CHECK_EQ(0u, reinterpret_cast<uintptr_t>(stack_start) &
80+
(kSafeStackAlignmentBytes - 1));
81+
void** current = reinterpret_cast<void**>(stack_end);
82+
for (; current < stack_start; ++current) {
83+
void* address = *current;
84+
if (address == nullptr) continue;
85+
visitor->VisitPointer(address);
86+
}
87+
#endif // __has_feature(safe_stack)
88+
#endif // defined(__has_feature)
89+
}
90+
91+
#endif // V8_TARGET_ARCH_X64
92+
93+
} // namespace
94+
95+
#ifdef V8_TARGET_ARCH_X64
96+
void Stack::IteratePointers(StackVisitor* visitor) const {
97+
PushAllRegistersAndIterateStack(this, visitor, &Stack::IteratePointersImpl);
98+
// No need to deal with callee-saved registers as they will be kept alive by
99+
// the regular conservative stack iteration.
100+
IterateSafeStackIfNecessary(visitor);
101+
}
102+
#endif // V8_TARGET_ARCH_X64
103+
104+
// No ASAN support as method accesses redzones while walking the stack.
105+
NO_SANITIZE_ADDRESS
106+
void Stack::IteratePointersImpl(StackVisitor* visitor,
107+
intptr_t* stack_end) const {
108+
#ifdef V8_USE_ADDRESS_SANITIZER
109+
void* asan_fake_stack = __asan_get_current_fake_stack();
110+
#endif // V8_USE_ADDRESS_SANITIZER
111+
// All supported platforms should have their stack aligned to at least
112+
// sizeof(void*).
113+
constexpr size_t kMinStackAlignment = sizeof(void*);
114+
// Redzone should not contain any pointers as the iteration is always called
115+
// from the assembly trampoline. If inline assembly is ever inlined through
116+
// LTO this may become necessary.
117+
constexpr size_t kRedZoneBytes = 128;
118+
void** current = reinterpret_cast<void**>(
119+
reinterpret_cast<uintptr_t>(stack_end - kRedZoneBytes));
120+
CHECK_EQ(0u, reinterpret_cast<uintptr_t>(current) & (kMinStackAlignment - 1));
121+
for (; current < stack_start_; ++current) {
122+
// MSAN: Instead of unpoisoning the whole stack, the slot's value is copied
123+
// into a local which is unpoisoned.
124+
void* address = *current;
125+
MSAN_UNPOISON(address, sizeof(address));
126+
if (address == nullptr) continue;
127+
visitor->VisitPointer(address);
128+
#ifdef V8_USE_ADDRESS_SANITIZER
129+
IterateAsanFakeFrameIfNecessary(visitor, asan_fake_stack, stack_start_,
130+
stack_end, address);
131+
#endif // V8_USE_ADDRESS_SANITIZER
132+
}
133+
}
134+
135+
} // namespace internal
136+
} // namespace cppgc

src/heap/cppgc/stack.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2020 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef V8_HEAP_CPPGC_STACK_H_
6+
#define V8_HEAP_CPPGC_STACK_H_
7+
8+
#include "src/base/macros.h"
9+
10+
namespace cppgc {
11+
namespace internal {
12+
13+
class StackVisitor {
14+
public:
15+
virtual void VisitPointer(const void* address) = 0;
16+
};
17+
18+
// Abstraction over the stack. Supports handling of:
19+
// - native stack;
20+
// - ASAN/MSAN;
21+
// - SafeStack: https://releases.llvm.org/10.0.0/tools/clang/docs/SafeStack.html
22+
class V8_EXPORT_PRIVATE Stack final {
23+
public:
24+
explicit Stack(const void* stack_start);
25+
26+
// Returns true if |slot| is part of the stack and false otherwise.
27+
bool IsOnStack(void* slot) const;
28+
29+
// Word-aligned iteration of the stack. Slot values are passed on to
30+
// |visitor|.
31+
//
32+
// TODO(chromium:1056170): Implement all platforms.
33+
#ifdef V8_TARGET_ARCH_X64
34+
void IteratePointers(StackVisitor* visitor) const;
35+
#endif // V8_TARGET_ARCH_X64
36+
37+
private:
38+
void IteratePointersImpl(StackVisitor* visitor, intptr_t* stack_end) const;
39+
40+
const void* stack_start_;
41+
};
42+
43+
} // namespace internal
44+
} // namespace cppgc
45+
46+
#endif // V8_HEAP_CPPGC_STACK_H_

test/unittests/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ v8_source_set("cppgc_unittests_sources") {
4949
"heap/cppgc/garbage-collected_unittest.cc",
5050
"heap/cppgc/gc-info_unittest.cc",
5151
"heap/cppgc/heap-object-header_unittest.cc",
52+
"heap/cppgc/stack_unittest.cc",
5253
"heap/cppgc/tests.cc",
5354
"heap/cppgc/tests.h",
5455
]

0 commit comments

Comments
 (0)