mirror of
https://github.com/darlinghq/darling-gdb.git
synced 2024-12-12 14:37:43 +00:00
d3ce09f5bf
* NEWS: Mention the additional style. * breakpoint.h (struct bp_target_info): New fields tcommands, persist. (struct bp_location): New field cmd_bytecode. * breakpoint.c: Include format.h. (disconnected_dprintf): New global. (parse_cmd_to_aexpr): New function. (build_target_command_list): New function. (insert_bp_location): Call it. (remove_breakpoints_pid): Skip dprintf breakpoints. (print_one_breakpoint_location): Ditto. (dprintf_style_agent): New global. (dprintf_style_enums): Add dprintf_style_agent. (update_dprintf_command_list): Add agent case. (agent_printf_command): New function. (_initialize_breakpoint): Add new commands. * common/ax.def (printf): New bytecode. * ax.h (ax_string): Declare. * ax-gdb.h (gen_printf): Declare. * ax-gdb.c: Include cli-utils.h, format.h. (gen_printf): New function. (maint_agent_print_command): New function. (_initialize_ax_gdb): Add maint agent-printf command. * ax-general.c (ax_string): New function. (ax_print): Add printf disassembly. * Makefile.in (SFILES): Add format.c (COMMON_OBS): Add format.o. * common/format.h: New file. * common/format.c: New file. * printcmd.c: Include format.h. (ui_printf): Call parse_format_string. * remote.c (remote_state): New field breakpoint_commands. (PACKET_BreakpointCommands): New enum. (remote_breakpoint_commands_feature): New function. (remote_protocol_features): Add new BreakpointCommands entry. (remote_can_run_breakpoint_commands): New function. (remote_add_target_side_commands): New function. (remote_insert_breakpoint): Call it. (remote_insert_hw_breakpoint): Ditto. (_initialize_remote): Add new packet configuration for target-side breakpoint commands. * target.h (struct target_ops): New field to_can_run_breakpoint_commands. (target_can_run_breakpoint_commands): New macro. * target.c (update_current_target): Handle to_can_run_breakpoint_commands. [gdbserver] * Makefile.in (WARN_CFLAGS_NO_FORMAT): Define. (ax.o): Add it to build rule. (ax-ipa.o): Ditto. (OBS): Add format.o. (IPA_OBS): Add format.o. * server.c (handle_query): Claim support for breakpoint commands. (process_point_options): Add command case. (process_serial_event): Leave running if there are printfs in effect. * mem-break.h (any_persistent_commands): Declare. (add_breakpoint_commands): Declare. (gdb_no_commands_at_breakpoint): Declare. (run_breakpoint_commands): Declare. * mem-break.c (struct point_command_list): New struct. (struct breakpoint): New field command_list. (any_persistent_commands): New function. (add_commands_to_breakpoint): New function. (add_breakpoint_commands): New function. (gdb_no_commands_at_breakpoint): New function. (run_breakpoint_commands): New function. * linux-low.c (linux_wait_1): Test for and run breakpoint commands locally. * ax.c: Include format.h. (ax_printf): New function. (gdb_eval_agent_expr): Add printf opcode. [doc] * gdb.texinfo (Dynamic Printf): Mention agent style and disconnected dprintf. (Maintenance Commands): Describe maint agent-printf. (General Query Packets): Mention BreakpointCommands feature. (Packets): Document commands extension to Z0 packet. * agentexpr.texi (Bytecode Descriptions): Document printf bytecode. [testsuite] * gdb.base/dprintf.exp: Add agent style tests.
1347 lines
29 KiB
C
1347 lines
29 KiB
C
/* 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));
|
|
}
|
|
}
|