switch_x86_msvc.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. * this is the internal transfer function.
  3. *
  4. * HISTORY
  5. * 24-Nov-02 Christian Tismer <tismer@tismer.com>
  6. * needed to add another magic constant to insure
  7. * that f in slp_eval_frame(PyFrameObject *f)
  8. * STACK_REFPLUS will probably be 1 in most cases.
  9. * gets included into the saved stack area.
  10. * 26-Sep-02 Christian Tismer <tismer@tismer.com>
  11. * again as a result of virtualized stack access,
  12. * the compiler used less registers. Needed to
  13. * explicit mention registers in order to get them saved.
  14. * Thanks to Jeff Senn for pointing this out and help.
  15. * 17-Sep-02 Christian Tismer <tismer@tismer.com>
  16. * after virtualizing stack save/restore, the
  17. * stack size shrunk a bit. Needed to introduce
  18. * an adjustment STACK_MAGIC per platform.
  19. * 15-Sep-02 Gerd Woetzel <gerd.woetzel@GMD.DE>
  20. * slightly changed framework for sparc
  21. * 01-Mar-02 Christian Tismer <tismer@tismer.com>
  22. * Initial final version after lots of iterations for i386.
  23. */
  24. #define alloca _alloca
  25. #define STACK_REFPLUS 1
  26. #ifdef SLP_EVAL
  27. #define STACK_MAGIC 0
  28. /* Some magic to quell warnings and keep slp_switch() from crashing when built
  29. with VC90. Disable global optimizations, and the warning: frame pointer
  30. register 'ebp' modified by inline assembly code.
  31. We used to just disable global optimizations ("g") but upstream stackless
  32. Python, as well as stackman, turn off all optimizations.
  33. References:
  34. https://github.com/stackless-dev/stackman/blob/dbc72fe5207a2055e658c819fdeab9731dee78b9/stackman/platforms/switch_x86_msvc.h
  35. https://github.com/stackless-dev/stackless/blob/main-slp/Stackless/platf/switch_x86_msvc.h
  36. */
  37. #define WIN32_LEAN_AND_MEAN
  38. #include <windows.h>
  39. #pragma optimize("", off) /* so that autos are stored on the stack */
  40. #pragma warning(disable:4731)
  41. #pragma warning(disable:4733) /* disable warning about modifying FS[0] */
  42. /**
  43. * Most modern compilers and environments handle C++ exceptions without any
  44. * special help from us. MSVC on 32-bit windows is an exception. There, C++
  45. * exceptions are dealt with using Windows' Structured Exception Handling
  46. * (SEH).
  47. *
  48. * SEH is implemented as a singly linked list of <function*, prev*> nodes. The
  49. * head of this list is stored in the Thread Information Block, which itself
  50. * is pointed to from the FS register. It's the first field in the structure,
  51. * or offset 0, so we can access it using assembly FS:[0], or the compiler
  52. * intrinsics and field offset information from the headers (as we do below).
  53. * Somewhat unusually, the tail of the list doesn't have prev == NULL, it has
  54. * prev == 0xFFFFFFFF.
  55. *
  56. * SEH was designed for C, and traditionally uses the MSVC compiler
  57. * intrinsincs __try{}/__except{}. It is also utilized for C++ exceptions by
  58. * MSVC; there, every throw of a C++ exception raises a SEH error with the
  59. * ExceptionCode 0xE06D7363; the SEH handler list is then traversed to
  60. * deal with the exception.
  61. *
  62. * If the SEH list is corrupt, then when a C++ exception is thrown the program
  63. * will abruptly exit with exit code 1. This does not use std::terminate(), so
  64. * std::set_terminate() is useless to debug this.
  65. *
  66. * The SEH list is closely tied to the call stack; entering a function that
  67. * uses __try{} or most C++ functions will push a new handler onto the front
  68. * of the list. Returning from the function will remove the handler. Saving
  69. * and restoring the head node of the SEH list (FS:[0]) per-greenlet is NOT
  70. * ENOUGH to make SEH or exceptions work.
  71. *
  72. * Stack switching breaks SEH because the call stack no longer necessarily
  73. * matches the SEH list. For example, given greenlet A that switches to
  74. * greenlet B, at the moment of entering greenlet B, we will have any SEH
  75. * handlers from greenlet A on the SEH list; greenlet B can then add its own
  76. * handlers to the SEH list. When greenlet B switches back to greenlet A,
  77. * greenlet B's handlers would still be on the SEH stack, but when switch()
  78. * returns control to greenlet A, we have replaced the contents of the stack
  79. * in memory, so all the address that greenlet B added to the SEH list are now
  80. * invalid: part of the call stack has been unwound, but the SEH list was out
  81. * of sync with the call stack. The net effect is that exception handling
  82. * stops working.
  83. *
  84. * Thus, when switching greenlets, we need to be sure that the SEH list
  85. * matches the effective call stack, "cutting out" any handlers that were
  86. * pushed by the greenlet that switched out and which are no longer valid.
  87. *
  88. * The easiest way to do this is to capture the SEH list at the time the main
  89. * greenlet for a thread is created, and, when initially starting a greenlet,
  90. * start a new SEH list for it, which contains nothing but the handler
  91. * established for the new greenlet itself, with the tail being the handlers
  92. * for the main greenlet. If we then save and restore the SEH per-greenlet,
  93. * they won't interfere with each others SEH lists. (No greenlet can unwind
  94. * the call stack past the handlers established by the main greenlet).
  95. *
  96. * By observation, a new thread starts with three SEH handlers on the list. By
  97. * the time we get around to creating the main greenlet, though, there can be
  98. * many more, established by transient calls that lead to the creation of the
  99. * main greenlet. Therefore, 3 is a magic constant telling us when to perform
  100. * the initial slice.
  101. *
  102. * All of this can be debugged using a vectored exception handler, which
  103. * operates independently of the SEH handler list, and is called first.
  104. * Walking the SEH list at key points can also be helpful.
  105. *
  106. * References:
  107. * https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
  108. * https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273
  109. * https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=msvc-160
  110. * https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160
  111. * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
  112. * https://docs.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler
  113. * https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm
  114. */
  115. #define GREENLET_NEEDS_EXCEPTION_STATE_SAVED
  116. typedef struct _GExceptionRegistration {
  117. struct _GExceptionRegistration* prev;
  118. void* handler_f;
  119. } GExceptionRegistration;
  120. static void
  121. slp_set_exception_state(const void *const seh_state)
  122. {
  123. // Because the stack from from which we do this is ALSO a handler, and
  124. // that one we want to keep, we need to relink the current SEH handler
  125. // frame to point to this one, cutting out the middle men, as it were.
  126. //
  127. // Entering a try block doesn't change the SEH frame, but entering a
  128. // function containing a try block does.
  129. GExceptionRegistration* current_seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
  130. current_seh_state->prev = (GExceptionRegistration*)seh_state;
  131. }
  132. static GExceptionRegistration*
  133. x86_slp_get_third_oldest_handler()
  134. {
  135. GExceptionRegistration* a = NULL; /* Closest to the top */
  136. GExceptionRegistration* b = NULL; /* second */
  137. GExceptionRegistration* c = NULL;
  138. GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
  139. a = b = c = seh_state;
  140. while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) {
  141. if ((void*)seh_state->prev < (void*)100) {
  142. fprintf(stderr, "\tERROR: Broken SEH chain.\n");
  143. return NULL;
  144. }
  145. a = b;
  146. b = c;
  147. c = seh_state;
  148. seh_state = seh_state->prev;
  149. }
  150. return a ? a : (b ? b : c);
  151. }
  152. static void*
  153. slp_get_exception_state()
  154. {
  155. // XXX: There appear to be three SEH handlers on the stack already at the
  156. // start of the thread. Is that a guarantee? Almost certainly not. Yet in
  157. // all observed cases it has been three. This is consistent with
  158. // faulthandler off or on, and optimizations off or on. It may not be
  159. // consistent with other operating system versions, though: we only have
  160. // CI on one or two versions (don't ask what there are).
  161. // In theory we could capture the number of handlers on the chain when
  162. // PyInit__greenlet is called: there are probably only the default
  163. // handlers at that point (unless we're embedded and people have used
  164. // __try/__except or a C++ handler)?
  165. return x86_slp_get_third_oldest_handler();
  166. }
  167. static int
  168. slp_switch(void)
  169. {
  170. /* MASM syntax is typically reversed from other assemblers.
  171. It is usually <instruction> <destination> <source>
  172. */
  173. int *stackref, stsizediff;
  174. /* store the structured exception state for this stack */
  175. DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
  176. __asm mov stackref, esp;
  177. /* modify EBX, ESI and EDI in order to get them preserved */
  178. __asm mov ebx, ebx;
  179. __asm xchg esi, edi;
  180. {
  181. SLP_SAVE_STATE(stackref, stsizediff);
  182. __asm {
  183. mov eax, stsizediff
  184. add esp, eax
  185. add ebp, eax
  186. }
  187. SLP_RESTORE_STATE();
  188. }
  189. __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state);
  190. return 0;
  191. }
  192. /* re-enable ebp warning and global optimizations. */
  193. #pragma optimize("", on)
  194. #pragma warning(default:4731)
  195. #pragma warning(default:4733) /* disable warning about modifying FS[0] */
  196. #endif
  197. /*
  198. * further self-processing support
  199. */
  200. /* we have IsBadReadPtr available, so we can peek at objects */
  201. #define STACKLESS_SPY
  202. #ifdef GREENLET_DEBUG
  203. #define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes)
  204. static int IS_ON_STACK(void*p)
  205. {
  206. int stackref;
  207. int stackbase = ((int)&stackref) & 0xfffff000;
  208. return (int)p >= stackbase && (int)p < stackbase + 0x00100000;
  209. }
  210. static void
  211. x86_slp_show_seh_chain()
  212. {
  213. GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList));
  214. fprintf(stderr, "====== SEH Chain ======\n");
  215. while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) {
  216. fprintf(stderr, "\tSEH_chain addr: %p handler: %p prev: %p\n",
  217. seh_state,
  218. seh_state->handler_f, seh_state->prev);
  219. if ((void*)seh_state->prev < (void*)100) {
  220. fprintf(stderr, "\tERROR: Broken chain.\n");
  221. break;
  222. }
  223. seh_state = seh_state->prev;
  224. }
  225. fprintf(stderr, "====== End SEH Chain ======\n");
  226. fflush(NULL);
  227. return;
  228. }
  229. //addVectoredExceptionHandler constants:
  230. //CALL_FIRST means call this exception handler first;
  231. //CALL_LAST means call this exception handler last
  232. #define CALL_FIRST 1
  233. #define CALL_LAST 0
  234. LONG WINAPI
  235. GreenletVectorHandler(PEXCEPTION_POINTERS ExceptionInfo)
  236. {
  237. // We get one of these for every C++ exception, with code
  238. // E06D7363
  239. // This is a special value that means "C++ exception from MSVC"
  240. // https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273
  241. //
  242. // Install in the module init function with:
  243. // AddVectoredExceptionHandler(CALL_FIRST, GreenletVectorHandler);
  244. PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord;
  245. fprintf(stderr,
  246. "GOT VECTORED EXCEPTION:\n"
  247. "\tExceptionCode : %p\n"
  248. "\tExceptionFlags : %p\n"
  249. "\tExceptionAddr : %p\n"
  250. "\tNumberparams : %ld\n",
  251. ExceptionRecord->ExceptionCode,
  252. ExceptionRecord->ExceptionFlags,
  253. ExceptionRecord->ExceptionAddress,
  254. ExceptionRecord->NumberParameters
  255. );
  256. if (ExceptionRecord->ExceptionFlags & 1) {
  257. fprintf(stderr, "\t\tEH_NONCONTINUABLE\n" );
  258. }
  259. if (ExceptionRecord->ExceptionFlags & 2) {
  260. fprintf(stderr, "\t\tEH_UNWINDING\n" );
  261. }
  262. if (ExceptionRecord->ExceptionFlags & 4) {
  263. fprintf(stderr, "\t\tEH_EXIT_UNWIND\n" );
  264. }
  265. if (ExceptionRecord->ExceptionFlags & 8) {
  266. fprintf(stderr, "\t\tEH_STACK_INVALID\n" );
  267. }
  268. if (ExceptionRecord->ExceptionFlags & 0x10) {
  269. fprintf(stderr, "\t\tEH_NESTED_CALL\n" );
  270. }
  271. if (ExceptionRecord->ExceptionFlags & 0x20) {
  272. fprintf(stderr, "\t\tEH_TARGET_UNWIND\n" );
  273. }
  274. if (ExceptionRecord->ExceptionFlags & 0x40) {
  275. fprintf(stderr, "\t\tEH_COLLIDED_UNWIND\n" );
  276. }
  277. fprintf(stderr, "\n");
  278. fflush(NULL);
  279. for(DWORD i = 0; i < ExceptionRecord->NumberParameters; i++) {
  280. fprintf(stderr, "\t\t\tParam %ld: %lX\n", i, ExceptionRecord->ExceptionInformation[i]);
  281. }
  282. if (ExceptionRecord->NumberParameters == 3) {
  283. fprintf(stderr, "\tAbout to traverse SEH chain\n");
  284. // C++ Exception records have 3 params.
  285. x86_slp_show_seh_chain();
  286. }
  287. return EXCEPTION_CONTINUE_SEARCH;
  288. }
  289. #endif