Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: "Botond Dénes" <bdenes@scylladb.com>
To: gdb-patches@sourceware.org
Subject: Re: [RFC PATCH v1] Support inspecting green threads
Date: Fri, 14 Aug 2020 18:27:15 +0300	[thread overview]
Message-ID: <921f774bd8b173d71033c6d1757a51a75342e204.camel@scylladb.com> (raw)
In-Reply-To: <20200814152557.2128464-1-bdenes@scylladb.com>

On Fri, 2020-08-14 at 18:25 +0300, Botond Dénes wrote:
> Some applications use green threads that have their own stacks. One
> such
> example is Scylla [1] which is using the seastar framework [2], which
> provides green threads in the form of seastar::thread [3]. These
> threads
> are created with `setcontext()` and later we switch in/out using
> `setjmp()`/`longjmp()`
> 
> When debugging Scylla (or any other seastar application) it is
> essential
> to be able to inspect the stacks of these green threads. For this we
> used a python extension, which essentially emulates a `longjmp()`
> call,
> restoring the registers from a `jmpbuf`. This approach worked fine
> for
> some time, however we started looking for another way to do this
> several reasons:
> * Writing to registers is dangerous, any bug in the python script or
> GDB
>   can result in crashing the debugged live application. Very
> undesirable
>   when having to debug on a production server.
> * Doesn't work in coredumps, where registers are not writable.
> * Stopped working for some time, resulting in a crash of GDB.
> 
> This patch aims to solve this problem by providing a family of
> commands
> which allow inspecting green threads.
> * fiber view - allows inspecting a green thread. This command takes a
>   list of registers as its arguments. More on these later.
> * fiber reset - reset the viewed fiber to the stack proper.
> 
> The implementation turned out to be quite simple. The command just
> propagates the registers to override to the sentinel frame, which
> will
> return the overridden values for registers of interest, instead of
> consulting the current register cache, thus simulating a register
> restore. This has two important advantages: it works in coredumps and
> it
> is save. If the user passes wrong register values, it might crash
> gdb,
> but as the application state is not mangled with, it should survive
> unscathed.
> 
> TODO:
> * Better interface for the command: instead of a fixed set of
> registers
>   (those that happen to be in glibc's jmpbuf on amd64), allow the
> user
>   to pass any amount of named registers to override.
> * More documentation.
> * More testing.
> * Code style.
> 
> I apologize for the mixed code style, but I wanted to probe my
> approach
> first, before spending more time on polishing.


Forgot to add links:
[1] https://github.com/scylladb/scylla
[2] https://github.com/scylladb/seastar
[3] http://docs.seastar.io/master/group__thread-module.html

> 
> Signed-off-by: Botond Dénes <bdenes@scylladb.com>
> ---
>  gdb/frame.c          | 16 ++++++++++++-
>  gdb/frame.h          |  7 ++++++
>  gdb/sentinel-frame.c | 30 ++++++++++++++++++++++---
>  gdb/sentinel-frame.h |  2 +-
>  gdb/stack.c          | 53
> ++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 103 insertions(+), 5 deletions(-)
> 
> diff --git a/gdb/frame.c b/gdb/frame.c
> index f65d43f9fc..17c139cfe9 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -53,6 +53,8 @@
>  
>  static struct frame_info *sentinel_frame;
>  
> +static struct saved_registers fiber_regs_override;
> +
>  /* Number of calls to reinit_frame_cache.  */
>  static unsigned int frame_cache_generation = 0;
>  
> @@ -1570,7 +1572,7 @@ create_sentinel_frame (struct program_space
> *pspace, struct regcache *regcache)
>    /* Explicitly initialize the sentinel frame's cache.  Provide it
>       with the underlying regcache.  In the future additional
>       information, such as the frame's thread will be added.  */
> -  frame->prologue_cache = sentinel_frame_cache (regcache);
> +  frame->prologue_cache = sentinel_frame_cache (regcache,
> &fiber_regs_override);
>    /* For the moment there is only one sentinel frame
> implementation.  */
>    frame->unwind = &sentinel_frame_unwind;
>    /* Link this frame back to itself.  The frame is self referential
> @@ -2934,6 +2936,18 @@ frame_prepare_for_sniffer (struct frame_info
> *frame,
>    frame->unwind = unwind;
>  }
>  
> +void set_fiber_register_override(saved_registers regs_override)
> +{
> +  fiber_regs_override = std::move(regs_override);
> +  reinit_frame_cache();
> +}
> +
> +void reset_fiber_register_override()
> +{
> +  fiber_regs_override.reg_map.clear();
> +  reinit_frame_cache();
> +}
> +
>  static struct cmd_list_element *set_backtrace_cmdlist;
>  static struct cmd_list_element *show_backtrace_cmdlist;
>  
> diff --git a/gdb/frame.h b/gdb/frame.h
> index 1c6afad1ae..a8ddd8af07 100644
> --- a/gdb/frame.h
> +++ b/gdb/frame.h
> @@ -71,6 +71,7 @@
>  
>  #include "language.h"
>  #include "cli/cli-option.h"
> +#include <map>
>  
>  struct symtab_and_line;
>  struct frame_unwind;
> @@ -955,5 +956,11 @@ extern void set_frame_previous_pc_masked (struct
> frame_info *frame);
>  
>  extern bool get_frame_pc_masked (const struct frame_info *frame);
>  
> +struct saved_registers {
> +    std::map<int, const std::vector<gdb_byte>> reg_map;
> +};
> +
> +extern void set_fiber_register_override(saved_registers
> regs_override);
> +extern void reset_fiber_register_override();
>  
>  #endif /* !defined (FRAME_H)  */
> diff --git a/gdb/sentinel-frame.c b/gdb/sentinel-frame.c
> index 1d601ca27b..b669a36656 100644
> --- a/gdb/sentinel-frame.c
> +++ b/gdb/sentinel-frame.c
> @@ -23,19 +23,23 @@
>  #include "sentinel-frame.h"
>  #include "inferior.h"
>  #include "frame-unwind.h"
> +#include "frame.h"
>  
>  struct frame_unwind_cache
>  {
>    struct regcache *regcache;
> +  struct saved_registers *saved_regs;
>  };
>  
>  void *
> -sentinel_frame_cache (struct regcache *regcache)
> +sentinel_frame_cache (struct regcache *regcache, struct
> saved_registers *saved_regs)
>  {
>    struct frame_unwind_cache *cache = 
>      FRAME_OBSTACK_ZALLOC (struct frame_unwind_cache);
>  
>    cache->regcache = regcache;
> +  cache->saved_regs = saved_regs;
> +
>    return cache;
>  }
>  
> @@ -50,8 +54,28 @@ sentinel_frame_prev_register (struct frame_info
> *this_frame,
>      = (struct frame_unwind_cache *) *this_prologue_cache;
>    struct value *value;
>  
> -  value = cache->regcache->cooked_read_value (regnum);
> -  VALUE_NEXT_FRAME_ID (value) = sentinel_frame_id;
> +  if (!cache->saved_regs) {
> +      value = cache->regcache->cooked_read_value (regnum);
> +      VALUE_NEXT_FRAME_ID (value) = sentinel_frame_id;
> +      return value;
> +  }
> +
> +  auto it = cache->saved_regs->reg_map.find(regnum);
> +
> +  if (it == cache->saved_regs->reg_map.end())
> +    {
> +      value = cache->regcache->cooked_read_value (regnum);
> +      VALUE_NEXT_FRAME_ID (value) = sentinel_frame_id;
> +      return value;
> +    }
> +
> +  auto* arch = cache->regcache->arch ();
> +
> +  value = allocate_value (register_type (arch, regnum));
> +  VALUE_LVAL (value) = lval_register;
> +  VALUE_REGNUM (value) = regnum;
> +
> +  memcpy(value_contents_raw (value), it->second.data(), it-
> >second.size());
>  
>    return value;
>  }
> diff --git a/gdb/sentinel-frame.h b/gdb/sentinel-frame.h
> index cef5fd21bd..20fd8d8d81 100644
> --- a/gdb/sentinel-frame.h
> +++ b/gdb/sentinel-frame.h
> @@ -30,7 +30,7 @@ struct regcache;
>  /* Pump prime the sentinel frame's cache.  Since this needs the
>     REGCACHE provide that here.  */
>  
> -extern void *sentinel_frame_cache (struct regcache *regcache);
> +extern void *sentinel_frame_cache (struct regcache *regcache, struct
> saved_registers *saved_regs = nullptr);
>  
>  /* At present there is only one type of sentinel frame.  */
>  
> diff --git a/gdb/stack.c b/gdb/stack.c
> index 265e764dc2..bb22d01813 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -55,6 +55,9 @@
>  #include "gdbsupport/def-vector.h"
>  #include "cli/cli-option.h"
>  #include "cli/cli-style.h"
> +#include "user-regs.h"
> +
> +#include <sstream>
>  
>  /* The possible choices of "set print frame-arguments", and the
> value
>     of this setting.  */
> @@ -3291,6 +3294,41 @@ find_frame_for_address (CORE_ADDR address)
>    return NULL;
>  }
>  
> +static void
> +fiber_base_command (const char *arg, int from_tty)
> +{
> +}
> +
> +static void
> +fiber_view_command (const char *arg, int from_tty)
> +{
> +  saved_registers regs_override;
> +
> +  std::stringstream ss(arg, std::ios_base::in);
> +
> +  struct gdbarch *gdbarch = get_frame_arch (get_current_frame());
> +
> +  const static char* jmpbuf_regs[] = {"rbx", "rbp", "r12", "r13",
> "r14", "r15", "rsp", "rip"};
> +
> +  for (auto reg : jmpbuf_regs) {
> +      uint64_t val;
> +      ss >> std::hex >> val;
> +
> +      const auto regnum = user_reg_map_name_to_regnum (gdbarch, reg,
> strlen(reg));
> +
> +      std::vector<gdb_byte> raw_val(sizeof(val), 0);
> +      memcpy(raw_val.data(), reinterpret_cast<gdb_byte*>(&val),
> sizeof(val));
> +      regs_override.reg_map.emplace(regnum, std::move(raw_val));
> +  }
> +
> +  set_fiber_register_override(regs_override);
> +}
> +
> +static void
> +fiber_reset_command (const char *arg, int from_tty)
> +{
> +    reset_fiber_register_override();
> +}
>  
>  
>  /* Commands with a prefix of `frame apply'.  */
> @@ -3305,6 +3343,8 @@ static struct cmd_list_element
> *select_frame_cmd_list = NULL;
>  /* Commands with a prefix of `info frame'.  */
>  static struct cmd_list_element *info_frame_cmd_list = NULL;
>  
> +static struct cmd_list_element *fiber_cmd_list = NULL;
> +
>  void _initialize_stack ();
>  void
>  _initialize_stack ()
> @@ -3597,6 +3637,19 @@ source line."),
>  			        NULL,
>  			        show_disassemble_next_line,
>  			        &setlist, &showlist);
> +
> +  add_prefix_cmd ("fiber", class_stack, &fiber_base_command,
> +		  _("Inspect or change the currently viewed fiber.\n"),
> +		  &fiber_cmd_list, "fiber", 1, &cmdlist);
> +
> +  add_cmd ("view", class_stack, &fiber_view_command,
> +	   _("Select the fiber to view.\n"),
> +	   &fiber_cmd_list);
> +
> +  add_cmd ("reset", class_stack, &fiber_reset_command,
> +	   _("Reset to the currently viewed fiber.\n"),
> +	   &fiber_cmd_list);
> +
>    disassemble_next_line = AUTO_BOOLEAN_FALSE;
>  
>    gdb::option::add_setshow_cmds_for_options




  reply	other threads:[~2020-08-14 15:27 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-14 15:25 Botond Dénes
2020-08-14 15:27 ` Botond Dénes [this message]
2020-08-14 15:33 ` H.J. Lu
2020-08-17 21:34 ` Tom Tromey
2020-08-19 12:14   ` Botond Dénes
2020-09-10 21:24     ` Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=921f774bd8b173d71033c6d1757a51a75342e204.camel@scylladb.com \
    --to=bdenes@scylladb.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox