Mirror of BoringSSL (grpc依赖)
https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
17 KiB
559 lines
17 KiB
#!/usr/bin/env perl |
|
# Copyright (c) 2018, Google Inc. |
|
# |
|
# Permission to use, copy, modify, and/or distribute this software for any |
|
# purpose with or without fee is hereby granted, provided that the above |
|
# copyright notice and this permission notice appear in all copies. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|
# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
|
|
# This file defines helper functions for crypto/test/abi_test.h on x86_64. See |
|
# that header for details on how to use this. |
|
# |
|
# For convenience, this file is linked into libcrypto, where consuming builds |
|
# already support architecture-specific sources. The static linker should drop |
|
# this code in non-test binaries. This includes a shared library build of |
|
# libcrypto, provided --gc-sections (ELF), -dead_strip (Mac), or equivalent is |
|
# used. |
|
# |
|
# References: |
|
# |
|
# SysV ABI: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf |
|
# Win64 ABI: https://docs.microsoft.com/en-us/cpp/build/x64-software-conventions?view=vs-2017 |
|
|
|
use strict; |
|
|
|
my $flavour = shift; |
|
my $output = shift; |
|
if ($flavour =~ /\./) { $output = $flavour; undef $flavour; } |
|
|
|
my $win64 = 0; |
|
$win64 = 1 if ($flavour =~ /[nm]asm|mingw64/ || $output =~ /\.asm$/); |
|
|
|
$0 =~ m/(.*[\/\\])[^\/\\]+$/; |
|
my $dir = $1; |
|
my $xlate; |
|
( $xlate="${dir}x86_64-xlate.pl" and -f $xlate ) or |
|
( $xlate="${dir}../../perlasm/x86_64-xlate.pl" and -f $xlate) or |
|
die "can't locate x86_64-xlate.pl"; |
|
|
|
open OUT, "| \"$^X\" \"$xlate\" $flavour \"$output\""; |
|
*STDOUT = *OUT; |
|
|
|
# @inp is the registers used for function inputs, in order. |
|
my @inp = $win64 ? ("%rcx", "%rdx", "%r8", "%r9") : |
|
("%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9"); |
|
|
|
# @caller_state is the list of registers that the callee must preserve for the |
|
# caller. This must match the definition of CallerState in abi_test.h. |
|
my @caller_state = ("%rbx", "%rbp", "%r12", "%r13", "%r14", "%r15"); |
|
if ($win64) { |
|
@caller_state = ("%rbx", "%rbp", "%rdi", "%rsi", "%r12", "%r13", "%r14", |
|
"%r15", "%xmm6", "%xmm7", "%xmm8", "%xmm9", "%xmm10", |
|
"%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15"); |
|
} |
|
|
|
# $caller_state_size is the size of CallerState, in bytes. |
|
my $caller_state_size = 0; |
|
foreach (@caller_state) { |
|
if (/^%r/) { |
|
$caller_state_size += 8; |
|
} elsif (/^%xmm/) { |
|
$caller_state_size += 16; |
|
} else { |
|
die "unknown register $_"; |
|
} |
|
} |
|
|
|
# load_caller_state returns code which loads a CallerState structure at |
|
# $off($reg) into the respective registers. No other registers are touched, but |
|
# $reg may not be a register in CallerState. $cb is an optional callback to |
|
# add extra lines after each movq or movdqa. $cb is passed the offset, relative |
|
# to $reg, and name of each register. |
|
sub load_caller_state { |
|
my ($off, $reg, $cb) = @_; |
|
my $ret = ""; |
|
foreach (@caller_state) { |
|
my $old_off = $off; |
|
if (/^%r/) { |
|
$ret .= "\tmovq\t$off($reg), $_\n"; |
|
$off += 8; |
|
} elsif (/^%xmm/) { |
|
$ret .= "\tmovdqa\t$off($reg), $_\n"; |
|
$off += 16; |
|
} else { |
|
die "unknown register $_"; |
|
} |
|
$ret .= $cb->($old_off, $_) if (defined($cb)); |
|
} |
|
return $ret; |
|
} |
|
|
|
# store_caller_state behaves like load_caller_state, except that it writes the |
|
# current values of the registers into $off($reg). |
|
sub store_caller_state { |
|
my ($off, $reg, $cb) = @_; |
|
my $ret = ""; |
|
foreach (@caller_state) { |
|
my $old_off = $off; |
|
if (/^%r/) { |
|
$ret .= "\tmovq\t$_, $off($reg)\n"; |
|
$off += 8; |
|
} elsif (/^%xmm/) { |
|
$ret .= "\tmovdqa\t$_, $off($reg)\n"; |
|
$off += 16; |
|
} else { |
|
die "unknown register $_"; |
|
} |
|
$ret .= $cb->($old_off, $_) if (defined($cb)); |
|
} |
|
return $ret; |
|
} |
|
|
|
# $max_params is the maximum number of parameters abi_test_trampoline supports. |
|
my $max_params = 10; |
|
|
|
# Windows reserves stack space for the register-based parameters, while SysV |
|
# only reserves space for the overflow ones. |
|
my $stack_params_skip = $win64 ? scalar(@inp) : 0; |
|
my $num_stack_params = $win64 ? $max_params : $max_params - scalar(@inp); |
|
|
|
my ($func, $state, $argv, $argc, $unwind) = @inp; |
|
my $code = <<____; |
|
.text |
|
|
|
# abi_test_trampoline loads callee-saved registers from |state|, calls |func| |
|
# with |argv|, then saves the callee-saved registers into |state|. It returns |
|
# the result of |func|. If |unwind| is non-zero, this function triggers unwind |
|
# instrumentation. |
|
# uint64_t abi_test_trampoline(void (*func)(...), CallerState *state, |
|
# const uint64_t *argv, size_t argc, |
|
# int unwind); |
|
.type abi_test_trampoline, \@abi-omnipotent |
|
.globl abi_test_trampoline |
|
.align 16 |
|
abi_test_trampoline: |
|
.Labi_test_trampoline_seh_begin: |
|
.cfi_startproc |
|
# Stack layout: |
|
# 8 bytes - align |
|
# $caller_state_size bytes - saved caller registers |
|
# 8 bytes - scratch space |
|
# 8 bytes - saved copy of \$unwind (SysV-only) |
|
# 8 bytes - saved copy of \$state |
|
# 8 bytes - saved copy of \$func |
|
# 8 bytes - if needed for stack alignment |
|
# 8*$num_stack_params bytes - parameters for \$func |
|
____ |
|
my $stack_alloc_size = 8 + $caller_state_size + 8*3 + 8*$num_stack_params; |
|
if (!$win64) { |
|
$stack_alloc_size += 8; |
|
} |
|
# SysV and Windows both require the stack to be 16-byte-aligned. The call |
|
# instruction offsets it by 8, so stack allocations must be 8 mod 16. |
|
if ($stack_alloc_size % 16 != 8) { |
|
$num_stack_params++; |
|
$stack_alloc_size += 8; |
|
} |
|
my $stack_params_offset = 8 * $stack_params_skip; |
|
my $func_offset = 8 * $num_stack_params; |
|
my $state_offset = $func_offset + 8; |
|
# On Win64, unwind is already passed in memory. On SysV, it is passed in as |
|
# register and we must reserve stack space for it. |
|
my ($unwind_offset, $scratch_offset); |
|
if ($win64) { |
|
$unwind_offset = $stack_alloc_size + 5*8; |
|
$scratch_offset = $state_offset + 8; |
|
} else { |
|
$unwind_offset = $state_offset + 8; |
|
$scratch_offset = $unwind_offset + 8; |
|
} |
|
my $caller_state_offset = $scratch_offset + 8; |
|
$code .= <<____; |
|
subq \$$stack_alloc_size, %rsp |
|
.cfi_adjust_cfa_offset $stack_alloc_size |
|
.Labi_test_trampoline_seh_prolog_alloc: |
|
____ |
|
$code .= <<____ if (!$win64); |
|
movq $unwind, $unwind_offset(%rsp) |
|
____ |
|
# 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 $reg, $off |
|
.Labi_test_trampoline_seh_prolog_$reg: |
|
____ |
|
}); |
|
$code .= <<____; |
|
.Labi_test_trampoline_seh_prolog_end: |
|
____ |
|
|
|
$code .= load_caller_state(0, $state); |
|
$code .= <<____; |
|
# Stash \$func and \$state, so they are available after the call returns. |
|
movq $func, $func_offset(%rsp) |
|
movq $state, $state_offset(%rsp) |
|
|
|
# Load parameters. Note this will clobber \$argv and \$argc, so we can |
|
# only use non-parameter volatile registers. There are three, and they |
|
# are the same between SysV and Win64: %rax, %r10, and %r11. |
|
movq $argv, %r10 |
|
movq $argc, %r11 |
|
____ |
|
foreach (@inp) { |
|
$code .= <<____; |
|
dec %r11 |
|
js .Largs_done |
|
movq (%r10), $_ |
|
addq \$8, %r10 |
|
____ |
|
} |
|
$code .= <<____; |
|
leaq $stack_params_offset(%rsp), %rax |
|
.Largs_loop: |
|
dec %r11 |
|
js .Largs_done |
|
|
|
# This block should be: |
|
# movq (%r10), %rtmp |
|
# movq %rtmp, (%rax) |
|
# There are no spare registers available, so we spill into the scratch |
|
# space. |
|
movq %r11, $scratch_offset(%rsp) |
|
movq (%r10), %r11 |
|
movq %r11, (%rax) |
|
movq $scratch_offset(%rsp), %r11 |
|
|
|
addq \$8, %r10 |
|
addq \$8, %rax |
|
jmp .Largs_loop |
|
|
|
.Largs_done: |
|
movq $func_offset(%rsp), %rax |
|
movq $unwind_offset(%rsp), %r10 |
|
testq %r10, %r10 |
|
jz .Lno_unwind |
|
|
|
# Set the trap flag. |
|
pushfq |
|
orq \$0x100, 0(%rsp) |
|
popfq |
|
|
|
# Run an instruction to trigger a breakpoint immediately before the |
|
# call. |
|
nop |
|
.globl abi_test_unwind_start |
|
abi_test_unwind_start: |
|
|
|
call *%rax |
|
.globl abi_test_unwind_return |
|
abi_test_unwind_return: |
|
|
|
# Clear the trap flag. Note this assumes the trap flag was clear on |
|
# entry. We do not support instrumenting an unwind-instrumented |
|
# |abi_test_trampoline|. |
|
pushfq |
|
andq \$-0x101, 0(%rsp) # -0x101 is ~0x100 |
|
popfq |
|
.globl abi_test_unwind_stop |
|
abi_test_unwind_stop: |
|
|
|
jmp .Lcall_done |
|
|
|
.Lno_unwind: |
|
call *%rax |
|
|
|
.Lcall_done: |
|
# Store what \$func did our state, so our caller can check. |
|
movq $state_offset(%rsp), $state |
|
____ |
|
$code .= store_caller_state(0, $state); |
|
|
|
# Restore our caller's state. |
|
$code .= load_caller_state($caller_state_offset, "%rsp", sub { |
|
my ($off, $reg) = @_; |
|
$reg = substr($reg, 1); |
|
return ".cfi_restore\t$reg\n"; |
|
}); |
|
$code .= <<____; |
|
addq \$$stack_alloc_size, %rsp |
|
.cfi_adjust_cfa_offset -$stack_alloc_size |
|
|
|
# %rax already contains \$func's return value, unmodified. |
|
ret |
|
.cfi_endproc |
|
.Labi_test_trampoline_seh_end: |
|
.size abi_test_trampoline,.-abi_test_trampoline |
|
____ |
|
|
|
# abi_test_clobber_* zeros the corresponding register. These are used to test |
|
# the ABI-testing framework. |
|
foreach ("ax", "bx", "cx", "dx", "di", "si", "bp", 8..15) { |
|
$code .= <<____; |
|
.type abi_test_clobber_r$_, \@abi-omnipotent |
|
.globl abi_test_clobber_r$_ |
|
.align 16 |
|
abi_test_clobber_r$_: |
|
xorq %r$_, %r$_ |
|
ret |
|
.size abi_test_clobber_r$_,.-abi_test_clobber_r$_ |
|
____ |
|
} |
|
|
|
foreach (0..15) { |
|
$code .= <<____; |
|
.type abi_test_clobber_xmm$_, \@abi-omnipotent |
|
.globl abi_test_clobber_xmm$_ |
|
.align 16 |
|
abi_test_clobber_xmm$_: |
|
pxor %xmm$_, %xmm$_ |
|
ret |
|
.size abi_test_clobber_xmm$_,.-abi_test_clobber_xmm$_ |
|
____ |
|
} |
|
|
|
$code .= <<____; |
|
# abi_test_bad_unwind_wrong_register preserves the ABI, but annotates the wrong |
|
# register in unwind metadata. |
|
# void abi_test_bad_unwind_wrong_register(void); |
|
.type abi_test_bad_unwind_wrong_register, \@abi-omnipotent |
|
.globl abi_test_bad_unwind_wrong_register |
|
.align 16 |
|
abi_test_bad_unwind_wrong_register: |
|
.cfi_startproc |
|
.Labi_test_bad_unwind_wrong_register_seh_begin: |
|
pushq %r12 |
|
.cfi_push %r13 # This should be %r12 |
|
.Labi_test_bad_unwind_wrong_register_seh_push_r13: |
|
# Windows evaluates epilogs directly in the unwinder, rather than using |
|
# unwind codes. Add a nop so there is one non-epilog point (immediately |
|
# before the nop) where the unwinder can observe the mistake. |
|
nop |
|
popq %r12 |
|
.cfi_pop %r12 |
|
ret |
|
.Labi_test_bad_unwind_wrong_register_seh_end: |
|
.cfi_endproc |
|
.size abi_test_bad_unwind_wrong_register,.-abi_test_bad_unwind_wrong_register |
|
|
|
# abi_test_bad_unwind_temporary preserves the ABI, but temporarily corrupts the |
|
# storage space for a saved register, breaking unwind. |
|
# void abi_test_bad_unwind_temporary(void); |
|
.type abi_test_bad_unwind_temporary, \@abi-omnipotent |
|
.globl abi_test_bad_unwind_temporary |
|
.align 16 |
|
abi_test_bad_unwind_temporary: |
|
.cfi_startproc |
|
.Labi_test_bad_unwind_temporary_seh_begin: |
|
pushq %r12 |
|
.cfi_push %r12 |
|
.Labi_test_bad_unwind_temporary_seh_push_r12: |
|
|
|
movq %r12, %rax |
|
inc %rax |
|
movq %rax, (%rsp) |
|
# Unwinding from here is incorrect. Although %r12 itself has not been |
|
# changed, the unwind codes say to look in (%rsp) instead. |
|
|
|
movq %r12, (%rsp) |
|
# Unwinding is now fixed. |
|
|
|
popq %r12 |
|
.cfi_pop %r12 |
|
ret |
|
.Labi_test_bad_unwind_temporary_seh_end: |
|
.cfi_endproc |
|
.size abi_test_bad_unwind_temporary,.-abi_test_bad_unwind_temporary |
|
|
|
# abi_test_get_and_clear_direction_flag clears the direction flag. If the flag |
|
# was previously set, it returns one. Otherwise, it returns zero. |
|
# int abi_test_get_and_clear_direction_flag(void); |
|
.type abi_test_set_direction_flag, \@abi-omnipotent |
|
.globl abi_test_get_and_clear_direction_flag |
|
abi_test_get_and_clear_direction_flag: |
|
pushfq |
|
popq %rax |
|
andq \$0x400, %rax |
|
shrq \$10, %rax |
|
cld |
|
ret |
|
.size abi_test_get_and_clear_direction_flag,.-abi_test_get_and_clear_direction_flag |
|
|
|
# abi_test_set_direction_flag sets the direction flag. |
|
# void abi_test_set_direction_flag(void); |
|
.type abi_test_set_direction_flag, \@abi-omnipotent |
|
.globl abi_test_set_direction_flag |
|
abi_test_set_direction_flag: |
|
std |
|
ret |
|
.size abi_test_set_direction_flag,.-abi_test_set_direction_flag |
|
____ |
|
|
|
if ($win64) { |
|
$code .= <<____; |
|
# abi_test_bad_unwind_epilog preserves the ABI, and correctly annotates the |
|
# prolog, but the epilog does not match Win64's rules, breaking unwind during |
|
# the epilog. |
|
# void abi_test_bad_unwind_epilog(void); |
|
.type abi_test_bad_unwind_epilog, \@abi-omnipotent |
|
.globl abi_test_bad_unwind_epilog |
|
.align 16 |
|
abi_test_bad_unwind_epilog: |
|
.Labi_test_bad_unwind_epilog_seh_begin: |
|
pushq %r12 |
|
.Labi_test_bad_unwind_epilog_seh_push_r12: |
|
|
|
nop |
|
|
|
# The epilog should begin here, but the nop makes it invalid. |
|
popq %r12 |
|
nop |
|
ret |
|
.Labi_test_bad_unwind_epilog_seh_end: |
|
.size abi_test_bad_unwind_epilog,.-abi_test_bad_unwind_epilog |
|
____ |
|
|
|
# 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_PUSH_NONVOL = 0; |
|
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; |
|
foreach my $reg (reverse @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_seh_prolog_$reg-.Labi_test_trampoline_seh_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_seh_prolog_$reg-.Labi_test_trampoline_seh_begin |
|
.byte $info |
|
.value $value |
|
____ |
|
$num_slots += 2; |
|
} else { |
|
die "unknown register $reg"; |
|
} |
|
} |
|
|
|
if ($stack_alloc_size <= 128) { |
|
my $info = $UWOP_ALLOC_SMALL | ((($stack_alloc_size - 8) / 8) << 4); |
|
$unwind_codes .= <<____; |
|
.byte .Labi_test_trampoline_seh_prolog_alloc-.Labi_test_trampoline_seh_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_seh_prolog_alloc-.Labi_test_trampoline_seh_begin |
|
.byte $info |
|
.value $value |
|
____ |
|
$num_slots += 2; |
|
} |
|
|
|
$code .= <<____; |
|
.section .pdata |
|
.align 4 |
|
# https://docs.microsoft.com/en-us/cpp/build/struct-runtime-function?view=vs-2017 |
|
.rva .Labi_test_trampoline_seh_begin |
|
.rva .Labi_test_trampoline_seh_end |
|
.rva .Labi_test_trampoline_seh_info |
|
|
|
.rva .Labi_test_bad_unwind_wrong_register_seh_begin |
|
.rva .Labi_test_bad_unwind_wrong_register_seh_end |
|
.rva .Labi_test_bad_unwind_wrong_register_seh_info |
|
|
|
.rva .Labi_test_bad_unwind_temporary_seh_begin |
|
.rva .Labi_test_bad_unwind_temporary_seh_end |
|
.rva .Labi_test_bad_unwind_temporary_seh_info |
|
|
|
.rva .Labi_test_bad_unwind_epilog_seh_begin |
|
.rva .Labi_test_bad_unwind_epilog_seh_end |
|
.rva .Labi_test_bad_unwind_epilog_seh_info |
|
|
|
.section .xdata |
|
.align 8 |
|
.Labi_test_trampoline_seh_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_seh_prolog_end-.Labi_test_trampoline_seh_begin |
|
.byte $num_slots |
|
.byte 0 # no frame register |
|
$unwind_codes |
|
|
|
.align 8 |
|
.Labi_test_bad_unwind_wrong_register_seh_info: |
|
.byte 1 # version 1, no flags |
|
.byte .Labi_test_bad_unwind_wrong_register_seh_push_r13-.Labi_test_bad_unwind_wrong_register_seh_begin |
|
.byte 1 # one slot |
|
.byte 0 # no frame register |
|
|
|
.byte .Labi_test_bad_unwind_wrong_register_seh_push_r13-.Labi_test_bad_unwind_wrong_register_seh_begin |
|
.byte @{[$UWOP_PUSH_NONVOL | ($UWOP_REG_NUMBER{r13} << 4)]} |
|
|
|
.align 8 |
|
.Labi_test_bad_unwind_temporary_seh_info: |
|
.byte 1 # version 1, no flags |
|
.byte .Labi_test_bad_unwind_temporary_seh_push_r12-.Labi_test_bad_unwind_temporary_seh_begin |
|
.byte 1 # one slot |
|
.byte 0 # no frame register |
|
|
|
.byte .Labi_test_bad_unwind_temporary_seh_push_r12-.Labi_test_bad_unwind_temporary_seh_begin |
|
.byte @{[$UWOP_PUSH_NONVOL | ($UWOP_REG_NUMBER{r12} << 4)]} |
|
|
|
.align 8 |
|
.Labi_test_bad_unwind_epilog_seh_info: |
|
.byte 1 # version 1, no flags |
|
.byte .Labi_test_bad_unwind_epilog_seh_push_r12-.Labi_test_bad_unwind_epilog_seh_begin |
|
.byte 1 # one slot |
|
.byte 0 # no frame register |
|
|
|
.byte .Labi_test_bad_unwind_epilog_seh_push_r12-.Labi_test_bad_unwind_epilog_seh_begin |
|
.byte @{[$UWOP_PUSH_NONVOL | ($UWOP_REG_NUMBER{r12} << 4)]} |
|
____ |
|
} |
|
|
|
print $code; |
|
close STDOUT or die "error closing STDOUT";
|
|
|