This is the mail archive of the gdb@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Design of common agent library


Hi,
This is a follow-up work to Stan's description on common agent here

  Toward multicore GDB - Target agent
  http://sourceware.org/ml/gdb/2011-11/msg00014.html

This design is going to describe the components of "common agent", and
their interactions.  We can start from 2.7 - 2.10, and target platforms
are both 32-bit and 64-bit x86-linux.  Comments, thoughts and questions
are welcome.

I didn't name this agent, so I still use term "common agent" in this
design.  Personally, I name it as "next-generation debugging agent,
NGDA", and your thoughts on its name is welcome as well.

Table of Contents
=================
1 Overview
2 modules of common agent library
    2.1 configuration
    2.2 signal and signal handler
        2.2.1 signal translation
        2.2.2 signal handlers
    2.3 debug thread
    2.4 thread
    2.5 itset
    2.6 point
    2.7 tracepoint runtime
        2.7.1 trace frame related operations
        2.7.2 trace buffer management
        2.7.3 while stepping state
        2.7.4 data collection
        2.7.5 control
        2.7.6 maintenance
        2.7.7 misc
    2.8 byte code compiler
    2.9 backend
        2.9.1 byte code compiler backend
        2.9.2 fast tracepoint backend
        2.9.3 signal context backend and trap point backend
        2.9.4 misc
    2.10 inter-operation with gdb/gdbserver
        2.10.1 trace control and buffer management
        2.10.2 static tracepoint
3 inter-operation with in-process debugging


1 Overview
~~~~~~~~~~~

The overview of common agent library is described [here] by Stan.  It is
quite general, and here are some of my thoughts,

- common agent library should work with gdb and gdbserver,
- common agent library lays a foundation for in-process debugging and
out-of-process debugging,
  - An agent byte code interpreter,
  - Evaluation of breakpoint or tracepoint's condition,
  - Matching thread or core specific breakpoint or tracepoint,

- common agent library should accept connections from multiple types of
clients,

[here]: http://sourceware.org/ml/gdb/2011-11/msg00014.html

For the design, it is made as much as it can be, taking different cases
into account, and leaving enough room for the work in the future.

2 modules of common agent library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This chapter can be treated as the main body of API design.  Since
common agent library provide basic stuffs, so its functionalities should
be partitioned into different modules, so that different tools can use
just the parts they need.  We can divide agent into several modules as
below,


2.1 configuration
==================
Common agent library can work in multiple configurations with different
capabilities.  In each session, only one configuration is set in common
agent library.

Different configurations may _request_ different capabilities of agent.
 For example, one client needs to access inferior registers, while the
other client doesn't.


  struct agent_config
  {
    /* Each slot presents one capability of agent.  */
    int capas[AGENT_CAPA_LAST];

    /* Initialization routine.  */
    void (* init) (void);
  };

Set agent's configuration,


  /* Set agent's configuration via CONFIG.  */
  void
  agent_set_config (struct agent_config *config)

For example, when common agent library is used with gdb/gdbserver, it
should be configured as not to handle signal, however, when common agent
library is used in in-proc-debugging, common agent library should be
configured to handle signal.

2.2 signal and signal handler
==============================

In agent, we can process signals by dedicated signal handlers, so

 - breakpoint and slow (or trap) tracepoint can be supported, which
replies on handling /SIGTRAP/ properly,
 - thread control and management relies on signals as well,
 - accessing inferior register contents is done by accessing registers
on stack in signal handler.

First of all, here are two functions to translate native signals back
and forth to signals that agent knows,

2.2.1 signal translation
-------------------------

  /* Translate our native signal number into one of the ones that agent
     knows about.  */

  gdb_signal_t
  translate_from_native_signal (int sig)

  /* Translate a agent signal SIG into its native counterpart.   */
  int
  translate_to_native_signal (gdb_signal_t sig)

2.2.2 signal handlers
----------------------

Dedicated signal handlers are registered to handle signals.


  /* This is the main signal handler for the agent.  It has two main
     uses; first, to implement thread stop/resume behavior, and second,
     to catch program signals like SIGTRAP so they can be reported to
     GDB.  Stopped threads will sit in here indefinitely, only
     continuing when the dedicated resume signal is received.
     SIG is the signal being received.  */
  void
  agent_signal_handler (int sig, siginfo_t *info, void *ptr)

  /* The resumption handler doesn't have anything to do; the signal is
     just a way to get a suspended thread running again.  SIG is the signal
     being received.  */
  void
  agent_resumption_handler (int sig, siginfo_t *info, void *ptr)

  /* This function rejiggers the application's signal handling so that
     the debug thread can get control when signals are raised.  It is
     only needed if the debugger wants to use traps or interrupt the
     application.  */
  void
  agent_init_signal_handlers ()

2.3 debug thread
=================

If signals are handled in agent, a dedicated thread is created, as a
server, to process or consume the *stop events* sent from or produced by
signal handler.  We call this thread ~debug thread~ .  Here are the
producer and consumer of stop event,


  /* Report that TINFO stopped.  Used by the the inferior thread's
     signal handlers.  */
  void
  stop_event_enqueue (gdb_stub_thread_info_t *tinfo)

  /* Pop a stop event from the queue.  Used by the debug thread.  */
  gdb_stub_thread_info_t *
  stop_event_dequeue (void)

and when signal handler pushes events into pipe, and notify ~debug
thread~ via

  /* Put something in the pipe, so the debug thread wakes up.  */

  void
  debug_thread_notify (void)

and then ~debug thread~ is waken up, picks up event from queue and
process it.

One major task of ~debug thread~ is to control (stop or resume) threads
managed by agent.


  /* Stop all threads that are currently being managed by the agent.  */
  void
  agent_stop_all (void)

  /* Unsuspend any threads that were stopped by the agent.  */
  void
  agent_resume_all (void)
  /* Resume TINFO from the current PC, taking into account a step
     request from GDB, the need to step over any tracepoint, or to do
     'while-stepping' processing.  */
  void
  agent_proceed (gdb_stub_thread_info_t *tinfo)

2.4 thread
===========
In agent, we have to store and query information for a given thread, and
these information are stored in ~agent_thread_info_t~


  /* Have pthread_t PTH_ID added to the list of threads being
     debugged.  Return the info for new thread.  */
  agent_thread_info_t *
  agent_thread_info_add (pthread_t pth_id)

  /* Given a thread id, return the thread info for it.  */
  agent_thread_info_t *
  agent_thread_info_find (gdb_thread_t t);

  /* Given a Posix thread id PTH_ID, return the stub's thread info for
it.  */
  agent_thread_info_t *
  agent_thread_info_find_from_pthread (pthread_t pth_id)

  /* Remove a thread info TINFO and associated data structures.  */
  void
  agent_thread_info_remove (agent_thread_info_t *tinfo)

2.5 itset
==========
~itset~ means _inferior thread set_ , which presets a set of process
(inferior), threads and cores.  Some times, a specific ~itset~ is
assigned to a tracepoint or breakpoint, so tracepoint or breakpoint is
hit conditionally if current process, thread and core of matches with
~itset~ or ~ptc set~ in breakpoint or tracepoint.


  /* Returns true if ITSET contains THREAD.  */
  int
  itset_contains_thread (struct itset *itset,
                         struct agent_thread_info_t *thread)


2.6 point
==========

Both tracepoint and breakpoint have some in common, and we move them
into a struct named ~base_point_t~ , which can be regarded as a base
class of tracepoint and breakpoint.


  typedef struct base_point_t
  {
    /* Pointer to next in linked-list.  */
    struct base_point_t *next;

    /* The point type, such as breakpoint, fast tracepoint, tracepoint,
       and etc.  */
    gdb_point_t type;

    /* The breakpoint address.  */
    gdb_addr_t addr;

    /* Pointer to the agent expression that is the point's or
       breakpoint's conditional, or NULL if the tracepoint is
       unconditional.  */
    agent_expr_t *cond;

    /* Compile condition.  */
    void (*compile_condition) (struct base_point_t *point, void *data);

    /* Test condition of point is true.  */
    int (*condition_is_true) (struct base_point_t *point,
                             gdb_stub_thread_info_t *tinfo, void *data);

    /* Pointer to the itset of this point, and it is used for thread/core
       specific breakpoint or tracepoint.  */
    struct itset *itset;

  } base_point_t;

- Evaluate condition for point, see ~condition_is_true~ and
~compile_condition~ in ~base_point_t~ tracepoint's or breakpoint's
condition can be evaluated in target side,
- Check itset, and match with current process, thread and core.

* point insertion to and removal from inferior
  - breakpoint and tracepoint insertion and removal
  - jump pad installation for fast tracepoint


    /* Add a breakpoint or tracepoint.  TYPE is the type of breakpoint/
       watchpoint/tracepoint to remove.  ADDR is the address of the point.
       LENGTH is the length in bytes to write.  Return 0 for sucess
otherwise
       return non-zero.  */
    int
    agent_addpoint (gdb_point_t type, gdb_addr_t addr, gdb_size_t length,
                    uint64_t *handle_p)

    /* Remove a breakpoint or tracepoint.  TYPE is the type of breakpoint/
       watchpoint/tracepoint to remove.  ADDR is the address of the point.
       LENGTH is the length in bytes to write.  */

    int
    agent_rmpoint (gdb_point_t type, gdb_addr_t addr, gdb_size_t length,
                   uint64_t handle)


2.7 tracepoint runtime
=======================

One task of agent is to manage tracepoint related contents correctly in
runtime,

2.7.1 trace frame related operations
-------------------------------------

  /* Add a raw traceframe for the given tracepoint TPOINT, and return
this raw
     traceframe.  */
  static traceframe_t* traceframe_add (tracepoint_t *tpoint);

  /* Add a block of desired block size AMT to the traceframe TFRAME
currently
     being worked on.  */
  unsigned char * traceframe_add_block (traceframe_t *tframe, int amt);

  /* Given a traceframe number NUM, find the NUMth traceframe in the
     buffer.  */
  traceframe_t* traceframe_find (int num);

  /* Look for the block of type TYPE_WANTED in the trameframe starting
     at DATABASE of DATASIZE bytes long.  TFNUM is the traceframe
     number.  */
  static unsigned char *
  traceframe_find_block_type (tracerun_t *trun,
                              unsigned char *database, unsigned int
datasize,
                              int tfnum, char type_wanted);

  /* Get a block of registers from the given traceframe TFRAME.  Return
     the pointer to register block.  */
  static unsigned char *
  traceframe_find_regblock (tracerun_t *trun,
                            traceframe_t *tframe, int tfnum);

  /* Get the value of the PC from the given traceframe TFRAME.  */
  static gdb_addr_t
  traceframe_get_pc (traceframe_t *tframe);

  /* Get the address (tracepoint or collected PC) of a traceframe
TFRAME.  */
  gdb_addr_t traceframe_get_address (traceframe_t *tframe);

2.7.2 trace buffer management
------------------------------

  static void
  agent_trace_buffer_init (char *buf, int bufsize)

  /* Empty out an already-existing trace buffer.  */
  void
  agent_trace_buffer_clear ()

  /* Return the total free space.  This is not necessarily the largest
     block we can allocate, because of the two-part case.  */
  int
  agent_trace_buffer_free_space (tracerun_t *trun)

2.7.3 while stepping state
---------------------------

  /* Call this when thread TINFO has hit the tracepoint defined by
     TP_NUMBER and TP_ADDRESS, and that tracepoint has a while-stepping
     action.  */
  static void
  while_stepping_state_add (gdb_stub_thread_info_t *tinfo,
                            int tp_number, gdb_addr_t tp_address)
  /* Release the while-stepping collecting state WSTEP.  */
  static void
  while_stepping_state_release (tracepoint_wstep_state_t *wstep)

  /* Release all while-stepping collecting states associated currently
     associated with thread TINFO.  */
  void
  while_stepping_state_release_list (gdb_stub_thread_info_t *tinfo)


2.7.4 data collection
----------------------

  /* This routine is designed to be called from the jump pads of fast
     tracepoint TPOINT and RAW_REGS is a pointer to the saved register
block.  */
  void
  gdb_agent_gdb_collect (tracepoint_t *tpoint, unsigned char *raw_regs);

  /* Do tracepoint data collection at a step in the while-stepping loop.  */
  static void
  collect_data_at_step (gdb_stub_thread_info_t *tinfo,
                        tracepoint_t *tpoint, int current_step);

  /* Create a trace frame for the hit of the given tracepoint TPOINT in the
     given thread TINFO.  */
  static void
  tracepoint_collect_data (gdb_stub_thread_info_t *tinfo,
                           tracepoint_t *tpoint,
                           unsigned char *raw_regs)

  /* If TINFO was handling a 'while-stepping' action, the step has
     finished, so collect any step data needed, and check if any more
     steps are required.  Return true if the thread was indeed
     collecting tracepoint data, false otherwise.  */
  int
  tracepoint_finished_step (gdb_stub_thread_info_t *tinfo);

