Add Win64 SEH unwind codes for the ABI test trampoline.

This is all manual right now. Once we've added SEH tests, we can add support
for emitting these in x86_64-xlate.pl, probably based on MASM and Yasm's unwind
directives, and unify with CFI. (Sadly, NASM does not support these
directives.) Then we can push that upstream to replace the error-prone and
non-standard custom handlers.

Change-Id: I5a734fd494b7eaafab24a00e6df624bd03b37d43
Reviewed-on: https://boringssl-review.googlesource.com/c/33785
Reviewed-by: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <alangley@gmail.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/abi_self_test.cc b/crypto/abi_self_test.cc
index cbc771f..773c9bd 100644
--- a/crypto/abi_self_test.cc
+++ b/crypto/abi_self_test.cc
@@ -19,6 +19,10 @@
 
 #include "test/abi_test.h"
 
+#if defined(OPENSSL_WINDOWS)
+#include <windows.h>
+#endif
+
 
 static bool test_function_was_called = false;
 static void TestFunction(int a1, int a2, int a3, int a4, int a5, int a6, int a7,
@@ -151,4 +155,31 @@
   CHECK_ABI(abi_test_clobber_xmm15);
 #endif
 }
+
+#if defined(OPENSSL_WINDOWS)
+static void ThrowWindowsException() {
+  DebugBreak();
+}
+
+static void ExceptionTest() {
+  bool handled = false;
+  __try {
+    CHECK_ABI(ThrowWindowsException);
+  } __except (GetExceptionCode() == EXCEPTION_BREAKPOINT
+                  ? EXCEPTION_EXECUTE_HANDLER
+                  : EXCEPTION_CONTINUE_SEARCH) {
+    handled = true;
+  }
+
+  EXPECT_TRUE(handled);
+}
+
+// Test that the trampoline's SEH metadata works.
+TEST(ABITest, TrampolineSEH) {
+  // Wrap the test in |CHECK_ABI|, to confirm the register-restoring annotations
+  // were correct.
+  CHECK_ABI(ExceptionTest);
+}
+#endif  // OPENSSL_WINDOWS
+
 #endif   // OPENSSL_X86_64 && SUPPORTS_ABI_TEST
diff --git a/crypto/test/asm/trampoline-x86_64.pl b/crypto/test/asm/trampoline-x86_64.pl
index b1f9b93..0f357b0 100755
--- a/crypto/test/asm/trampoline-x86_64.pl
+++ b/crypto/test/asm/trampoline-x86_64.pl
@@ -137,6 +137,7 @@
 .globl	abi_test_trampoline
 .align	16
 abi_test_trampoline:
+.Labi_test_trampoline_begin:
 .cfi_startproc
 	# Stack layout:
 	#   8 bytes - align
@@ -162,16 +163,25 @@
 $code .= <<____;
 	subq	\$$stack_alloc_size, %rsp
 .cfi_adjust_cfa_offset	$stack_alloc_size
+.Labi_test_trampoline_prolog_alloc:
 ____
 # Store our caller's state. This is needed because we modify it ourselves, and
 # also to isolate the test infrastruction from the function under test failing
 # to save some register.
+my %reg_offsets;
 $code .= store_caller_state($caller_state_offset, "%rsp", sub {
   my ($off, $reg) = @_;
   $reg = substr($reg, 1);
+  $reg_offsets{$reg} = $off;
   $off -= $stack_alloc_size + 8;
-  return ".cfi_offset\t$reg, $off\n";
+  return <<____;
+.cfi_offset	$reg, $off
+.Labi_test_trampoline_prolog_$reg:
+____
 });
+$code .= <<____;
+.Labi_test_trampoline_prolog_end:
+____
 
 $code .= load_caller_state(0, $state);
 $code .= <<____;
@@ -234,6 +244,7 @@
   # %rax already contains \$func's return value, unmodified.
 	ret
 .cfi_endproc
+.Labi_test_trampoline_end:
 .size	abi_test_trampoline,.-abi_test_trampoline
 ____
 
