diff options
author | Stephen Roberts <43952910+stephen-roberts-work@users.noreply.github.com> | 2018-11-13 16:57:42 +0000 |
---|---|---|
committer | Dave Watson <davejwatson@fb.com> | 2018-11-13 08:57:42 -0800 |
commit | 748a2df11f0d10bd39fd5291d2b27b61392732da (patch) | |
tree | 6ad18852707f713cae689cd1734a7759863cdc92 /tests | |
parent | f551e16213c52169af8bda554e4051b756a169cc (diff) | |
download | libunwind-748a2df11f0d10bd39fd5291d2b27b61392732da.tar.gz |
dwarf: Push correct CFA onto stack for dwarf expression evaluation. (#93)
dwarf: Push correct CFA onto stack for dwarf expression evaluation.
This change fixes a bug where stale CFAs were pushed onto the dwarf
expression stack before expression evaluation. Some optimising compilers
emit CFI which relies on this being correct.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Gx64-test-dwarf-expressions.c | 68 | ||||
-rw-r--r-- | tests/Lx64-test-dwarf-expressions.c | 5 | ||||
-rw-r--r-- | tests/Makefile.am | 17 | ||||
-rw-r--r-- | tests/x64-test-dwarf-expressions.S | 78 |
4 files changed, 167 insertions, 1 deletions
diff --git a/tests/Gx64-test-dwarf-expressions.c b/tests/Gx64-test-dwarf-expressions.c new file mode 100644 index 00000000..209f8713 --- /dev/null +++ b/tests/Gx64-test-dwarf-expressions.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> + +#include <libunwind.h> + +static int verbose; +static int nerrors; + +#define panic(args...) \ + do { printf (args); ++nerrors; } while (0) + +// Assembly routine which sets up the stack for the test then calls another one +// which clobbers the stack, and which in turn calls recover_register below +extern int64_t DW_CFA_expression_testcase(int64_t regnum, int64_t height); + +// recover_register is called by the assembly routines. It returns the value of +// a register at a specified height from the inner-most frame. The return value +// is propagated back through the assembly routines to the testcase. +extern int64_t recover_register(int64_t regnum, int64_t height) +{ + // Initialize cursor to current frame + int rc, i; + unw_cursor_t cursor; + unw_context_t context; + unw_getcontext(&context); + unw_init_local(&cursor, &context); + // Unwind frames until required height from inner-most frame (i.e. this one) + for (i = 0; i < height; ++i) + { + rc = unw_step(&cursor); + if (rc < 0) + panic("%s: unw_step failed on step %d with return code %d", __FUNCTION__, i, rc); + else if (rc == 0) + panic("%s: unw_step failed to reach the end of the stack", __FUNCTION__); + unw_word_t pc; + rc = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (rc < 0 || pc == 0) + panic("%s: unw_get_reg failed to locate the program counter", __FUNCTION__); + } + // We're now at the required height, extract register + uint64_t value; + if ((rc = unw_get_reg(&cursor, (unw_regnum_t) regnum, &value)) != 0) + panic("%s: unw_get_reg failed to retrieve register %lu", __FUNCTION__, regnum); + return value; +} + +int +main (int argc, char **argv) +{ + if (argc > 1) + verbose = 1; + + if (DW_CFA_expression_testcase(12, 1) != 0) + panic("r12 should be clobbered at height 1 (DW_CFA_expression_inner)"); + if (DW_CFA_expression_testcase(12, 2) != 111222333) + panic("r12 should be restored at height 2 (DW_CFA_expression_testcase)"); + + if (nerrors > 0) + { + fprintf (stderr, "FAILURE: detected %d errors\n", nerrors); + exit (-1); + } + + if (verbose) + printf ("SUCCESS.\n"); + return 0; +} diff --git a/tests/Lx64-test-dwarf-expressions.c b/tests/Lx64-test-dwarf-expressions.c new file mode 100644 index 00000000..07e916e6 --- /dev/null +++ b/tests/Lx64-test-dwarf-expressions.c @@ -0,0 +1,5 @@ +#define UNW_LOCAL_ONLY +#include <libunwind.h> +#if !defined(UNW_REMOTE_ONLY) +#include "Gx64-test-dwarf-expressions.c" +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 4b0b9db7..fee8a70b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -37,7 +37,11 @@ if ARCH_PPC64 if USE_ALTIVEC noinst_PROGRAMS_arch += ppc64-test-altivec endif #USE_ALTIVEC -endif #ARCH_PPC64 +else #!ARCH_PPC64 +if ARCH_X86_64 + check_PROGRAMS_arch += Gx64-test-dwarf-expressions Lx64-test-dwarf-expressions +endif #ARCH X86_64 +endif #!ARCH_PPC64 endif #!ARCH_IA64 check_PROGRAMS_cdep += Gtest-bt Ltest-bt Gtest-exc Ltest-exc \ Gtest-init Ltest-init \ @@ -139,6 +143,14 @@ Gia64_test_nat_SOURCES = Gia64-test-nat.c ia64-test-nat-asm.S ia64_test_dyn1_SOURCES = ia64-test-dyn1.c ia64-dyn-asm.S flush-cache.S \ flush-cache.h ppc64_test_altivec_SOURCES = ppc64-test-altivec.c ppc64-test-altivec-utils.c + + +Gx64_test_dwarf_expressions_SOURCES = Gx64-test-dwarf-expressions.c \ + x64-test-dwarf-expressions.S +Lx64_test_dwarf_expressions_SOURCES = Lx64-test-dwarf-expressions.c \ + x64-test-dwarf-expressions.S + + Gtest_init_SOURCES = Gtest-init.cxx Ltest_init_SOURCES = Ltest-init.cxx Ltest_cxx_exceptions_SOURCES = Ltest-cxx-exceptions.cxx @@ -232,3 +244,6 @@ Lia64_test_readonly_LDADD = $(LIBUNWIND_local) ia64_test_dyn1_LDADD = $(LIBUNWIND) ia64_test_sig_LDADD = $(LIBUNWIND) ppc64_test_altivec_LDADD = $(LIBUNWIND) + +Gx64_test_dwarf_expressions_LDADD = $(LIBUNWIND) $(LIBUNWIND_local) +Lx64_test_dwarf_expressions_LDADD = $(LIBUNWIND_local) diff --git a/tests/x64-test-dwarf-expressions.S b/tests/x64-test-dwarf-expressions.S new file mode 100644 index 00000000..f275625d --- /dev/null +++ b/tests/x64-test-dwarf-expressions.S @@ -0,0 +1,78 @@ +.global DW_CFA_expression_testcase + +.extern recover_register + +.text + +# CFI expressions were added in DWARF v3 to allow compilers to specify memory +# locations or register values using DWARF programs. These programs are simple +# stack-based operations which allow the compiler to encode integer mathematics +# and other complex logic. CFI expressions are therefore more powerful than the +# conventional register + offset schemes. +# +# These tests capture a bug we have fixed in libunwind. CFI expression programs +# always start with the current CFA pushed onto the stack. This file contains a +# pair of routines which test CFI expression parsing. Specifically they test +# DW_CFA_expression logic, which uses DWARF expressions to compute the address +# where a non-volatile register was stored. +# +# Main calls DW_CFA_expression_testcase, which sets up known state in a +# non-volatile (caller-saved) register. We use r12 for this purpose. After this +# DW_CFA_expression_testcase then calls DW_CFA_expression_inner, which clobbers +# r12 after stashing its value on the stack. This routine contains a DWARF3 CFI +# expression to restore the value of r12 on unwind which should allow libunwind +# to recover clobbered state. DW_CFA_expression_inner calls recover_register to +# retrieve the cached register value. This function recovers the register value +# by using libunwind to unwind the stack through DW_CFA_expression_inner and up +# to the call site in DW_CFA_expression_testcase. If our expression is correct, +# libunwind will be able to restore r12 from the stack. +# +# BE CAREFUL WITH rdi, rsi, rax HERE! The arguments to recover_register are +# passed in via rdi, rsi and I just let them flow through unchanged. Similarly +# RAX flows back unchanged. Adding any function calls to the below may clobber +# these registers and cause this test to fail mysteriously. + + +######################################################## +# Test: Restoring a register using a DW_CFA_expression # +# which uses implicit CFA pushed onto stack. # +######################################################## + +.type DW_CFA_expression_testcase STT_FUNC +DW_CFA_expression_testcase: + .cfi_startproc + push %r12 + .cfi_adjust_cfa_offset 8 + # Move our sentinel (known) value into non-volatile (Callee-saved) r12 + mov $111222333, %r12 + .cfi_rel_offset %r12, 0 + call DW_CFA_expression_inner + pop %r12 + .cfi_restore %r12 + .cfi_adjust_cfa_offset -8 + ret + .cfi_endproc +.size DW_CFA_expression_testcase,.-DW_CFA_expression_testcase + +.type DW_CFA_expression_inner STT_FUNC +DW_CFA_expression_inner: + .cfi_startproc + push %r12 + .cfi_adjust_cfa_offset 8 + # !! IMPORTANT BIT !! The test is all about how we parse the following bytes. + # Now we use an expression to describe where our sentinel value is stored: + # DW_CFA_expression(0x10), r12(0x0c), Length(0x02), (preamble) + # DW_OP_lit16(0x40), DW_OP_minus(0x1c) (instructions) + # Parsing starts with the CFA on the stack, then pushes 16, then does a minus + # which is eqivalent to a=pop(), b=pop(), push(b-a), leaving us with a value + # of cfa-16 (cfa points at old rsp, cfa-8 is our rip, so we stored r12 at + # cfa-16). + xor %r12, %r12 # Trash r12 + .cfi_escape 0x10, 0x0c, 0x2, 0x40, 0x1c # DW_CFA_expression for recovery + call recover_register + pop %r12 + .cfi_restore %r12 + .cfi_adjust_cfa_offset -8 + ret + .cfi_endproc +.size DW_CFA_expression_inner,.-DW_CFA_expression_inner |