There are multiple types of actions, and we define base action, and concrete
actions inherit this.


  typedef struct tracepoint_action_t {
    char type;

    /* Run action SELF.  */
    void (*run) (struct tracepoint_action_t *self,
gdb_stub_thread_info_t *tinfo,
                 struct tracepoint_t *tpoint, struct traceframe_t *tframe);

  } tracepoint_action_t;

Different concrete types of action implement different ~run~ function.

2.7.5 control
--------------

  /* Stop the tracing run.  DISCONN is 1 if stopping due to disconnection,
     0 if stopping due to tstop.  */
  void
  gdb_stop_tracing (int disconn)

2.7.6 maintainance
-------------------

  /* Restore the program to its pre-tracing state.   */
  void
  tracepoints_clear_installed ();

2.7.7 misc
-----------

  /* Return true if TINFO just hit a tracepoint.  Collect data if so.   */
  int
  tracepoint_was_hit (gdb_stub_thread_info_t *tinfo, int
gdb_stub_nonstop_mode);

2.8 byte code compiler
=======================
Agent can compile byte code to native code or interpret/evaluate byte code.

  /* Given an agent expression AEXPR, turn it into native code.  */

  enum eval_result_type
  agent_expr_compile_bytecodes (agent_expr_t *aexpr,
                                int64_t (*get_tsv_value_pself) (int num),
                                void (*set_tsv_value) (int num, int64_t
val))

  /* translate string to agent expression back and forth.  */
  agent_expr_t *
  agent_expr_parse (const char **actparm)
  char *
  agent_expr_unparse (agent_expr_t *aexpr)

  /* The agent expression evaluator, as specified by the GDB docs. Evaluate
     agent expression AEXPR for thread TINFO.  RAW_REGS points to block of
     registers.  TFRAME is a traceframe to get any trace data.  Evaluation
     result value is put in RSLT.  It returns 0 if everything went OK, and
     a nonzero error code otherwise.  */
  enum eval_result_type
  agent_expr_eval (gdb_stub_thread_info_t *tinfo,
                   unsigned char *raw_regs,
                   traceframe_t *tframe,
                   agent_expr_t *aexpr,
                   uint64_t *rslt)

2.9 backend
============
Generally speaking, backend can be regarded as a combination of ~OS~ and
~target~ processor.  ~backend~ is composed by the following parts,
 - byte code compiler backend
 - fast tracepoint backend
 - trap point backend
 - signal context backend,
 - misc

2.9.1 byte code compiler backend
---------------------------------
It is to emit native instructions for each byte code instruction.


  struct bytecode_compiler_emit_backend
  {
    void (*emit_prologue) (void);
    void (*emit_epilogue) (void);
    void (*emit_add) (void);
    void (*emit_sub) (void);
    void (*emit_mul) (void);
    void (*emit_lsh) (void);
    void (*emit_rsh_signed) (void);
    void (*emit_rsh_unsigned) (void);
    void (*emit_ext) (int arg);
    void (*emit_log_not) (void);
    void (*emit_bit_and) (void);
    void (*emit_bit_or) (void);
    void (*emit_bit_xor) (void);
    void (*emit_bit_not) (void);
    void (*emit_equal) (void);
    void (*emit_less_signed) (void);
    void (*emit_less_unsigned) (void);
    void (*emit_ref) (int size,
                      int (*agent_mem_read_to) (unsigned char *,
                                                gdb_addr_t from,
gdb_size_t));
    void (*emit_if_goto) (int *offset_p, int *size_p);
    void (*emit_goto) (int *offset_p, int *size_p);

    void (*write_goto_address) (unsigned char *from, unsigned char *to,
int size);

    void (*emit_const) (int64_t num);

    void (*emit_call) (void *fn);

    void (*emit_reg) (int reg);
    void (*emit_pop) (void);
    void (*emit_stack_flush) (void);
    void (*emit_zero_ext) (int arg);
    void (*emit_swap) (void);
    void (*emit_stack_adjust) (int n);

    void (*emit_int_call_1) (int64_t (*fn) (int), int arg1);
    void (*emit_void_call_2) (void (*fn) (int, int64_t), int arg1);

    void (*emit_eq_goto) (int *offset_p, int *size_p);
    void (*emit_ne_goto) (int *offset_p, int *size_p);
    void (*emit_lt_goto) (int *offset_p, int *size_p);
    void (*emit_le_goto) (int *offset_p, int *size_p);
    void (*emit_gt_goto) (int *offset_p, int *size_p);
    void (*emit_ge_goto) (int *offset_p, int *size_p);
  };