@@ -263,5 +274,92 @@
 ____
 }
 
+if ($win64) {
+  # Add unwind metadata for SEH.
+  #
+  # TODO(davidben): This is all manual right now. Once we've added SEH tests,
+  # add support for emitting these in x86_64-xlate.pl, probably based on MASM
+  # and Yasm's unwind directives, and unify with CFI. (Sadly, NASM does not
+  # support these directives.) Then push that upstream to replace the
+  # error-prone and non-standard custom handlers.
+
+  # See https://docs.microsoft.com/en-us/cpp/build/struct-unwind-code?view=vs-2017
+  my $UWOP_ALLOC_LARGE = 1;
+  my $UWOP_ALLOC_SMALL = 2;
+  my $UWOP_SAVE_NONVOL = 4;
+  my $UWOP_SAVE_XMM128 = 8;
+
+  my %UWOP_REG_NUMBER = (rax => 0, rcx => 1, rdx => 2, rbx => 3, rsp => 4,
+                         rbp => 5, rsi => 6, rdi => 7,
+                         map(("r$_" => $_), (8..15)));
+
+  my $unwind_codes = "";
+  my $num_slots = 0;
+  if ($stack_alloc_size <= 128) {
+    my $info = $UWOP_ALLOC_SMALL | ((($stack_alloc_size - 8) / 8) << 4);
+    $unwind_codes .= <<____;
+	.byte	.Labi_test_trampoline_prolog_alloc-.Labi_test_trampoline_begin
+	.byte	$info
+____
+    $num_slots++;
+  } else {
+    die "stack allocation needs three unwind slots" if ($stack_alloc_size > 512 * 1024 + 8);
+    my $info = $UWOP_ALLOC_LARGE;
+    my $value = $stack_alloc_size / 8;
+    $unwind_codes .= <<____;
+	.byte	.Labi_test_trampoline_prolog_alloc-.Labi_test_trampoline_begin
+	.byte	$info
+	.value	$value
+____
+    $num_slots += 2;
+  }
+
+  foreach my $reg (@caller_state) {
+    $reg = substr($reg, 1);
+    die "unknown register $reg" unless exists($reg_offsets{$reg});
+    if ($reg =~ /^r/) {
+      die "unknown register $reg" unless exists($UWOP_REG_NUMBER{$reg});
+      my $info = $UWOP_SAVE_NONVOL | ($UWOP_REG_NUMBER{$reg} << 4);
+      my $value = $reg_offsets{$reg} / 8;
+      $unwind_codes .= <<____;
+	.byte	.Labi_test_trampoline_prolog_$reg-.Labi_test_trampoline_begin
+	.byte	$info
+	.value	$value
+____
+      $num_slots += 2;
+    } elsif ($reg =~ /^xmm/) {
+      my $info = $UWOP_SAVE_XMM128 | (substr($reg, 3) << 4);
+      my $value = $reg_offsets{$reg} / 16;
+      $unwind_codes .= <<____;
+	.byte	.Labi_test_trampoline_prolog_$reg-.Labi_test_trampoline_begin
+	.byte	$info
+	.value	$value
+____
+      $num_slots += 2;
+    } else {
+      die "unknown register $reg";
+    }
+  }
+
+  $code .= <<____;
+.section	.pdata
+.align	4
+	# https://docs.microsoft.com/en-us/cpp/build/struct-runtime-function?view=vs-2017
+	.rva	.Labi_test_trampoline_begin
+	.rva	.Labi_test_trampoline_end
+	.rva	.Labi_test_trampoline_info
+
+.section	.xdata
+.align	8
+.Labi_test_trampoline_info:
+	# https://docs.microsoft.com/en-us/cpp/build/struct-unwind-info?view=vs-2017
+	.byte	1	# version 1, no flags
+	.byte	.Labi_test_trampoline_prolog_end-.Labi_test_trampoline_begin
+	.byte	$num_slots
+	.byte	0	# no frame register
+$unwind_codes
+____
+}
+
 print $code;
 close STDOUT;