From 20aa2c606ef682889722b03b1d874befa84fbf53 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Fri, 1 Jul 2016 11:56:39 +0100 Subject: [PATCH] Extend JIT-reader test and fix GDB problems that exposes The jit-reader.exp test isn't really exercising the jit-reader's unwinder API at all. This commit address that, and then fixes GDB problems exposed. - The custom JIT reader provided for the jit-reader.exp testcase always rejects the jitted function's frame... This is because the custom JIT reader in the testcase never ever sets state->code_begin/end, so the bounds check in gdb.base/jitreader.c:unwind_frame: if (this_ip >= state->code_end || this_ip < state->code_begin) return GDB_FAIL; tends to fail, unless you're "lucky" (because it references uninitialized data). The result is that GDB is always actually using a built-in unwinder for the jitted function. - The provided unwinder doesn't do anything that GDB's built-in unwinder can't do. IOW, we can't really tell whether the JIT reader's unwinder is working or not. I fixed that by making the jitted function mangle its own stack pointer with a xor, and then teaching the jit unwinder to demangle it back (another xor). So now "backtrace" with GDB's built-in unwinder fails while with the jit unwinder, it succeeds. - GDB crashes after unloading the JIT reader, and flushing frames... I made the testcase use the "flushregs" command after unloading the JIT reader, to force the JIT frames to be flushed. However, that crashes GDB... When reinit_frame_cache tears down a frame's cache, it calls its unwinder's dealloc_cache method, which for JIT frames ends up in jit.c:jit_dealloc_cache. This function calls each of the frame's gdb_reg_value's "free" pointer: for (i = 0; i < gdbarch_num_regs (frame_arch); i++) if (priv_data->registers[i] && priv_data->registers[i]->free) priv_data->registers[i]->free (priv_data->registers[i]); and the problem is these gdb_reg_value instances have been returned by the JIT reader that has been already unloaded, and their "free" function pointers likely point to functions in the DSO that has already been unloaded... A fix for that could be to call reinit_frame_cache in jit_reader_unload_command _before_ unloading the jit reader DSO so that the jit reader is given a chance to clean up the gdb_reg_values before it is unloaded. However, the fix for the point below makes this unnecessary, because it stops jit.c from keeping around gdb_reg_values in the first place. - However, it still makes sense to clear the frame cache when loading or unloading a JIT unwinder. This makes testing a JIT unwinder a bit simpler. - Not only the frame cache actually -- gdb is not unloading the jit-registered objfiles when the JIT reader is unloaded, and not loading the already-registered descriptors when a JIT reader is loaded. The new test exercises unloading the jit reader, loading it back again, and then making sure the JIT reader's unwinder works again. Without the unload/re-load of already-read descriptors, the newly loaded JIT would have no idea where the new function is, because it's stored at symbol read time. - I added a couple "info frame" calls to the test, and that crashes GDB... The problem is that jit_frame_prev_register assumes it'll only be called for raw registers, so when it gets a pseudo register number, the "priv->registers[reg]" access is really an out-of-bounds access. To fix that, I made jit_frame_prev_register use gdbarch_pseudo_register_read_value for reading the pseudo-registers. However, that works with a regcache and we don't have one. To fix that, I made the JIT unwinder store a regcache in its cache instead of an array of gdb_reg_value pointers. gdb/ChangeLog: 2016-07-01 Pedro Alves Tom Tromey * jit.c (jit_reader_load_command): Call reinit_frame_cache and jit_inferior_created_hook. (jit_reader_unload_command): Call reinit_frame_cache and jit_inferior_exit_hook. * jit.c (struct jit_unwind_private) : Delete field. : New field. (jit_unwind_reg_set_impl): Set the register's value in the regcache. Free the passed-in gdb_reg_value. (jit_dealloc_cache): Adjust to free the regcache. (jit_frame_sniffer): Allocate a regcache instead of an array of gdb_reg_value pointers. (jit_frame_this_id): Adjust. (jit_frame_prev_register): Read raw registers off of the regcache instead of from the gdb_reg_value pointer array. Use gdbarch_pseudo_register_read_value to read pseudo registers. * regcache.c (regcache_raw_set_cached_value): New function, factored out from ... (regcache_raw_write): ... here. * regcache.h (regcache_raw_set_cached_value): Declare. gdb/testsuite/ChangeLog: 2016-07-01 Pedro Alves * gdb.base/jit-reader.exp (info_registers_current_frame): New procedure. (jit_reader_test): Test the jit reader's unwinder. * gdb.base/jithost.c (jit_function_00_code): New global. (main): Use memcpy to fill in the mmapped code, instead of poking bytes manually here. * gdb.base/jitreader.c (enum register_mapping) : New value. (read_debug_info): Save the function's range. (read_sp): New function. (unwind_frame): Use it. Also unwind RBP. (get_frame_id): Use read_sp. (gdb_init_reader): Use calloc instead of malloc. * lib/gdb.exp (get_hexadecimal_valueof): Add optional 'test' parameter. Use gdb_test_multiple. --- gdb/ChangeLog | 23 ++++ gdb/jit.c | 51 +++++--- gdb/regcache.c | 15 ++- gdb/regcache.h | 8 ++ gdb/testsuite/ChangeLog | 18 +++ gdb/testsuite/gdb.base/jit-reader.exp | 174 +++++++++++++++++++++++++- gdb/testsuite/gdb.base/jithost.c | 24 +++- gdb/testsuite/gdb.base/jitreader.c | 55 +++++++- gdb/testsuite/lib/gdb.exp | 21 ++-- 9 files changed, 347 insertions(+), 42 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index d3caa1abf57..53c773eb4d6 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,26 @@ +2016-07-01 Pedro Alves + Tom Tromey + + * jit.c (jit_reader_load_command): Call reinit_frame_cache and + jit_inferior_created_hook. + (jit_reader_unload_command): Call reinit_frame_cache and + jit_inferior_exit_hook. + * jit.c (struct jit_unwind_private) : Delete field. + : New field. + (jit_unwind_reg_set_impl): Set the register's value in the + regcache. Free the passed-in gdb_reg_value. + (jit_dealloc_cache): Adjust to free the regcache. + (jit_frame_sniffer): Allocate a regcache instead of an array of + gdb_reg_value pointers. + (jit_frame_this_id): Adjust. + (jit_frame_prev_register): Read raw registers off of the regcache + instead of from the gdb_reg_value pointer array. Use + gdbarch_pseudo_register_read_value to read pseudo registers. + * regcache.c (regcache_raw_set_cached_value): New function, + factored out from ... + (regcache_raw_write): ... here. + * regcache.h (regcache_raw_set_cached_value): Declare. + 2016-07-01 Pedro Alves Antoine Tremblay diff --git a/gdb/jit.c b/gdb/jit.c index 9fd5ae675b0..2b6cf77f08a 100644 --- a/gdb/jit.c +++ b/gdb/jit.c @@ -51,6 +51,7 @@ static const char *const jit_descriptor_name = "__jit_debug_descriptor"; static const struct program_space_data *jit_program_space_data = NULL; static void jit_inferior_init (struct gdbarch *gdbarch); +static void jit_inferior_exit_hook (struct inferior *inf); /* An unwinder is registered for every gdbarch. This key is used to remember if the unwinder has been registered for a particular @@ -218,6 +219,8 @@ jit_reader_load_command (char *args, int from_tty) prev_cleanup = make_cleanup (xfree, so_name); loaded_jit_reader = jit_reader_load (so_name); + reinit_frame_cache (); + jit_inferior_created_hook (); do_cleanups (prev_cleanup); } @@ -229,6 +232,8 @@ jit_reader_unload_command (char *args, int from_tty) if (!loaded_jit_reader) error (_("No JIT reader loaded.")); + reinit_frame_cache (); + jit_inferior_exit_hook (current_inferior ()); loaded_jit_reader->functions->destroy (loaded_jit_reader->functions); gdb_dlclose (loaded_jit_reader->handle); @@ -1090,7 +1095,7 @@ struct jit_unwind_private { /* Cached register values. See jit_frame_sniffer to see how this works. */ - struct gdb_reg_value **registers; + struct regcache *regcache; /* The frame being unwound. */ struct frame_info *this_frame; @@ -1115,11 +1120,12 @@ jit_unwind_reg_set_impl (struct gdb_unwind_callbacks *cb, int dwarf_regnum, fprintf_unfiltered (gdb_stdlog, _("Could not recognize DWARF regnum %d"), dwarf_regnum); + value->free (value); return; } - gdb_assert (priv->registers); - priv->registers[gdb_reg] = value; + regcache_raw_set_cached_value (priv->regcache, gdb_reg, value->value); + value->free (value); } static void @@ -1162,14 +1168,10 @@ jit_dealloc_cache (struct frame_info *this_frame, void *cache) struct gdbarch *frame_arch; int i; - gdb_assert (priv_data->registers); + gdb_assert (priv_data->regcache != NULL); frame_arch = get_frame_arch (priv_data->this_frame); - for (i = 0; i < gdbarch_num_regs (frame_arch); i++) - if (priv_data->registers[i] && priv_data->registers[i]->free) - priv_data->registers[i]->free (priv_data->registers[i]); - - xfree (priv_data->registers); + regcache_xfree (priv_data->regcache); xfree (priv_data); } @@ -1188,6 +1190,8 @@ jit_frame_sniffer (const struct frame_unwind *self, struct jit_unwind_private *priv_data; struct gdb_unwind_callbacks callbacks; struct gdb_reader_funcs *funcs; + struct address_space *aspace; + struct gdbarch *gdbarch; callbacks.reg_get = jit_unwind_reg_get_impl; callbacks.reg_set = jit_unwind_reg_set_impl; @@ -1200,11 +1204,12 @@ jit_frame_sniffer (const struct frame_unwind *self, gdb_assert (!*cache); + aspace = get_frame_address_space (this_frame); + gdbarch = get_frame_arch (this_frame); + *cache = XCNEW (struct jit_unwind_private); priv_data = (struct jit_unwind_private *) *cache; - priv_data->registers = - XCNEWVEC (struct gdb_reg_value *, - gdbarch_num_regs (get_frame_arch (this_frame))); + priv_data->regcache = regcache_xmalloc (gdbarch, aspace); priv_data->this_frame = this_frame; callbacks.priv_data = priv_data; @@ -1240,7 +1245,7 @@ jit_frame_this_id (struct frame_info *this_frame, void **cache, struct gdb_reader_funcs *funcs; struct gdb_unwind_callbacks callbacks; - priv.registers = NULL; + priv.regcache = NULL; priv.this_frame = this_frame; /* We don't expect the frame_id function to set any registers, so we @@ -1264,17 +1269,25 @@ static struct value * jit_frame_prev_register (struct frame_info *this_frame, void **cache, int reg) { struct jit_unwind_private *priv = (struct jit_unwind_private *) *cache; - struct gdb_reg_value *value; + struct gdbarch *gdbarch; if (priv == NULL) return frame_unwind_got_optimized (this_frame, reg); - gdb_assert (priv->registers); - value = priv->registers[reg]; - if (value && value->defined) - return frame_unwind_got_bytes (this_frame, reg, value->value); + gdbarch = get_regcache_arch (priv->regcache); + if (reg < gdbarch_num_regs (gdbarch)) + { + gdb_byte *buf = (gdb_byte *) alloca (register_size (gdbarch, reg)); + enum register_status status; + + status = regcache_raw_read (priv->regcache, reg, buf); + if (status == REG_VALID) + return frame_unwind_got_bytes (this_frame, reg, buf); + else + return frame_unwind_got_optimized (this_frame, reg); + } else - return frame_unwind_got_optimized (this_frame, reg); + return gdbarch_pseudo_register_read_value (gdbarch, priv->regcache, reg); } /* Relay everything back to the unwinder registered by the JIT debug diff --git a/gdb/regcache.c b/gdb/regcache.c index f0ba0cf5521..a5c90a6314c 100644 --- a/gdb/regcache.c +++ b/gdb/regcache.c @@ -890,6 +890,17 @@ regcache_cooked_write_unsigned (struct regcache *regcache, int regnum, regcache_cooked_write (regcache, regnum, buf); } +/* See regcache.h. */ + +void +regcache_raw_set_cached_value (struct regcache *regcache, int regnum, + const gdb_byte *buf) +{ + memcpy (register_buffer (regcache, regnum), buf, + regcache->descr->sizeof_register[regnum]); + regcache->register_status[regnum] = REG_VALID; +} + void regcache_raw_write (struct regcache *regcache, int regnum, const gdb_byte *buf) @@ -917,9 +928,7 @@ regcache_raw_write (struct regcache *regcache, int regnum, inferior_ptid = regcache->ptid; target_prepare_to_store (regcache); - memcpy (register_buffer (regcache, regnum), buf, - regcache->descr->sizeof_register[regnum]); - regcache->register_status[regnum] = REG_VALID; + regcache_raw_set_cached_value (regcache, regnum, buf); /* Register a cleanup function for invalidating the register after it is written, in case of a failure. */ diff --git a/gdb/regcache.h b/gdb/regcache.h index 1a4ff4248a8..1bb0ce0ec8d 100644 --- a/gdb/regcache.h +++ b/gdb/regcache.h @@ -66,6 +66,14 @@ extern void regcache_raw_write_signed (struct regcache *regcache, extern void regcache_raw_write_unsigned (struct regcache *regcache, int regnum, ULONGEST val); +/* Set a raw register's value in the regcache's buffer. Unlike + regcache_raw_write, this is not write-through. The intention is + allowing to change the buffer contents of a read-only regcache + allocated with regcache_xmalloc. */ + +extern void regcache_raw_set_cached_value + (struct regcache *regcache, int regnum, const gdb_byte *buf); + /* Partial transfer of raw registers. These perform read, modify, write style operations. The read variant returns the status of the register. */ diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index fd943790c53..bcbffaa044b 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,21 @@ +2016-07-01 Pedro Alves + + * gdb.base/jit-reader.exp (info_registers_current_frame): New + procedure. + (jit_reader_test): Test the jit reader's unwinder. + * gdb.base/jithost.c (jit_function_00_code): New global. + (main): Use memcpy to fill in the mmapped code, instead of poking + bytes manually here. + * gdb.base/jitreader.c (enum register_mapping) : New + value. + (read_debug_info): Save the function's range. + (read_sp): New function. + (unwind_frame): Use it. Also unwind RBP. + (get_frame_id): Use read_sp. + (gdb_init_reader): Use calloc instead of malloc. + * lib/gdb.exp (get_hexadecimal_valueof): Add optional 'test' + parameter. Use gdb_test_multiple. + 2016-07-01 Pedro Alves Antoine Tremblay diff --git a/gdb/testsuite/gdb.base/jit-reader.exp b/gdb/testsuite/gdb.base/jit-reader.exp index b5e297aaf7f..642c257eff9 100644 --- a/gdb/testsuite/gdb.base/jit-reader.exp +++ b/gdb/testsuite/gdb.base/jit-reader.exp @@ -57,10 +57,50 @@ if { [gdb_compile_shlib "${srcdir}/${subdir}/${jit_reader_src}" "${jit_reader_bi return -1 } +# Test "info registers" in the current frame, expecting RSP's value to +# be SP. + +proc info_registers_current_frame {sp} { + global hex decimal + + set any "\[^\r\n\]*" + + gdb_test "info registers" \ + [multi_line \ + "rax $hex $decimal" \ + "rbx $hex $decimal" \ + "rcx $hex $decimal" \ + "rdx $hex $decimal" \ + "rsi $hex $decimal" \ + "rdi $hex $decimal" \ + "rbp $hex $hex" \ + "rsp $sp $sp" \ + "r8 $hex $decimal" \ + "r9 $hex $decimal" \ + "r10 $hex $decimal" \ + "r11 $hex $decimal" \ + "r12 $hex $decimal" \ + "r13 $hex $decimal" \ + "r14 $hex $decimal" \ + "r15 $hex $decimal" \ + "rip $hex $hex$any" \ + "eflags $hex \\\[$any\\\]" \ + "cs $hex $decimal" \ + "ss $hex $decimal" \ + "ds $hex $decimal" \ + "es $hex $decimal" \ + "fs $hex $decimal" \ + "gs $hex $decimal" \ + ] +} + proc jit_reader_test {} { global jit_host_bin global jit_reader_bin global verbose + global hex decimal + + set any "\[^\r\n\]*" clean_restart $jit_host_bin gdb_load_shlib $jit_reader_bin @@ -73,7 +113,139 @@ proc jit_reader_test {} { gdb_run_cmd gdb_test "" "Program received signal SIGTRAP, .*" "expect SIGTRAP" - gdb_test "bt" "jit_function_00.*" + # Test the JIT reader unwinder. + with_test_prefix "with jit-reader" { + + with_test_prefix "before mangling" { + gdb_test "bt" \ + [multi_line \ + "#0 ${any} in jit_function_00 ${any}" \ + "#1 ${any} in main ${any}" \ + ] \ + "bt works" + + set sp_before_mangling \ + [get_hexadecimal_valueof "\$sp" 0 "get sp"] + + gdb_test "up" "#1 $any in main $any\r\n$any function $any" \ + "move up to caller" + + set caller_sp \ + [get_hexadecimal_valueof "\$sp" 0 "get caller sp"] + } + + # Step over the instruction that mangles the stack pointer. + # While that confuses GDB's built-in unwinder, the JIT + # reader's unwinder understands the mangling and should thus + # be able to unwind at that location. + with_test_prefix "after mangling" { + gdb_test "si" "in jit_function_00 .*" "step over stack mangling" + + set sp_after_mangling \ + [get_hexadecimal_valueof "\$sp" 0 "get sp"] + + gdb_assert {$sp_before_mangling != $sp_after_mangling} \ + "sp is mangled" + + # Check that the jit unwinder manages to backtrace through + # the mangled stack pointer. + gdb_test "bt" \ + [multi_line \ + "#0 ${any} in jit_function_00 ${any}" \ + "#1 ${any} in main ${any}" \ + ] \ + "bt works" + + with_test_prefix "current frame" { + info_registers_current_frame $sp_after_mangling + + gdb_test "info frame" \ + "Stack level 0, frame at $sp_before_mangling.*in jit_function_00.*" + } + + with_test_prefix "caller frame" { + gdb_test "up" "#1 $any in main $any\r\n$any function $any" \ + "up to caller" + + # Since the JIT unwinder only provides RIP/RSP/RBP, + # all other registers should show as "". + gdb_test "info registers" \ + [multi_line \ + "rax " \ + "rbx " \ + "rcx " \ + "rdx " \ + "rsi " \ + "rdi " \ + "rbp $hex $hex" \ + "rsp $caller_sp $caller_sp" \ + "r8 " \ + "r9 " \ + "r10 " \ + "r11 " \ + "r12 " \ + "r13 " \ + "r14 " \ + "r15 " \ + "rip $hex $hex $any" \ + "eflags " \ + "cs " \ + "ss " \ + "ds " \ + "es " \ + "fs " \ + "gs " \ + ] + + # Make sure that "info frame" doesn't crash. + gdb_test "info frame" "Stack level 1, .*in main.*" + + # ... and that neither does printing a pseudo + # register. + gdb_test "print /x \$ebp" " = $hex" "print pseudo register" + + # There's no way for the JIT reader API to support + # modifyiable values. + gdb_test "print \$rbp = -1" \ + "Attempt to assign to an unmodifiable value\." \ + "cannot assign to register" + } + } + } + + # Now unload the jit reader, and ensure that backtracing really + # doesn't work without it. + with_test_prefix "without jit-reader" { + gdb_test_no_output "jit-reader-unload ${jit_reader_bin}" \ + "jit-reader-unload" + + # Check that we're no longer using the JIT unwinder, and that + # the built-in unwinder cannot backtrace through the mangled + # stack pointer. + gdb_test "bt" \ + [multi_line \ + "Backtrace stopped: Cannot access memory at address $sp_after_mangling" \ + ] \ + "bt shows error" + + gdb_test "info frame" "Cannot access memory at address.*" \ + "info frame shows error" + info_registers_current_frame $sp_after_mangling + gdb_test "up" "Initial frame selected; you cannot go up\\." \ + "cannot go up" + } + + with_test_prefix "with jit-reader again" { + gdb_test_no_output "jit-reader-load ${jit_reader_bin}" "jit-reader-load" + + # Check that the jit unwinder manages to backtrace through + # the mangled stack pointer. + gdb_test "bt" \ + [multi_line \ + "#0 ${any} in jit_function_00 ${any}" \ + "#1 ${any} in main ${any}" \ + ] + } } jit_reader_test diff --git a/gdb/testsuite/gdb.base/jithost.c b/gdb/testsuite/gdb.base/jithost.c index 09ab377b926..afefc98be97 100644 --- a/gdb/testsuite/gdb.base/jithost.c +++ b/gdb/testsuite/gdb.base/jithost.c @@ -33,18 +33,32 @@ struct jit_code_entry only_entry; typedef void (jit_function_t) (); -int main (int argc, char **argv) +/* The code of the jit_function_00 function that is copied into an + mmapped buffer in the inferior at run time. + + The second instruction mangles the stack pointer, meaning that when + stopped at the third instruction, GDB needs assistance from the JIT + unwinder in order to be able to unwind successfully. */ +const unsigned char jit_function_00_code[] = { + 0xcc, /* int3 */ + 0x48, 0x83, 0xf4, 0xff, /* xor $0xffffffffffffffff, %rsp */ + 0x48, 0x83, 0xf4, 0xff, /* xor $0xffffffffffffffff, %rsp */ + 0xc3 /* ret */ +}; + +int +main (int argc, char **argv) { + struct jithost_abi *symfile; char *code = mmap (NULL, getpagesize (), PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); jit_function_t *function = (jit_function_t *) code; - code[0] = 0xcc; /* SIGTRAP */ - code[1] = 0xc3; /* RET */ + memcpy (code, jit_function_00_code, sizeof (jit_function_00_code)); - struct jithost_abi *symfile = malloc (sizeof (struct jithost_abi)); + symfile = malloc (sizeof (struct jithost_abi)); symfile->begin = code; - symfile->end = code + 2; + symfile->end = code + sizeof (jit_function_00_code); only_entry.symfile_addr = symfile; only_entry.symfile_size = sizeof (struct jithost_abi); diff --git a/gdb/testsuite/gdb.base/jitreader.c b/gdb/testsuite/gdb.base/jitreader.c index 850fb46c8de..c18e40d2b54 100644 --- a/gdb/testsuite/gdb.base/jitreader.c +++ b/gdb/testsuite/gdb.base/jitreader.c @@ -28,6 +28,7 @@ GDB_DECLARE_GPL_COMPATIBLE_READER; enum register_mapping { AMD64_RA = 16, + AMD64_RBP = 6, AMD64_RSP = 7, }; @@ -47,6 +48,11 @@ read_debug_info (struct gdb_reader_funcs *self, struct gdb_symtab *symtab = cbs->symtab_open (cbs, object, ""); GDB_CORE_ADDR begin = (GDB_CORE_ADDR) symfile->begin; GDB_CORE_ADDR end = (GDB_CORE_ADDR) symfile->end; + struct reader_state *state = (struct reader_state *) self->priv_data; + + /* Record the function's range, for the unwinder. */ + state->code_begin = begin; + state->code_end = end; cbs->block_open (cbs, symtab, NULL, begin, end, "jit_function_00"); @@ -91,11 +97,36 @@ read_register (struct gdb_unwind_callbacks *callbacks, int dw_reg, return 1; } +/* Read the stack pointer into *VALUE. IP is the address the inferior + is currently stopped at. Takes care of demangling the stack + pointer if necessary. */ + +static int +read_sp (struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs, + uintptr_t ip, uintptr_t *value) +{ + struct reader_state *state = (struct reader_state *) self->priv_data; + uintptr_t sp; + + if (!read_register (cbs, AMD64_RSP, &sp)) + return GDB_FAIL; + + /* If stopped at the instruction after the "xor $-1, %rsp", demangle + the stack pointer back. */ + if (ip == state->code_begin + 5) + sp ^= (uintptr_t) -1; + + *value = sp; + return GDB_SUCCESS; +} + static enum gdb_status unwind_frame (struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs) { const int word_size = sizeof (uintptr_t); - uintptr_t this_sp, this_ip, prev_ip, prev_sp; + uintptr_t prev_sp, this_sp; + uintptr_t prev_ip, this_ip; + uintptr_t prev_bp, this_bp; struct reader_state *state = (struct reader_state *) self->priv_data; if (!read_register (cbs, AMD64_RA, &this_ip)) @@ -104,15 +135,25 @@ unwind_frame (struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs) if (this_ip >= state->code_end || this_ip < state->code_begin) return GDB_FAIL; - if (!read_register (cbs, AMD64_RSP, &this_sp)) + /* Unwind RBP in order to make the unwinder that tries to unwind + from the just-unwound frame happy. */ + if (!read_register (cbs, AMD64_RBP, &this_bp)) return GDB_FAIL; + /* RBP is unmodified. */ + prev_bp = this_bp; - if (cbs->target_read (this_sp, &prev_ip, word_size) == GDB_FAIL) + /* Fetch the demangled stack pointer. */ + if (!read_sp (self, cbs, this_ip, &this_sp)) return GDB_FAIL; + /* The return address is saved on the stack. */ + if (cbs->target_read (this_sp, &prev_ip, word_size) == GDB_FAIL) + return GDB_FAIL; prev_sp = this_sp + word_size; + write_register (cbs, AMD64_RA, prev_ip); write_register (cbs, AMD64_RSP, prev_sp); + write_register (cbs, AMD64_RBP, prev_bp); return GDB_SUCCESS; } @@ -121,9 +162,11 @@ get_frame_id (struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs) { struct reader_state *state = (struct reader_state *) self->priv_data; struct gdb_frame_id frame_id; - + uintptr_t ip; uintptr_t sp; - read_register (cbs, AMD64_RSP, &sp); + + read_register (cbs, AMD64_RA, &ip); + read_sp (self, cbs, ip, &sp); frame_id.code_address = (GDB_CORE_ADDR) state->code_begin; frame_id.stack_address = (GDB_CORE_ADDR) sp; @@ -141,7 +184,7 @@ destroy_reader (struct gdb_reader_funcs *self) struct gdb_reader_funcs * gdb_init_reader (void) { - struct reader_state *state = malloc (sizeof (struct reader_state)); + struct reader_state *state = calloc (1, sizeof (struct reader_state)); struct gdb_reader_funcs *reader_funcs = malloc (sizeof (struct gdb_reader_funcs)); diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index b52fdd9c5d5..b7b8fad75cd 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -5435,19 +5435,24 @@ proc get_integer_valueof { exp default } { return ${val} } -proc get_hexadecimal_valueof { exp default } { +# Retrieve the value of EXP in the inferior, as an hexadecimal value +# (using "print /x"). DEFAULT is used as fallback if print fails. +# TEST is the test message to use. If can be ommitted, in which case +# a test message is built from EXP. + +proc get_hexadecimal_valueof { exp default {test ""} } { global gdb_prompt - send_gdb "print /x ${exp}\n" - set test "get hexadecimal valueof \"${exp}\"" - gdb_expect { + + if {$test == ""} { + set test "get hexadecimal valueof \"${exp}\"" + } + + set val ${default} + gdb_test_multiple "print /x ${exp}" $test { -re "\\$\[0-9\]* = (0x\[0-9a-zA-Z\]+).*$gdb_prompt $" { set val $expect_out(1,string) pass "$test" } - timeout { - set val ${default} - fail "$test (timeout)" - } } return ${val} } -- 2.30.2