2.9.2 fast tracepoint backend
------------------------------

  struct fast_tracepoint_backend
  {
    /* Determine needs trampoline buffer or not.   */
    int need_tramploline_buffer_p;

    /* Move a block of saved registers REGS (typically located on the stack)
       into a thread TINFO's register block.  */
    void (*get_fast_tracepoint_regs) (gdb_stub_thread_info_t *tinfo,
                                      unsigned char *regs);
  };

2.9.3 signal context backend and trap point backend
----------------------------------------------------

So far, breakpoint and slow tracepoint can be named as ~trap point~ ,
because they reply on _SIGTRAP_ .  Agent should insert special
instructions into inferior to trigger _SIGTRAP_ .


  struct trap_point_backend
  {
    /* Length in bytes of breakpoint instruction.  */
    int breakpoint_size;

    int decr_pc_after_break;

    /* Breakpoint instruction.  */
    unsigned char *breakpoint_insn;
  };

  struct signal_context_backend
  {
    /* Extract register contents from CONTEXT and store them into TINFO.  */
    void (*get_signal_context_regs) (gdb_stub_thread_info_t *tinfo,
                                     ucontext_t *context);
    /* Store register contentes from TINFO to CONTEXT.  */
    void (*put_signal_context_regs) (gdb_stub_thread_info_t *tinfo,
                                     ucontext_t *context);

    uint64_t (*get_reg) (gdb_stub_thread_info_t *tinfo, int regnum);
    void (*set_reg) (gdb_stub_thread_info_t *tinfo, int regnum, uint64_t
val);

    /* Get the register number of PC.  */
    int (*get_pc_reg_num) ();

    struct trap_point_backend breakpoint;
  }

2.9.4 misc
-----------

  /* Maximum length of instruction.  */
  int max_insn_length;

  /* Get the core number of thread TINFO is running on.  */
  int (*core_of_thread) (gdb_stub_thread_info_t *);

2.10 inter-operation with gdb/gdbserver
========================================

The common agent library design above doesn't take interaction with
gdb/gdbserver into account.  The interaction is mainly done by some
control variables and sockets, which is quite independent of gdb or
gdbserver.  Common agent library doesn't bind itself to gdb or
gdbserver.  Any tools can talk with common agent library by means of
accessing these control variables.  This part should be put into a
separate module.

2.10.1 trace control and buffer management
-------------------------------------------
The tasks of this module are

+ inter-operate with gdbserver on some shared control variables and
functions,
  + gdb_agent_trace_buffer_lo, gdb_agent_trace_buffer_hi,
gdb_agent_trace_buffer_is_full,
  + gdb_agent_expr_eval_result
  + gdb_agent_error_tracepoint, gdb_agent_tracepoints,
  + gdb_agent_tracing,
  + gdb_agent_trace_state_variables
  + gdb_agent_traceframes_created
  + gdb_agent_gdb_jump_pad_buffer, gdb_agent_gdb_jump_pad_buffer_end,
+ follow the mechanism to control trace buffer between libinproctrace.so
and gdbserver
  + gdb_agent_trace_buffer_ctrl_curr, gdb_agent_trace_buffer_ctrl
  + gdb_agent_traceframe_write_count, gdb_agent_traceframe_read_count,
  + gdb_agent_stop_tracing, gdb_agent_flush_trace_buffer,
gdb_agent_about_to_request_buffer_space,
+ keep the same struct layout of tracepoint for both gdbserver side and
agent site.

2.10.2 static tracepoint
-------------------------
This module adds static tracepoint support for LTTng/UST based static
tracepoints.  This module includes

  + Synchronization with ~gdbserver~ .  The command exchange and
synchronization between gdbserver and common agent is done by a socket
communication and a piece of shared buffer.
  + Translate commands from gdbserver, and invoke corresponding
~libust.so~ APIs.

Shared control variables are
 + gdb_agent_ust_loaded,
 + gdb_agent_helper_thread_id,
 + gdb_agent_cmd_buf,

3 inter-operation with in-process debugging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Think a little bit further, more operations can be done in the
inferior's process, such as breakpoint condition evaluation, and
debugging overhead can be reduced further.  It is a long-term goal, but
we can think of it on the module granularity.

+ a module to handle RSP, and translate packets into commands,
+ a module to manage in-process trace buffer, which is more flexible
than out-of-process trace buffer management,

-- 
Yao (éå)


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]