1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
#pragma once
#include "Utils/Platform.h"
#include "Core/GcType.h"
#include "OS/UThread.h"
#if !defined(X86) && !defined(X64) && !defined(ARM64)
#error "Stack scanning for machines other than X86, X86-64 and ARM64 is not implemented yet."
#endif
namespace storm {
/**
* Options to control scanning.
*/
enum ScanOption {
// Scan nothing.
scanNone,
// Scan only the header (if one exists).
scanHeader,
// Scan all pointers.
scanAll,
};
/**
* Generic implementation of stack- and object scanning.
*
* The stack scanning can be used regardless of what object format is used, but the
* heap-scanning functions assume that the object format implemented in "Format.h" is used.
*
* How objects are scanned are described by the template parameter 'Scanner'. See the
* implementation of 'NullScanner' below to see what is required by a scanner implementation.
*
* TODO: We could skip return values and use exceptions exclusively, and use exceptions instead,
* as suggested by MPS.
*/
template <class Scanner>
struct Scan {
private:
typedef typename Scanner::Result Result;
typedef typename Scanner::Source Source;
// Helper functions. The public interface is below.
static inline Result fix12(Scanner &s, void **ptr) {
if (s.fix1(*ptr))
return s.fix2(ptr);
return Result();
}
// Same as 'fix12', but for stack addresses that may contain authenticated pointers to
// binary objects.
#ifndef ARM_USE_PAC
static inline Result fixStack12(Scanner &s, void **ptr) {
return fix12(s, ptr);
}
#else
static inline Result fixStack12(Scanner &s, void **ptr) {
void *decoded = *ptr;
__asm__(
"mov x30, %0\n"
"xpaclri\n"
"mov %0, x30\n"
: "+r" (decoded) : : "r30"
);
// We never have to write back these.
if (decoded != *ptr)
ptr = &decoded;
return fix12(s, ptr);
}
#endif
public:
// Shorthand for stacks.
typedef os::InlineSet<os::Stack> StackSet;
// Scan an array of pointers.
static Result array(Source &source, void *base, size_t count) {
void **from = (void **)base;
Scanner s(source);
for (size_t i = 0; i < count; i++) {
Result r = fix12(s, from + i);
if (r != Result())
return r;
}
return Result();
}
// Scan an array of pointers. Also supports scanning a part of a stack where the full extent
// of the stack is known.
static Result array(Source &source, void *base, void *limit) {
void **from = (void **)base;
void **to = (void **)limit;
Scanner s(source);
for (; from != to; from++) {
Result r = fix12(s, from);
if (r != Result())
return r;
}
return Result();
}
// Scan all UThreads running on a specific thread. 'stacks' is the set of all stacks that
// are tied to this particular thread. Out of those threads, one may be scheduled
// currently. The extent of this thread is specified by 'current' (the lower end, on X86).
// Additionally, returns the number of bytes scanned, which may be interesting for some GC
// implementations.
// If 'current' is null, the currently running thread is not scanned.
static Result stacks(Source &source, const StackSet &stacks, void *current, size_t *scanned) {
size_t bytesScanned = 0;
// We scan all UThreads on this thread, if one of them is the currently running thread
// their 'desc' is null. In that case we delay scanning that thread until after the
// other threads. If we do not find an 'active' thread, we're in the middle of a thread
// switch, which means that we can scan all threads as if they were sleeping.
// Aside from that, we need to be aware that UThreads may be executed by another thread
// during detours. The UThreads will always be located inside the stacks set on the
// thread where they are intended to run. They can not be moved around while they
// contain anything useful since it is not possible to move the UThreads between sets
// atomically. Instead, the Stack objects participating in a thread switch are
// marked with a 'detourActive' != 0. Any such UThread shall be ignored during stack
// scanning since they are considered to belong to another thread. The UThread is
// instead associated with the proper thread by following the pointer inside the
// 'detourTo' member.
// Note: This code currently contains bits that are specific to X86 CPUs, e.g. that the
// stack grows towards lower addresses.
// Remember we did not find a running stack.
void **to = null;
Scanner s(source);
StackSet::iterator end = stacks.end();
for (StackSet::iterator i = stacks.begin(); i != end; ++i) {
// If this thread is used as a detour thread, do not scan it at all.
const os::Stack *first = *i;
if (first->detourActive)
continue;
// Examine the main stack and all detours for this thread.
for (const os::Stack *stack = first; stack; stack = stack->detourTo) {
// Is this the currently scheduled UThread for this thread?
if (!stack->desc) {
// We should not find two of these for any given thread.
dbg_assert(to == null, L"We found two potential main stacks.");
// This is the main stack. Scan it later!
to = (void **)stack->high();
// Do a quick sanity check. If the stack is allocated, then we don't want to
// scan it if "current" is outside the range. This can happen when an
// exception is thrown though a "stackCall" mechanism.
if (stack->low() != stack->high()) {
// If it is outside, then we don't scan it now. We're in the process of
// destroying the stack.
if (size_t(current) <= size_t(stack->low()) ||
size_t(current) > size_t(stack->high()))
to = null;
}
continue;
}
// All is well. Commence scanning!
void **low = (void **)stack->desc->low;
void **high = (void **)stack->high();
bytesScanned += (char *)high - (char *)low;
for (; low < high; low++) {
Result r = fixStack12(s, low);
if (r != Result())
return r;
}
}
}
// If we are right in the middle of a thread switch, we will fail to find a main
// stack. This means we have already scanned all stacks, and thus we do not need to do
// anything more.
if (to == null) {
#ifdef DEBUG
// To see if this ever happens, and if it is handled correctly. This is *really*
// rare, as we have to hit a window of ~6 machine instructions when pausing another
// thread to see this.
PLN(L"------------ We found an UThread in the process of switching! ------------");
#endif
} else if (current) {
bytesScanned += (char *)to - (char *)current;
for (void **at = (void **)current; at < to; at++) {
Result r = fixStack12(s, at);
if (r != Result())
return r;
}
}
if (scanned)
*scanned = bytesScanned;
return Result();
}
};
/**
* Simple implementation of a scanner for illustration purposes. Contains all members required
* by a full scanner.
*
* This class is instantiated on the stack inside the scanning function, in order to provide a
* place where implementations may place variables that benefit from being inlined by the
* compiler, and hopefully placed in registers.
*/
struct NullScanner {
// Describes the type of results returned from the 'fix' functions, and subsequently also by
// the scanning functions. The value Result() is considered 'success'.
typedef int Result;
// Where we get the scanning information from initially. This class/struct need to be
// constructible from an instance of this type.
typedef void *Source;
// Constructor from 'Source'.
NullScanner(Source &source) {}
// Called before each object is scanned when scanning formatted objects (not called when
// scanning a bunch of raw pointers, such as when scanning the stack). Determines if a
// particular object should be scanned, and to what degree it should be scanned (eg. not at
// all, only the header, or everything).
inline ScanOption object(void *start, void *end) { return scanAll; }
// Called once for each reference. Assumed to be a quick "early-out" check that the GC can
// use to quickly discard references that are not interesting at the moment. We also assume
// that this check can be called for internal pointers to objects without harm. Returns
// 'true' if 'fix2' need to be called.
inline bool fix1(void *ptr) { return false; }
// Called whenever a reference passes the check in 'fix1'. If required by the GC, only base
// pointers to objects may be passed to this function. Note that the pointer may be altered
// by this function in case the object moved.
inline Result fix2(void **ptr) { return Result(); }
// Called by the object scanning in "Format.h" for each object header, in case those need
// special treatment. Works like 'fix1' and 'fix2', except that both steps require proper
// base pointers, as indicated by the type.
inline bool fixHeader1(GcType *header) { return false; }
inline Result fixHeader2(GcType **header) { return Result(); }
};
}
|