-
Notifications
You must be signed in to change notification settings - Fork 288
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
f551e16
commit 748a2df
Showing
8 changed files
with
190 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#define UNW_LOCAL_ONLY | ||
#include <libunwind.h> | ||
#if !defined(UNW_REMOTE_ONLY) | ||
#include "Gx64-test-dwarf-expressions.c" | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |