| Index: gdb/gdbserver/ax.c
|
| diff --git a/gdb/gdbserver/ax.c b/gdb/gdbserver/ax.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..70e93222e0a2968f71f8ae75cf08e1f66fa7c8e0
|
| --- /dev/null
|
| +++ b/gdb/gdbserver/ax.c
|
| @@ -0,0 +1,1346 @@
|
| +/* Agent expression code for remote server.
|
| + Copyright (C) 2009-2012 Free Software Foundation, Inc.
|
| +
|
| + This file is part of GDB.
|
| +
|
| + This program is free software; you can redistribute it and/or modify
|
| + it under the terms of the GNU General Public License as published by
|
| + the Free Software Foundation; either version 3 of the License, or
|
| + (at your option) any later version.
|
| +
|
| + This program is distributed in the hope that it will be useful,
|
| + but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| + GNU General Public License for more details.
|
| +
|
| + You should have received a copy of the GNU General Public License
|
| + along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
| +
|
| +#include "server.h"
|
| +#include "ax.h"
|
| +#include "format.h"
|
| +
|
| +static void ax_vdebug (const char *, ...) ATTR_FORMAT (printf, 1, 2);
|
| +
|
| +#ifdef IN_PROCESS_AGENT
|
| +int debug_agent = 0;
|
| +#endif
|
| +
|
| +static void
|
| +ax_vdebug (const char *fmt, ...)
|
| +{
|
| + char buf[1024];
|
| + va_list ap;
|
| +
|
| + va_start (ap, fmt);
|
| + vsprintf (buf, fmt, ap);
|
| + fprintf (stderr, PROG "/ax: %s\n", buf);
|
| + va_end (ap);
|
| +}
|
| +
|
| +#define ax_debug_1(level, fmt, args...) \
|
| + do { \
|
| + if (level <= debug_threads) \
|
| + ax_vdebug ((fmt), ##args); \
|
| + } while (0)
|
| +
|
| +#define ax_debug(FMT, args...) \
|
| + ax_debug_1 (1, FMT, ##args)
|
| +
|
| +/* This enum must exactly match what is documented in
|
| + gdb/doc/agentexpr.texi, including all the numerical values. */
|
| +
|
| +enum gdb_agent_op
|
| + {
|
| +#define DEFOP(NAME, SIZE, DATA_SIZE, CONSUMED, PRODUCED, VALUE) \
|
| + gdb_agent_op_ ## NAME = VALUE,
|
| +#include "ax.def"
|
| +#undef DEFOP
|
| + gdb_agent_op_last
|
| + };
|
| +
|
| +static const char *gdb_agent_op_names [gdb_agent_op_last] =
|
| + {
|
| + "?undef?"
|
| +#define DEFOP(NAME, SIZE, DATA_SIZE, CONSUMED, PRODUCED, VALUE) , # NAME
|
| +#include "ax.def"
|
| +#undef DEFOP
|
| + };
|
| +
|
| +static const unsigned char gdb_agent_op_sizes [gdb_agent_op_last] =
|
| + {
|
| + 0
|
| +#define DEFOP(NAME, SIZE, DATA_SIZE, CONSUMED, PRODUCED, VALUE) , SIZE
|
| +#include "ax.def"
|
| +#undef DEFOP
|
| + };
|
| +
|
| +/* A wrapper for gdb_agent_op_names that does some bounds-checking. */
|
| +
|
| +static const char *
|
| +gdb_agent_op_name (int op)
|
| +{
|
| + if (op < 0 || op >= gdb_agent_op_last || gdb_agent_op_names[op] == NULL)
|
| + return "?undef?";
|
| + return gdb_agent_op_names[op];
|
| +}
|
| +
|
| +#ifndef IN_PROCESS_AGENT
|
| +
|
| +/* The packet form of an agent expression consists of an 'X', number
|
| + of bytes in expression, a comma, and then the bytes. */
|
| +
|
| +struct agent_expr *
|
| +gdb_parse_agent_expr (char **actparm)
|
| +{
|
| + char *act = *actparm;
|
| + ULONGEST xlen;
|
| + struct agent_expr *aexpr;
|
| +
|
| + ++act; /* skip the X */
|
| + act = unpack_varlen_hex (act, &xlen);
|
| + ++act; /* skip a comma */
|
| + aexpr = xmalloc (sizeof (struct agent_expr));
|
| + aexpr->length = xlen;
|
| + aexpr->bytes = xmalloc (xlen);
|
| + convert_ascii_to_int (act, aexpr->bytes, xlen);
|
| + *actparm = act + (xlen * 2);
|
| + return aexpr;
|
| +}
|
| +
|
| +/* Convert the bytes of an agent expression back into hex digits, so
|
| + they can be printed or uploaded. This allocates the buffer,
|
| + callers should free when they are done with it. */
|
| +
|
| +char *
|
| +gdb_unparse_agent_expr (struct agent_expr *aexpr)
|
| +{
|
| + char *rslt;
|
| +
|
| + rslt = xmalloc (2 * aexpr->length + 1);
|
| + convert_int_to_ascii (aexpr->bytes, rslt, aexpr->length);
|
| + return rslt;
|
| +}
|
| +
|
| +/* Bytecode compilation. */
|
| +
|
| +CORE_ADDR current_insn_ptr;
|
| +
|
| +int emit_error;
|
| +
|
| +struct bytecode_address
|
| +{
|
| + int pc;
|
| + CORE_ADDR address;
|
| + int goto_pc;
|
| + /* Offset and size of field to be modified in the goto block. */
|
| + int from_offset, from_size;
|
| + struct bytecode_address *next;
|
| +} *bytecode_address_table;
|
| +
|
| +void
|
| +emit_prologue (void)
|
| +{
|
| + target_emit_ops ()->emit_prologue ();
|
| +}
|
| +
|
| +void
|
| +emit_epilogue (void)
|
| +{
|
| + target_emit_ops ()->emit_epilogue ();
|
| +}
|
| +
|
| +static void
|
| +emit_add (void)
|
| +{
|
| + target_emit_ops ()->emit_add ();
|
| +}
|
| +
|
| +static void
|
| +emit_sub (void)
|
| +{
|
| + target_emit_ops ()->emit_sub ();
|
| +}
|
| +
|
| +static void
|
| +emit_mul (void)
|
| +{
|
| + target_emit_ops ()->emit_mul ();
|
| +}
|
| +
|
| +static void
|
| +emit_lsh (void)
|
| +{
|
| + target_emit_ops ()->emit_lsh ();
|
| +}
|
| +
|
| +static void
|
| +emit_rsh_signed (void)
|
| +{
|
| + target_emit_ops ()->emit_rsh_signed ();
|
| +}
|
| +
|
| +static void
|
| +emit_rsh_unsigned (void)
|
| +{
|
| + target_emit_ops ()->emit_rsh_unsigned ();
|
| +}
|
| +
|
| +static void
|
| +emit_ext (int arg)
|
| +{
|
| + target_emit_ops ()->emit_ext (arg);
|
| +}
|
| +
|
| +static void
|
| +emit_log_not (void)
|
| +{
|
| + target_emit_ops ()->emit_log_not ();
|
| +}
|
| +
|
| +static void
|
| +emit_bit_and (void)
|
| +{
|
| + target_emit_ops ()->emit_bit_and ();
|
| +}
|
| +
|
| +static void
|
| +emit_bit_or (void)
|
| +{
|
| + target_emit_ops ()->emit_bit_or ();
|
| +}
|
| +
|
| +static void
|
| +emit_bit_xor (void)
|
| +{
|
| + target_emit_ops ()->emit_bit_xor ();
|
| +}
|
| +
|
| +static void
|
| +emit_bit_not (void)
|
| +{
|
| + target_emit_ops ()->emit_bit_not ();
|
| +}
|
| +
|
| +static void
|
| +emit_equal (void)
|
| +{
|
| + target_emit_ops ()->emit_equal ();
|
| +}
|
| +
|
| +static void
|
| +emit_less_signed (void)
|
| +{
|
| + target_emit_ops ()->emit_less_signed ();
|
| +}
|
| +
|
| +static void
|
| +emit_less_unsigned (void)
|
| +{
|
| + target_emit_ops ()->emit_less_unsigned ();
|
| +}
|
| +
|
| +static void
|
| +emit_ref (int size)
|
| +{
|
| + target_emit_ops ()->emit_ref (size);
|
| +}
|
| +
|
| +static void
|
| +emit_if_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_if_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +write_goto_address (CORE_ADDR from, CORE_ADDR to, int size)
|
| +{
|
| + target_emit_ops ()->write_goto_address (from, to, size);
|
| +}
|
| +
|
| +static void
|
| +emit_const (LONGEST num)
|
| +{
|
| + target_emit_ops ()->emit_const (num);
|
| +}
|
| +
|
| +static void
|
| +emit_reg (int reg)
|
| +{
|
| + target_emit_ops ()->emit_reg (reg);
|
| +}
|
| +
|
| +static void
|
| +emit_pop (void)
|
| +{
|
| + target_emit_ops ()->emit_pop ();
|
| +}
|
| +
|
| +static void
|
| +emit_stack_flush (void)
|
| +{
|
| + target_emit_ops ()->emit_stack_flush ();
|
| +}
|
| +
|
| +static void
|
| +emit_zero_ext (int arg)
|
| +{
|
| + target_emit_ops ()->emit_zero_ext (arg);
|
| +}
|
| +
|
| +static void
|
| +emit_swap (void)
|
| +{
|
| + target_emit_ops ()->emit_swap ();
|
| +}
|
| +
|
| +static void
|
| +emit_stack_adjust (int n)
|
| +{
|
| + target_emit_ops ()->emit_stack_adjust (n);
|
| +}
|
| +
|
| +/* FN's prototype is `LONGEST(*fn)(int)'. */
|
| +
|
| +static void
|
| +emit_int_call_1 (CORE_ADDR fn, int arg1)
|
| +{
|
| + target_emit_ops ()->emit_int_call_1 (fn, arg1);
|
| +}
|
| +
|
| +/* FN's prototype is `void(*fn)(int,LONGEST)'. */
|
| +
|
| +static void
|
| +emit_void_call_2 (CORE_ADDR fn, int arg1)
|
| +{
|
| + target_emit_ops ()->emit_void_call_2 (fn, arg1);
|
| +}
|
| +
|
| +static void
|
| +emit_eq_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_eq_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_ne_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_ne_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_lt_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_lt_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_ge_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_ge_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_gt_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_gt_goto (offset_p, size_p);
|
| +}
|
| +
|
| +static void
|
| +emit_le_goto (int *offset_p, int *size_p)
|
| +{
|
| + target_emit_ops ()->emit_le_goto (offset_p, size_p);
|
| +}
|
| +
|
| +/* Scan an agent expression for any evidence that the given PC is the
|
| + target of a jump bytecode in the expression. */
|
| +
|
| +int
|
| +is_goto_target (struct agent_expr *aexpr, int pc)
|
| +{
|
| + int i;
|
| + unsigned char op;
|
| +
|
| + for (i = 0; i < aexpr->length; i += 1 + gdb_agent_op_sizes[op])
|
| + {
|
| + op = aexpr->bytes[i];
|
| +
|
| + if (op == gdb_agent_op_goto || op == gdb_agent_op_if_goto)
|
| + {
|
| + int target = (aexpr->bytes[i + 1] << 8) + aexpr->bytes[i + 2];
|
| + if (target == pc)
|
| + return 1;
|
| + }
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* Given an agent expression, turn it into native code. */
|
| +
|
| +enum eval_result_type
|
| +compile_bytecodes (struct agent_expr *aexpr)
|
| +{
|
| + int pc = 0;
|
| + int done = 0;
|
| + unsigned char op, next_op;
|
| + int arg;
|
| + /* This is only used to build 64-bit value for constants. */
|
| + ULONGEST top;
|
| + struct bytecode_address *aentry, *aentry2;
|
| +
|
| +#define UNHANDLED \
|
| + do \
|
| + { \
|
| + ax_debug ("Cannot compile op 0x%x\n", op); \
|
| + return expr_eval_unhandled_opcode; \
|
| + } while (0)
|
| +
|
| + if (aexpr->length == 0)
|
| + {
|
| + ax_debug ("empty agent expression\n");
|
| + return expr_eval_empty_expression;
|
| + }
|
| +
|
| + bytecode_address_table = NULL;
|
| +
|
| + while (!done)
|
| + {
|
| + op = aexpr->bytes[pc];
|
| +
|
| + ax_debug ("About to compile op 0x%x, pc=%d\n", op, pc);
|
| +
|
| + /* Record the compiled-code address of the bytecode, for use by
|
| + jump instructions. */
|
| + aentry = xmalloc (sizeof (struct bytecode_address));
|
| + aentry->pc = pc;
|
| + aentry->address = current_insn_ptr;
|
| + aentry->goto_pc = -1;
|
| + aentry->from_offset = aentry->from_size = 0;
|
| + aentry->next = bytecode_address_table;
|
| + bytecode_address_table = aentry;
|
| +
|
| + ++pc;
|
| +
|
| + emit_error = 0;
|
| +
|
| + switch (op)
|
| + {
|
| + case gdb_agent_op_add:
|
| + emit_add ();
|
| + break;
|
| +
|
| + case gdb_agent_op_sub:
|
| + emit_sub ();
|
| + break;
|
| +
|
| + case gdb_agent_op_mul:
|
| + emit_mul ();
|
| + break;
|
| +
|
| + case gdb_agent_op_div_signed:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_div_unsigned:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_rem_signed:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_rem_unsigned:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_lsh:
|
| + emit_lsh ();
|
| + break;
|
| +
|
| + case gdb_agent_op_rsh_signed:
|
| + emit_rsh_signed ();
|
| + break;
|
| +
|
| + case gdb_agent_op_rsh_unsigned:
|
| + emit_rsh_unsigned ();
|
| + break;
|
| +
|
| + case gdb_agent_op_trace:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_trace_quick:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + case gdb_agent_op_log_not:
|
| + emit_log_not ();
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_and:
|
| + emit_bit_and ();
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_or:
|
| + emit_bit_or ();
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_xor:
|
| + emit_bit_xor ();
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_not:
|
| + emit_bit_not ();
|
| + break;
|
| +
|
| + case gdb_agent_op_equal:
|
| + next_op = aexpr->bytes[pc];
|
| + if (next_op == gdb_agent_op_if_goto
|
| + && !is_goto_target (aexpr, pc)
|
| + && target_emit_ops ()->emit_eq_goto)
|
| + {
|
| + ax_debug ("Combining equal & if_goto");
|
| + pc += 1;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_eq_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else if (next_op == gdb_agent_op_log_not
|
| + && (aexpr->bytes[pc + 1] == gdb_agent_op_if_goto)
|
| + && !is_goto_target (aexpr, pc + 1)
|
| + && target_emit_ops ()->emit_ne_goto)
|
| + {
|
| + ax_debug ("Combining equal & log_not & if_goto");
|
| + pc += 2;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_ne_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else
|
| + emit_equal ();
|
| + break;
|
| +
|
| + case gdb_agent_op_less_signed:
|
| + next_op = aexpr->bytes[pc];
|
| + if (next_op == gdb_agent_op_if_goto
|
| + && !is_goto_target (aexpr, pc))
|
| + {
|
| + ax_debug ("Combining less_signed & if_goto");
|
| + pc += 1;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_lt_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else if (next_op == gdb_agent_op_log_not
|
| + && !is_goto_target (aexpr, pc)
|
| + && (aexpr->bytes[pc + 1] == gdb_agent_op_if_goto)
|
| + && !is_goto_target (aexpr, pc + 1))
|
| + {
|
| + ax_debug ("Combining less_signed & log_not & if_goto");
|
| + pc += 2;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_ge_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else
|
| + emit_less_signed ();
|
| + break;
|
| +
|
| + case gdb_agent_op_less_unsigned:
|
| + emit_less_unsigned ();
|
| + break;
|
| +
|
| + case gdb_agent_op_ext:
|
| + arg = aexpr->bytes[pc++];
|
| + if (arg < (sizeof (LONGEST) * 8))
|
| + emit_ext (arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_ref8:
|
| + emit_ref (1);
|
| + break;
|
| +
|
| + case gdb_agent_op_ref16:
|
| + emit_ref (2);
|
| + break;
|
| +
|
| + case gdb_agent_op_ref32:
|
| + emit_ref (4);
|
| + break;
|
| +
|
| + case gdb_agent_op_ref64:
|
| + emit_ref (8);
|
| + break;
|
| +
|
| + case gdb_agent_op_if_goto:
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_if_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + break;
|
| +
|
| + case gdb_agent_op_goto:
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + break;
|
| +
|
| + case gdb_agent_op_const8:
|
| + emit_stack_flush ();
|
| + top = aexpr->bytes[pc++];
|
| + emit_const (top);
|
| + break;
|
| +
|
| + case gdb_agent_op_const16:
|
| + emit_stack_flush ();
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + emit_const (top);
|
| + break;
|
| +
|
| + case gdb_agent_op_const32:
|
| + emit_stack_flush ();
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + emit_const (top);
|
| + break;
|
| +
|
| + case gdb_agent_op_const64:
|
| + emit_stack_flush ();
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + emit_const (top);
|
| + break;
|
| +
|
| + case gdb_agent_op_reg:
|
| + emit_stack_flush ();
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + emit_reg (arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_end:
|
| + ax_debug ("At end of expression\n");
|
| +
|
| + /* Assume there is one stack element left, and that it is
|
| + cached in "top" where emit_epilogue can get to it. */
|
| + emit_stack_adjust (1);
|
| +
|
| + done = 1;
|
| + break;
|
| +
|
| + case gdb_agent_op_dup:
|
| + /* In our design, dup is equivalent to stack flushing. */
|
| + emit_stack_flush ();
|
| + break;
|
| +
|
| + case gdb_agent_op_pop:
|
| + emit_pop ();
|
| + break;
|
| +
|
| + case gdb_agent_op_zero_ext:
|
| + arg = aexpr->bytes[pc++];
|
| + if (arg < (sizeof (LONGEST) * 8))
|
| + emit_zero_ext (arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_swap:
|
| + next_op = aexpr->bytes[pc];
|
| + /* Detect greater-than comparison sequences. */
|
| + if (next_op == gdb_agent_op_less_signed
|
| + && !is_goto_target (aexpr, pc)
|
| + && (aexpr->bytes[pc + 1] == gdb_agent_op_if_goto)
|
| + && !is_goto_target (aexpr, pc + 1))
|
| + {
|
| + ax_debug ("Combining swap & less_signed & if_goto");
|
| + pc += 2;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_gt_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else if (next_op == gdb_agent_op_less_signed
|
| + && !is_goto_target (aexpr, pc)
|
| + && (aexpr->bytes[pc + 1] == gdb_agent_op_log_not)
|
| + && !is_goto_target (aexpr, pc + 1)
|
| + && (aexpr->bytes[pc + 2] == gdb_agent_op_if_goto)
|
| + && !is_goto_target (aexpr, pc + 2))
|
| + {
|
| + ax_debug ("Combining swap & less_signed & log_not & if_goto");
|
| + pc += 3;
|
| + aentry->pc = pc;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + aentry->goto_pc = arg;
|
| + emit_le_goto (&(aentry->from_offset), &(aentry->from_size));
|
| + }
|
| + else
|
| + emit_swap ();
|
| + break;
|
| +
|
| + case gdb_agent_op_getv:
|
| + emit_stack_flush ();
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + emit_int_call_1 (get_get_tsv_func_addr (),
|
| + arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_setv:
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + emit_void_call_2 (get_set_tsv_func_addr (),
|
| + arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_tracev:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + /* GDB never (currently) generates any of these ops. */
|
| + case gdb_agent_op_float:
|
| + case gdb_agent_op_ref_float:
|
| + case gdb_agent_op_ref_double:
|
| + case gdb_agent_op_ref_long_double:
|
| + case gdb_agent_op_l_to_d:
|
| + case gdb_agent_op_d_to_l:
|
| + case gdb_agent_op_trace16:
|
| + UNHANDLED;
|
| + break;
|
| +
|
| + default:
|
| + ax_debug ("Agent expression op 0x%x not recognized\n", op);
|
| + /* Don't struggle on, things will just get worse. */
|
| + return expr_eval_unrecognized_opcode;
|
| + }
|
| +
|
| + /* This catches errors that occur in target-specific code
|
| + emission. */
|
| + if (emit_error)
|
| + {
|
| + ax_debug ("Error %d while emitting code for %s\n",
|
| + emit_error, gdb_agent_op_name (op));
|
| + return expr_eval_unhandled_opcode;
|
| + }
|
| +
|
| + ax_debug ("Op %s compiled\n", gdb_agent_op_name (op));
|
| + }
|
| +
|
| + /* Now fill in real addresses as goto destinations. */
|
| + for (aentry = bytecode_address_table; aentry; aentry = aentry->next)
|
| + {
|
| + int written = 0;
|
| +
|
| + if (aentry->goto_pc < 0)
|
| + continue;
|
| +
|
| + /* Find the location that we are going to, and call back into
|
| + target-specific code to write the actual address or
|
| + displacement. */
|
| + for (aentry2 = bytecode_address_table; aentry2; aentry2 = aentry2->next)
|
| + {
|
| + if (aentry2->pc == aentry->goto_pc)
|
| + {
|
| + ax_debug ("Want to jump from %s to %s\n",
|
| + paddress (aentry->address),
|
| + paddress (aentry2->address));
|
| + write_goto_address (aentry->address + aentry->from_offset,
|
| + aentry2->address, aentry->from_size);
|
| + written = 1;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + /* Error out if we didn't find a destination. */
|
| + if (!written)
|
| + {
|
| + ax_debug ("Destination of goto %d not found\n",
|
| + aentry->goto_pc);
|
| + return expr_eval_invalid_goto;
|
| + }
|
| + }
|
| +
|
| + return expr_eval_no_error;
|
| +}
|
| +
|
| +#endif
|
| +
|
| +/* Make printf-type calls using arguments supplied from the host. We
|
| + need to parse the format string ourselves, and call the formatting
|
| + function with one argument at a time, partly because there is no
|
| + safe portable way to construct a varargs call, and partly to serve
|
| + as a security barrier against bad format strings that might get
|
| + in. */
|
| +
|
| +static void
|
| +ax_printf (CORE_ADDR fn, CORE_ADDR chan, char *format,
|
| + int nargs, ULONGEST *args)
|
| +{
|
| + char *f = format;
|
| + struct format_piece *fpieces;
|
| + int i, fp;
|
| + char *current_substring;
|
| + int nargs_wanted;
|
| +
|
| + ax_debug ("Printf of \"%s\" with %d args", format, nargs);
|
| +
|
| + fpieces = parse_format_string (&f);
|
| +
|
| + nargs_wanted = 0;
|
| + for (fp = 0; fpieces[fp].string != NULL; fp++)
|
| + if (fpieces[fp].argclass != literal_piece)
|
| + ++nargs_wanted;
|
| +
|
| + if (nargs != nargs_wanted)
|
| + error (_("Wrong number of arguments for specified format-string"));
|
| +
|
| + i = 0;
|
| + for (fp = 0; fpieces[fp].string != NULL; fp++)
|
| + {
|
| + current_substring = fpieces[fp].string;
|
| + ax_debug ("current substring is '%s', class is %d",
|
| + current_substring, fpieces[fp].argclass);
|
| + switch (fpieces[fp].argclass)
|
| + {
|
| + case string_arg:
|
| + {
|
| + gdb_byte *str;
|
| + CORE_ADDR tem;
|
| + int j;
|
| +
|
| + tem = args[i];
|
| +
|
| + /* This is a %s argument. Find the length of the string. */
|
| + for (j = 0;; j++)
|
| + {
|
| + gdb_byte c;
|
| +
|
| + read_inferior_memory (tem + j, &c, 1);
|
| + if (c == 0)
|
| + break;
|
| + }
|
| +
|
| + /* Copy the string contents into a string inside GDB. */
|
| + str = (gdb_byte *) alloca (j + 1);
|
| + if (j != 0)
|
| + read_inferior_memory (tem, str, j);
|
| + str[j] = 0;
|
| +
|
| + printf (current_substring, (char *) str);
|
| + }
|
| + break;
|
| +
|
| + case long_long_arg:
|
| +#if defined (CC_HAS_LONG_LONG) && defined (PRINTF_HAS_LONG_LONG)
|
| + {
|
| + long long val = args[i];
|
| +
|
| + printf (current_substring, val);
|
| + break;
|
| + }
|
| +#else
|
| + error (_("long long not supported in agent printf"));
|
| +#endif
|
| + case int_arg:
|
| + {
|
| + int val = args[i];
|
| +
|
| + printf (current_substring, val);
|
| + break;
|
| + }
|
| +
|
| + case long_arg:
|
| + {
|
| + long val = args[i];
|
| +
|
| + printf (current_substring, val);
|
| + break;
|
| + }
|
| +
|
| + case literal_piece:
|
| + /* Print a portion of the format string that has no
|
| + directives. Note that this will not include any
|
| + ordinary %-specs, but it might include "%%". That is
|
| + why we use printf_filtered and not puts_filtered here.
|
| + Also, we pass a dummy argument because some platforms
|
| + have modified GCC to include -Wformat-security by
|
| + default, which will warn here if there is no
|
| + argument. */
|
| + printf (current_substring, 0);
|
| + break;
|
| +
|
| + default:
|
| + error (_("Format directive in '%s' not supported in agent printf"),
|
| + current_substring);
|
| + }
|
| +
|
| + /* Maybe advance to the next argument. */
|
| + if (fpieces[fp].argclass != literal_piece)
|
| + ++i;
|
| + }
|
| +
|
| + free_format_pieces (fpieces);
|
| +}
|
| +
|
| +/* The agent expression evaluator, as specified by the GDB docs. It
|
| + returns 0 if everything went OK, and a nonzero error code
|
| + otherwise. */
|
| +
|
| +enum eval_result_type
|
| +gdb_eval_agent_expr (struct regcache *regcache,
|
| + struct traceframe *tframe,
|
| + struct agent_expr *aexpr,
|
| + ULONGEST *rslt)
|
| +{
|
| + int pc = 0;
|
| +#define STACK_MAX 100
|
| + ULONGEST stack[STACK_MAX], top;
|
| + int sp = 0;
|
| + unsigned char op;
|
| + int arg;
|
| +
|
| + /* This union is a convenient way to convert representations. For
|
| + now, assume a standard architecture where the hardware integer
|
| + types have 8, 16, 32, 64 bit types. A more robust solution would
|
| + be to import stdint.h from gnulib. */
|
| + union
|
| + {
|
| + union
|
| + {
|
| + unsigned char bytes[1];
|
| + unsigned char val;
|
| + } u8;
|
| + union
|
| + {
|
| + unsigned char bytes[2];
|
| + unsigned short val;
|
| + } u16;
|
| + union
|
| + {
|
| + unsigned char bytes[4];
|
| + unsigned int val;
|
| + } u32;
|
| + union
|
| + {
|
| + unsigned char bytes[8];
|
| + ULONGEST val;
|
| + } u64;
|
| + } cnv;
|
| +
|
| + if (aexpr->length == 0)
|
| + {
|
| + ax_debug ("empty agent expression");
|
| + return expr_eval_empty_expression;
|
| + }
|
| +
|
| + /* Cache the stack top in its own variable. Much of the time we can
|
| + operate on this variable, rather than dinking with the stack. It
|
| + needs to be copied to the stack when sp changes. */
|
| + top = 0;
|
| +
|
| + while (1)
|
| + {
|
| + op = aexpr->bytes[pc++];
|
| +
|
| + ax_debug ("About to interpret byte 0x%x", op);
|
| +
|
| + switch (op)
|
| + {
|
| + case gdb_agent_op_add:
|
| + top += stack[--sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_sub:
|
| + top = stack[--sp] - top;
|
| + break;
|
| +
|
| + case gdb_agent_op_mul:
|
| + top *= stack[--sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_div_signed:
|
| + if (top == 0)
|
| + {
|
| + ax_debug ("Attempted to divide by zero");
|
| + return expr_eval_divide_by_zero;
|
| + }
|
| + top = ((LONGEST) stack[--sp]) / ((LONGEST) top);
|
| + break;
|
| +
|
| + case gdb_agent_op_div_unsigned:
|
| + if (top == 0)
|
| + {
|
| + ax_debug ("Attempted to divide by zero");
|
| + return expr_eval_divide_by_zero;
|
| + }
|
| + top = stack[--sp] / top;
|
| + break;
|
| +
|
| + case gdb_agent_op_rem_signed:
|
| + if (top == 0)
|
| + {
|
| + ax_debug ("Attempted to divide by zero");
|
| + return expr_eval_divide_by_zero;
|
| + }
|
| + top = ((LONGEST) stack[--sp]) % ((LONGEST) top);
|
| + break;
|
| +
|
| + case gdb_agent_op_rem_unsigned:
|
| + if (top == 0)
|
| + {
|
| + ax_debug ("Attempted to divide by zero");
|
| + return expr_eval_divide_by_zero;
|
| + }
|
| + top = stack[--sp] % top;
|
| + break;
|
| +
|
| + case gdb_agent_op_lsh:
|
| + top = stack[--sp] << top;
|
| + break;
|
| +
|
| + case gdb_agent_op_rsh_signed:
|
| + top = ((LONGEST) stack[--sp]) >> top;
|
| + break;
|
| +
|
| + case gdb_agent_op_rsh_unsigned:
|
| + top = stack[--sp] >> top;
|
| + break;
|
| +
|
| + case gdb_agent_op_trace:
|
| + agent_mem_read (tframe,
|
| + NULL, (CORE_ADDR) stack[--sp], (ULONGEST) top);
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_trace_quick:
|
| + arg = aexpr->bytes[pc++];
|
| + agent_mem_read (tframe, NULL, (CORE_ADDR) top, (ULONGEST) arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_log_not:
|
| + top = !top;
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_and:
|
| + top &= stack[--sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_or:
|
| + top |= stack[--sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_xor:
|
| + top ^= stack[--sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_bit_not:
|
| + top = ~top;
|
| + break;
|
| +
|
| + case gdb_agent_op_equal:
|
| + top = (stack[--sp] == top);
|
| + break;
|
| +
|
| + case gdb_agent_op_less_signed:
|
| + top = (((LONGEST) stack[--sp]) < ((LONGEST) top));
|
| + break;
|
| +
|
| + case gdb_agent_op_less_unsigned:
|
| + top = (stack[--sp] < top);
|
| + break;
|
| +
|
| + case gdb_agent_op_ext:
|
| + arg = aexpr->bytes[pc++];
|
| + if (arg < (sizeof (LONGEST) * 8))
|
| + {
|
| + LONGEST mask = 1 << (arg - 1);
|
| + top &= ((LONGEST) 1 << arg) - 1;
|
| + top = (top ^ mask) - mask;
|
| + }
|
| + break;
|
| +
|
| + case gdb_agent_op_ref8:
|
| + agent_mem_read (tframe, cnv.u8.bytes, (CORE_ADDR) top, 1);
|
| + top = cnv.u8.val;
|
| + break;
|
| +
|
| + case gdb_agent_op_ref16:
|
| + agent_mem_read (tframe, cnv.u16.bytes, (CORE_ADDR) top, 2);
|
| + top = cnv.u16.val;
|
| + break;
|
| +
|
| + case gdb_agent_op_ref32:
|
| + agent_mem_read (tframe, cnv.u32.bytes, (CORE_ADDR) top, 4);
|
| + top = cnv.u32.val;
|
| + break;
|
| +
|
| + case gdb_agent_op_ref64:
|
| + agent_mem_read (tframe, cnv.u64.bytes, (CORE_ADDR) top, 8);
|
| + top = cnv.u64.val;
|
| + break;
|
| +
|
| + case gdb_agent_op_if_goto:
|
| + if (top)
|
| + pc = (aexpr->bytes[pc] << 8) + (aexpr->bytes[pc + 1]);
|
| + else
|
| + pc += 2;
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_goto:
|
| + pc = (aexpr->bytes[pc] << 8) + (aexpr->bytes[pc + 1]);
|
| + break;
|
| +
|
| + case gdb_agent_op_const8:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + top = aexpr->bytes[pc++];
|
| + break;
|
| +
|
| + case gdb_agent_op_const16:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + break;
|
| +
|
| + case gdb_agent_op_const32:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + break;
|
| +
|
| + case gdb_agent_op_const64:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + top = aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + top = (top << 8) + aexpr->bytes[pc++];
|
| + break;
|
| +
|
| + case gdb_agent_op_reg:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + {
|
| + int regnum = arg;
|
| +
|
| + switch (register_size (regnum))
|
| + {
|
| + case 8:
|
| + collect_register (regcache, regnum, cnv.u64.bytes);
|
| + top = cnv.u64.val;
|
| + break;
|
| + case 4:
|
| + collect_register (regcache, regnum, cnv.u32.bytes);
|
| + top = cnv.u32.val;
|
| + break;
|
| + case 2:
|
| + collect_register (regcache, regnum, cnv.u16.bytes);
|
| + top = cnv.u16.val;
|
| + break;
|
| + case 1:
|
| + collect_register (regcache, regnum, cnv.u8.bytes);
|
| + top = cnv.u8.val;
|
| + break;
|
| + default:
|
| + internal_error (__FILE__, __LINE__,
|
| + "unhandled register size");
|
| + }
|
| + }
|
| + break;
|
| +
|
| + case gdb_agent_op_end:
|
| + ax_debug ("At end of expression, sp=%d, stack top cache=0x%s",
|
| + sp, pulongest (top));
|
| + if (rslt)
|
| + {
|
| + if (sp <= 0)
|
| + {
|
| + /* This should be an error */
|
| + ax_debug ("Stack is empty, nothing to return");
|
| + return expr_eval_empty_stack;
|
| + }
|
| + *rslt = top;
|
| + }
|
| + return expr_eval_no_error;
|
| +
|
| + case gdb_agent_op_dup:
|
| + stack[sp++] = top;
|
| + break;
|
| +
|
| + case gdb_agent_op_pop:
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_pick:
|
| + arg = aexpr->bytes[pc++];
|
| + stack[sp] = top;
|
| + top = stack[sp - arg];
|
| + ++sp;
|
| + break;
|
| +
|
| + case gdb_agent_op_rot:
|
| + {
|
| + ULONGEST tem = stack[sp - 1];
|
| +
|
| + stack[sp - 1] = stack[sp - 2];
|
| + stack[sp - 2] = top;
|
| + top = tem;
|
| + }
|
| + break;
|
| +
|
| + case gdb_agent_op_zero_ext:
|
| + arg = aexpr->bytes[pc++];
|
| + if (arg < (sizeof (LONGEST) * 8))
|
| + top &= ((LONGEST) 1 << arg) - 1;
|
| + break;
|
| +
|
| + case gdb_agent_op_swap:
|
| + /* Interchange top two stack elements, making sure top gets
|
| + copied back onto stack. */
|
| + stack[sp] = top;
|
| + top = stack[sp - 1];
|
| + stack[sp - 1] = stack[sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_getv:
|
| + /* Flush the cached stack top. */
|
| + stack[sp++] = top;
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + top = agent_get_trace_state_variable_value (arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_setv:
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + agent_set_trace_state_variable_value (arg, top);
|
| + /* Note that we leave the value on the stack, for the
|
| + benefit of later/enclosing expressions. */
|
| + break;
|
| +
|
| + case gdb_agent_op_tracev:
|
| + arg = aexpr->bytes[pc++];
|
| + arg = (arg << 8) + aexpr->bytes[pc++];
|
| + agent_tsv_read (tframe, arg);
|
| + break;
|
| +
|
| + case gdb_agent_op_tracenz:
|
| + agent_mem_read_string (tframe, NULL, (CORE_ADDR) stack[--sp],
|
| + (ULONGEST) top);
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + break;
|
| +
|
| + case gdb_agent_op_printf:
|
| + {
|
| + int nargs, slen, i;
|
| + CORE_ADDR fn = 0, chan = 0;
|
| + /* Can't have more args than the entire size of the stack. */
|
| + ULONGEST args[STACK_MAX];
|
| + char *format;
|
| +
|
| + nargs = aexpr->bytes[pc++];
|
| + slen = aexpr->bytes[pc++];
|
| + slen = (slen << 8) + aexpr->bytes[pc++];
|
| + format = (char *) &(aexpr->bytes[pc]);
|
| + pc += slen;
|
| + /* Pop function and channel. */
|
| + fn = top;
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + chan = top;
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + /* Pop arguments into a dedicated array. */
|
| + for (i = 0; i < nargs; ++i)
|
| + {
|
| + args[i] = top;
|
| + if (--sp >= 0)
|
| + top = stack[sp];
|
| + }
|
| +
|
| + /* A bad format string means something is very wrong; give
|
| + up immediately. */
|
| + if (format[slen - 1] != '\0')
|
| + error (_("Unterminated format string in printf bytecode"));
|
| +
|
| + ax_printf (fn, chan, format, nargs, args);
|
| + }
|
| + break;
|
| +
|
| + /* GDB never (currently) generates any of these ops. */
|
| + case gdb_agent_op_float:
|
| + case gdb_agent_op_ref_float:
|
| + case gdb_agent_op_ref_double:
|
| + case gdb_agent_op_ref_long_double:
|
| + case gdb_agent_op_l_to_d:
|
| + case gdb_agent_op_d_to_l:
|
| + case gdb_agent_op_trace16:
|
| + ax_debug ("Agent expression op 0x%x valid, but not handled",
|
| + op);
|
| + /* If ever GDB generates any of these, we don't have the
|
| + option of ignoring. */
|
| + return 1;
|
| +
|
| + default:
|
| + ax_debug ("Agent expression op 0x%x not recognized", op);
|
| + /* Don't struggle on, things will just get worse. */
|
| + return expr_eval_unrecognized_opcode;
|
| + }
|
| +
|
| + /* Check for stack badness. */
|
| + if (sp >= (STACK_MAX - 1))
|
| + {
|
| + ax_debug ("Expression stack overflow");
|
| + return expr_eval_stack_overflow;
|
| + }
|
| +
|
| + if (sp < 0)
|
| + {
|
| + ax_debug ("Expression stack underflow");
|
| + return expr_eval_stack_underflow;
|
| + }
|
| +
|
| + ax_debug ("Op %s -> sp=%d, top=0x%s",
|
| + gdb_agent_op_name (op), sp, phex_nz (top, 0));
|
| + }
|
| +}
|
|
|