summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Roberts <43952910+stephen-roberts-work@users.noreply.github.com>2018-11-13 16:57:42 +0000
committerDave Watson <davejwatson@fb.com>2018-11-13 08:57:42 -0800
commit748a2df11f0d10bd39fd5291d2b27b61392732da (patch)
tree6ad18852707f713cae689cd1734a7759863cdc92
parentf551e16213c52169af8bda554e4051b756a169cc (diff)
downloadlibunwind-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.
-rw-r--r--.gitignore1
-rw-r--r--include/dwarf.h2
-rw-r--r--src/dwarf/Gexpr.c14
-rw-r--r--src/dwarf/Gparser.c17
-rw-r--r--tests/Gx64-test-dwarf-expressions.c68
-rw-r--r--tests/Lx64-test-dwarf-expressions.c5
-rw-r--r--tests/Makefile.am17
-rw-r--r--tests/x64-test-dwarf-expressions.S78
8 files changed, 190 insertions, 12 deletions
diff --git a/.gitignore b/.gitignore
index 7b7905f0..af68f0e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,5 +75,6 @@ tests/[GL]ia64-test-readonly
tests/[GL]ia64-test-stack
tests/ia64-test-dyn1
tests/ia64-test-sig
+tests/[GL]x64-test-dwarf-expressions
tests/*.log
tests/*.trs
diff --git a/include/dwarf.h b/include/dwarf.h
index fab93c61..764f6f20 100644
--- a/include/dwarf.h
+++ b/include/dwarf.h
@@ -419,7 +419,7 @@ extern int dwarf_find_unwind_table (struct elf_dyn_info *edi, unw_addr_space_t a
unw_word_t ip);
extern void dwarf_put_unwind_info (unw_addr_space_t as,
unw_proc_info_t *pi, void *arg);
-extern int dwarf_eval_expr (struct dwarf_cursor *c, unw_word_t *addr,
+extern int dwarf_eval_expr (struct dwarf_cursor *c, unw_word_t stack_val, unw_word_t *addr,
unw_word_t len, unw_word_t *valp,
int *is_register);
extern int
diff --git a/src/dwarf/Gexpr.c b/src/dwarf/Gexpr.c
index 709c0c8f..2af45433 100644
--- a/src/dwarf/Gexpr.c
+++ b/src/dwarf/Gexpr.c
@@ -237,8 +237,8 @@ dwarf_stack_aligned(struct dwarf_cursor *c, unw_word_t cfa_addr,
}
HIDDEN int
-dwarf_eval_expr (struct dwarf_cursor *c, unw_word_t *addr, unw_word_t len,
- unw_word_t *valp, int *is_register)
+dwarf_eval_expr (struct dwarf_cursor *c, unw_word_t stack_val, unw_word_t *addr,
+ unw_word_t len, unw_word_t *valp, int *is_register)
{
unw_word_t operand1 = 0, operand2 = 0, tmp1, tmp2 = 0, tmp3, end_addr;
uint8_t opcode, operands_signature, u8;
@@ -287,10 +287,14 @@ do { \
end_addr = *addr + len;
*is_register = 0;
- Debug (14, "len=%lu, pushing cfa=0x%lx\n",
- (unsigned long) len, (unsigned long) c->cfa);
+ Debug (14, "len=%lu, pushing initial value=0x%lx\n",
+ (unsigned long) len, (unsigned long) stack_val);
- push (c->cfa); /* push current CFA as required by DWARF spec */
+ /* The DWARF standard requires the current CFA to be pushed onto the stack */
+ /* before evaluating DW_CFA_expression and DW_CFA_val_expression programs. */
+ /* DW_CFA_def_cfa_expressions do not take an initial value, but we push on */
+ /* a dummy value to keep this logic consistent. */
+ push (stack_val);
while (*addr < end_addr)
{
diff --git a/src/dwarf/Gparser.c b/src/dwarf/Gparser.c
index 1ce862c2..fe7c5817 100644
--- a/src/dwarf/Gparser.c
+++ b/src/dwarf/Gparser.c
@@ -734,7 +734,7 @@ create_state_record_for (struct dwarf_cursor *c, dwarf_state_record_t *sr,
}
static inline int
-eval_location_expr (struct dwarf_cursor *c, unw_addr_space_t as,
+eval_location_expr (struct dwarf_cursor *c, unw_word_t stack_val, unw_addr_space_t as,
unw_accessors_t *a, unw_word_t addr,
dwarf_loc_t *locp, void *arg)
{
@@ -746,7 +746,7 @@ eval_location_expr (struct dwarf_cursor *c, unw_addr_space_t as,
return ret;
/* evaluate the expression: */
- if ((ret = dwarf_eval_expr (c, &addr, len, &val, &is_register)) < 0)
+ if ((ret = dwarf_eval_expr (c, stack_val, &addr, len, &val, &is_register)) < 0)
return ret;
if (is_register)
@@ -804,7 +804,10 @@ apply_reg_state (struct dwarf_cursor *c, struct dwarf_reg_state *rs)
assert (rs->reg.where[DWARF_CFA_REG_COLUMN] == DWARF_WHERE_EXPR);
addr = rs->reg.val[DWARF_CFA_REG_COLUMN];
- if ((ret = eval_location_expr (c, as, a, addr, &cfa_loc, arg)) < 0)
+ /* The dwarf standard doesn't specify an initial value to be pushed on */
+ /* the stack before DW_CFA_def_cfa_expression evaluation. We push on a */
+ /* dummy value (0) to keep the eval_location_expr function consistent. */
+ if ((ret = eval_location_expr (c, 0, as, a, addr, &cfa_loc, arg)) < 0)
return ret;
/* the returned location better be a memory location... */
if (DWARF_IS_REG_LOC (cfa_loc))
@@ -844,13 +847,17 @@ apply_reg_state (struct dwarf_cursor *c, struct dwarf_reg_state *rs)
case DWARF_WHERE_EXPR:
addr = rs->reg.val[i];
- if ((ret = eval_location_expr (c, as, a, addr, new_loc + i, arg)) < 0)
+ /* The dwarf standard requires the current CFA to be pushed on the */
+ /* stack before DW_CFA_expression evaluation. */
+ if ((ret = eval_location_expr (c, cfa, as, a, addr, new_loc + i, arg)) < 0)
return ret;
break;
case DWARF_WHERE_VAL_EXPR:
addr = rs->reg.val[i];
- if ((ret = eval_location_expr (c, as, a, addr, new_loc + i, arg)) < 0)
+ /* The dwarf standard requires the current CFA to be pushed on the */
+ /* stack before DW_CFA_val_expression evaluation. */
+ if ((ret = eval_location_expr (c, cfa, as, a, addr, new_loc + i, arg)) < 0)
return ret;
new_loc[i] = DWARF_VAL_LOC (c, DWARF_GET_LOC (new_loc[i]));
break;
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