home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 1999 mARCH
/
PCWK3A99.iso
/
Linux
/
DDD331
/
DDD-3_1_.000
/
DDD-3_1_
/
ddd-3.1.1
/
ddd
/
GDBAgent.C
< prev
next >
Wrap
C/C++ Source or Header
|
1998-11-24
|
62KB
|
2,778 lines
// $Id: GDBAgent.C,v 1.181 1998/11/24 15:07:03 zeller Exp $
// Communicate with separate GDB process
// Copyright (C) 1995-1998 Technische Universitaet Braunschweig, Germany.
// Written by Dorothea Luetkehaus <luetke@ips.cs.tu-bs.de>
// and Andreas Zeller <zeller@ips.cs.tu-bs.de>.
//
// This file is part of DDD.
//
// DDD 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 2 of the License, or (at your option) any later version.
//
// DDD 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 DDD -- see the file COPYING.
// If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page,
// `http://www.cs.tu-bs.de/softech/ddd/',
// or send a mail to the DDD developers <ddd@ips.cs.tu-bs.de>.
char GDBAgent_rcsid[] =
"$Id: GDBAgent.C,v 1.181 1998/11/24 15:07:03 zeller Exp $";
// From: hobson@pipper.enet.dec.com
// Subject: And the Twelve Bugs of Christmas ....
// Keywords: topical, heard it, computers, chuckle, originally
// appeared in fourth quarter, 1992
// Newsgroups: rec.humor.funny.reruns
// Date: Tue, 23 Dec 97 13:20:02 EST
//
// For the first bug of Christmas, my manager said to me
// See if they can do it again.
//
// For the second bug of Christmas, my manager said to me
// Ask them how they did it and
// See if they can do it again.
//
// For the third bug of Christmas, my manager said to me
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the fourth bug of Christmas, my manager said to me
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the fifth bug of Christmas, my manager said to me
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the sixth bug of Christmas, my manager said to me
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the seventh bug of Christmas, my manager said to me
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the eighth bug of Christmas, my manager said to me
// Find a way around it
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the ninth bug of Christmas, my manager said to me
// Blame it on the hardware
// Find a way around it
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the tenth bug of Christmas, my manager said to me
// Change the documentation
// Blame it on the hardware
// Find a way around it
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the eleventh bug of Christmas, my manager said to me
// Say it's not supported
// Change the documentation
// Blame it on the hardware
// Find a way around it
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
//
// For the twelfth bug of Christmas, my manager said to me
// Tell them it's a feature
// Say it's not supported
// Change the documentation
// Blame it on the hardware
// Find a way around it
// Say they need an upgrade
// Reinstall the software
// Ask for a dump
// Run with the debugger
// Try to reproduce it
// Ask them how they did it and
// See if they can do it again.
#ifdef __GNUG__
#pragma implementation
#endif
//-----------------------------------------------------------------------------
// GDBAgent implementation
//-----------------------------------------------------------------------------
#include "GDBAgent.h"
#include "cook.h"
#include "ddd.h"
#include "string-fun.h"
#include "regexps.h"
#include "index.h"
#include "isid.h"
#include "home.h"
#include "value-read.h" // read_token
#include <stdlib.h>
#include <iostream.h>
#include <fstream.h>
#include <ctype.h>
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
DEFINE_TYPE_INFO_1(GDBAgent, TTYAgent);
//-----------------------------------------------------------------------------
// Construction and setup
//-----------------------------------------------------------------------------
// Constructor
GDBAgent::GDBAgent (XtAppContext app_context,
const string& gdb_call,
DebuggerType tp,
unsigned int nTypes)
: TTYAgent (app_context, gdb_call, nTypes),
state(BusyOnInitialCmds),
_type(tp),
_user_data(0),
_has_frame_command(tp == GDB || tp == XDB),
_has_func_command(tp == DBX),
_has_run_io_command(false),
_has_print_r_option(false),
_has_output_command(false),
_has_where_h_option(false),
_has_display_command(tp == GDB || tp == DBX || tp == PYDB),
_has_clear_command(tp == GDB || tp == DBX || tp == JDB || tp == PERL),
_has_handler_command(false),
_has_pwd_command(tp == GDB || tp == DBX || tp == PYDB || tp == PERL),
_has_setenv_command(tp == DBX),
_has_edit_command(tp == DBX),
_has_make_command(tp == GDB || tp == DBX || tp == PERL),
_has_jump_command(tp == GDB || tp == DBX || tp == XDB),
_has_regs_command(tp == GDB),
_has_watch_command(0), // see below
_has_named_values(tp == GDB || tp == DBX || tp == JDB),
_has_when_command(tp == DBX),
_has_when_semicolon(tp == DBX),
_wants_delete_comma(false),
_has_err_redirection(tp == GDB || tp == DBX || tp == XDB),
_has_givenfile_command(false),
_has_cont_sig_command(false),
_has_examine_command(tp == GDB || tp == DBX),
_has_rerun_command(tp == DBX),
_rerun_clears_args(false),
_program_language((tp == JDB) ? LANGUAGE_JAVA :
(tp == PYDB) ? LANGUAGE_PYTHON :
(tp == PERL) ? LANGUAGE_PERL :
LANGUAGE_C),
_verbatim(false),
_recording(false),
_detect_echos(true),
last_prompt(""),
last_written(""),
echoed_characters(-1),
questions_waiting(false),
_qu_data(0),
qu_index(0),
_qu_count(0),
cmd_array(0),
complete_answers(0),
_qu_datas(0),
_qa_data(0),
_on_answer(0),
_on_answer_completion(0),
_on_question_completion(0),
_on_qu_array_completion(0),
complete_answer("")
{
// Suppress default error handlers
removeAllHandlers(Panic);
removeAllHandlers(Strange);
removeAllHandlers(Died);
// Add own handlers
addHandler(Panic, PanicHP);
addHandler(Strange, StrangeHP);
addHandler(Died, DiedHP);
addHandler(Input, InputHP);
// Add trace handlers
addHandler(Input, traceInputHP); // GDB => DDD
addHandler(Output, traceOutputHP); // DDD => GDB
addHandler(Error, traceErrorHP); // GDB Errors => DDD
// Setup watch mode
if (type() == GDB)
_has_watch_command = WATCH_CHANGE | WATCH_READ | WATCH_WRITE;
else if (type() == DBX)
_has_watch_command = WATCH_CHANGE;
else
_has_watch_command = 0;
}
// Copy constructor
GDBAgent::GDBAgent(const GDBAgent& gdb)
: TTYAgent(gdb),
state(gdb.state),
_type(gdb.type()),
_user_data(0),
_has_frame_command(gdb.has_frame_command()),
_has_func_command(gdb.has_func_command()),
_has_run_io_command(gdb.has_run_io_command()),
_has_print_r_option(gdb.has_print_r_option()),
_has_output_command(gdb.has_output_command()),
_has_where_h_option(gdb.has_where_h_option()),
_has_display_command(gdb.has_display_command()),
_has_clear_command(gdb.has_clear_command()),
_has_handler_command(gdb.has_handler_command()),
_has_pwd_command(gdb.has_pwd_command()),
_has_setenv_command(gdb.has_setenv_command()),
_has_edit_command(gdb.has_edit_command()),
_has_make_command(gdb.has_make_command()),
_has_jump_command(gdb.has_jump_command()),
_has_regs_command(gdb.has_regs_command()),
_has_watch_command(gdb.has_watch_command()),
_has_named_values(gdb.has_named_values()),
_has_when_command(gdb.has_when_command()),
_has_when_semicolon(gdb.has_when_semicolon()),
_wants_delete_comma(gdb.wants_delete_comma()),
_has_err_redirection(gdb.has_err_redirection()),
_has_givenfile_command(gdb.has_givenfile_command()),
_has_cont_sig_command(gdb.has_cont_sig_command()),
_has_examine_command(gdb.has_examine_command()),
_has_rerun_command(gdb.has_rerun_command()),
_rerun_clears_args(gdb.rerun_clears_args()),
_program_language(gdb.program_language()),
_verbatim(gdb.verbatim()),
_recording(gdb.recording()),
_detect_echos(gdb.detect_echos()),
last_prompt(""),
last_written(""),
echoed_characters(-1),
questions_waiting(false),
_qu_data(0),
qu_index(0),
_qu_count(0),
cmd_array(0),
complete_answers(0),
_qu_datas(0),
_qa_data(0),
_on_answer(0),
_on_answer_completion(0),
_on_question_completion(0),
_on_qu_array_completion(0),
complete_answer("")
{}
// Return default title
string GDBAgent::title() const
{
switch (type())
{
case GDB:
return "GDB";
case DBX:
return "DBX";
case XDB:
return "XDB";
case JDB:
return "JDB";
case PYDB:
return "PYDB";
case PERL:
return "Perl";
}
return "debugger";
}
// Trace communication
void GDBAgent::trace(char *prefix, void *call_data) const
{
DataLength* dl = (DataLength *) call_data;
string s(dl->data, dl->length);
bool s_ends_with_nl = false;
if (s.length() > 0 && s[s.length() - 1] == '\n')
{
s_ends_with_nl = true;
s = s.before(int(s.length() - 1));
}
s = quote(s);
string nl = string("\\n\"\n") + replicate(' ', strlen(prefix)) + "\"";
s.gsub("\\n", nl);
if (s_ends_with_nl)
s(s.length() - 1, 0) = "\\n";
dddlog << prefix << s << '\n';
dddlog.flush();
}
void GDBAgent::traceInputHP(Agent *source, void *, void *call_data)
{
GDBAgent *gdb = ptr_cast(GDBAgent, source);
if (gdb != 0)
gdb->trace("<- ", call_data);
}
void GDBAgent::traceOutputHP(Agent *source, void *, void *call_data)
{
GDBAgent *gdb = ptr_cast(GDBAgent, source);
if (gdb != 0)
gdb->trace("-> ", call_data);
}
void GDBAgent::traceErrorHP(Agent *source, void *, void *call_data)
{
GDBAgent *gdb = ptr_cast(GDBAgent, source);
if (gdb != 0)
gdb->trace("<= ", call_data);
}
// Start GDBAgent
void GDBAgent::do_start (OAProc on_answer,
OACProc on_answer_completion,
void* user_data)
{
_on_answer = on_answer;
_on_answer_completion = on_answer_completion;
_user_data = user_data;
TTYAgent::start();
callHandlers(ReadyForQuestion, (void *)false);
callHandlers(ReadyForCmd, (void *)false);
callHandlers(LanguageChanged, (void *)this);
}
// Start with some extra commands
void GDBAgent::start_plus (OAProc on_answer,
OACProc on_answer_completion,
void* user_data,
const StringArray& cmds,
const VoidArray& qu_datas,
int qu_count,
OQACProc on_qu_array_completion,
void* qa_data)
{
state = BusyOnInitialCmds;
if (qu_count > 0) {
questions_waiting = true;
init_qu_array(cmds, qu_datas, qu_count,
on_qu_array_completion, qa_data);
}
do_start(on_answer, on_answer_completion, user_data);
}
// Destructor
GDBAgent::~GDBAgent ()
{
shutdown();
}
//-----------------------------------------------------------------------------
// Command sending
//-----------------------------------------------------------------------------
// Send CMD to GDB, associated with USER_DATA. Return false iff busy.
bool GDBAgent::send_user_cmd(string cmd, void *user_data) // without '\n'
{
if (user_data)
_user_data = user_data;
switch (state) {
case ReadyWithPrompt:
case BusyOnInitialCmds:
// Process CMD
state = BusyOnCmd;
complete_answer = "";
callHandlers(ReadyForQuestion, (void *)false);
cmd += '\n';
write(cmd);
flush();
return true;
case BusyOnQuestion:
case BusyOnQuArray:
case BusyOnCmd:
break;
}
return false;
}
// Send CMD to GDB (unconditionally), associated with USER_DATA.
bool GDBAgent::send_user_ctrl_cmd(string cmd, void *user_data)
{
if (user_data)
_user_data = user_data;
// Upon ^D, GDB is no more in state to receive commands.
// Expect a new prompt to appear.
if (cmd == '\004' && state == ReadyWithPrompt)
{
state = BusyOnCmd;
complete_answer = "";
}
write(cmd);
flush();
return true;
}
// Send command array CMDS to GDB, associated with QU_DATAS.
bool GDBAgent::send_user_cmd_plus (const StringArray& cmds,
const VoidArray& qu_datas,
int qu_count,
OQACProc on_qu_array_completion,
void* qa_data,
string user_cmd,
void* user_data)
{
if (state != ReadyWithPrompt)
return false;
if (user_data)
_user_data = user_data;
if (qu_count > 0)
{
questions_waiting = true;
init_qu_array(cmds, qu_datas, qu_count,
on_qu_array_completion, qa_data);
}
// Process command
state = BusyOnCmd;
complete_answer = "";
callHandlers(ReadyForQuestion, (void *)false);
user_cmd += '\n';
write(user_cmd);
flush();
return true;
}
// Send CMD to GDB; upon completion, call ON_QUESTION_COMPLETION with QU_DATA
bool GDBAgent::send_question (string cmd,
OQCProc on_question_completion,
void* qu_data)
{
if (state != ReadyWithPrompt)
return false;
state = BusyOnQuestion;
callHandlers(ReadyForQuestion, (void *)false);
callHandlers(ReadyForCmd, (void *)false);
_on_question_completion = on_question_completion;
_qu_data = qu_data;
complete_answer = "";
cmd += '\n';
write(cmd);
flush();
return true;
}
// Send CMDS to GDB; upon completion, call ON_QU_ARRAY_COMPLETION with QU_DATAS
bool GDBAgent::send_qu_array (const StringArray& cmds,
const VoidArray& qu_datas,
int qu_count,
OQACProc on_qu_array_completion,
void* qa_data)
{
if (qu_count == 0)
return true;
if (state != ReadyWithPrompt)
return false;
state = BusyOnQuArray;
callHandlers(ReadyForQuestion, (void *)false);
callHandlers(ReadyForCmd, (void *)false);
init_qu_array(cmds, qu_datas, qu_count, on_qu_array_completion, qa_data);
// Send first question
write(cmd_array[0]);
flush();
return true;
}
// Initialize GDB question array
void GDBAgent::init_qu_array (const StringArray& cmds,
const VoidArray& qu_datas,
int qu_count,
OQACProc on_qu_array_completion,
void* qa_data)
{
_on_qu_array_completion = on_qu_array_completion;
qu_index = 0;
_qu_count = qu_count;
_qa_data = qa_data;
StringArray empty_s;
VoidArray empty_v;
complete_answers = empty_s;
cmd_array = empty_s;
_qu_datas = empty_v;
for (int i = 0; i < qu_count; i++)
{
complete_answers += "";
cmd_array += cmds[i] + '\n';
_qu_datas += qu_datas[i];
}
}
//-----------------------------------------------------------------------------
// Prompt Recognition
//-----------------------------------------------------------------------------
// Return true iff ANSWER ends with primary prompt.
bool GDBAgent::ends_with_prompt (const string& ans)
{
string answer = ans;
strip_control(answer);
switch (type())
{
case GDB:
// GDB reads in breakpoint commands using a `>' prompt
if (recording() && answer.contains('>', -1))
{
last_prompt = ">";
return true;
}
// In annotation level 2, GDB `annotates' its prompt.
if (answer.contains("\032\032prompt\n", -1))
return true;
// FALL THROUGH
case DBX:
case PYDB:
{
// Any line ending in `(gdb) ' or `(dbx) ' is a prompt.
int i = answer.length() - 1;
if (i < 0 || answer[i] != ' ')
return false;
while (i >= 0 && answer[i] != '\n' && answer[i] != '(')
i--;
if (i < 0 || answer[i] != '(')
return false;
string possible_prompt = answer.from(i);
#if RUNTIME_REGEX
// Marc Lepage <mlepage@kingston.hummingbird.com> says that
// DBX on Solaris 2.5 has a prompt like `(dbx N) '. We're
// liberal here and allow anything in the form `(NAME) ',
// where the first word in NAME must contain a `db'.
static regex rxprompt("[(][^ )]*db[^)]*[)] ");
#endif
if (possible_prompt.matches(rxprompt))
{
last_prompt = possible_prompt;
recording(false);
return true;
}
return false;
}
case XDB:
{
// Any line equal to `>' is a prompt.
unsigned beginning_of_line = answer.index('\n', -1) + 1;
if (beginning_of_line < answer.length()
&& answer.length() > 0
&& answer[beginning_of_line] == '>')
{
last_prompt = ">";
return true;
}
return false;
}
case PERL:
{
// Any line ending in `DB<N> ' is a prompt.
// Since N does not make sense in DDD, we use `DB<> ' instead.
#if RUNTIME_REGEX
static regex rxperlprompt("[ \t]*DB<+[0-9]*>+[ \t]*");
#endif
int i = answer.length() - 1;
if (i < 1 || answer[i] != ' ' || answer[i - 1] != '>')
return false;
while (i > 0 && answer[i - 1] != '\n' && !answer.contains("DB", i))
i--;
string possible_prompt = answer.from(i);
if (possible_prompt.matches(rxperlprompt))
{
last_prompt = "DB<> ";
return true;
}
return false;
}
case JDB:
{
// JDB prompts using "> " or "THREAD[DEPTH] ". All these
// prompts may also occur asynchronously.
#if RUNTIME_REGEX
// Threaded prompt: "THREAD[DEPTH] "
static regex rxjdbprompt
("[a-zA-Z][a-zA-Z0-9 ]*[a-zA-Z0-9][[][1-9][0-9]*[]] ");
// Same, but in reverse
static regex rxjdbprompt_reverse
(" []][0-9]*[1-9][[][a-zA-Z0-9][a-zA-Z0-9 ]*[a-zA-Z]");
// Non-threaded prompt: "[DEPTH] " or "> "
static regex rxjdbprompt_nothread
("(>|[[][1-9][0-9]*[]]) ");
#endif
// Check for threaded prompt at the end of the last line
string reverse_answer = reverse(answer);
int match_len = rxjdbprompt_reverse.match(reverse_answer.chars(),
reverse_answer.length(), 0);
if (match_len > 0)
{
last_prompt = reverse(reverse_answer.at(0, match_len));
return true;
}
// Check for non-threaded prompt as the last line
int beginning_of_line = answer.index('\n', -1) + 1;
string possible_prompt = ((string &) answer).from(beginning_of_line);
if (possible_prompt.matches(rxjdbprompt_nothread))
{
last_prompt = possible_prompt;
return true;
}
// Check for threaded prompt at the beginning of each line
int last_nl = answer.length() - 1;
while (last_nl >= 0)
{
last_nl = answer.index('\n', last_nl - answer.length());
int beginning_of_line = last_nl + 1;
match_len = rxjdbprompt.match(answer.chars(), answer.length(),
beginning_of_line);
if (match_len > 0)
{
int i = beginning_of_line + match_len;
while (i < int(answer.length()) && isspace(answer[i]))
i++;
if (i < int(answer.length()) && answer[i] == '=')
{
// This is no prompt, but something like `dates[1] = 33'.
}
else
{
last_prompt = answer.at(beginning_of_line, match_len);
return true;
}
}
last_nl--;
}
return false;
}
}
return false; // Never reached
}
static bool ends_in(const string& answer, const string& prompt)
{
return answer.contains(prompt, answer.length() - prompt.length());
}
// Return true iff ANSWER ends with secondary prompt.
bool GDBAgent::ends_with_secondary_prompt (const string& ans)
{
string answer = ans;
strip_control(answer);
switch (type())
{
case DBX:
if (ends_in(answer, "]: "))
{
// AIX DBX issues `Select one of [FROM - TO]: ' in the last line
// Reported by Jonathan Edwards <edwards@intranet.com>
#if RUNTIME_REGEX
static regex rxselect("Select one of \\[[0-9]+ - [0-9]+\\]: ");
#endif
int idx = index(answer, rxselect, "Select one of ", -1);
if (idx >= 0 && answer.index('\n', idx) < 0)
return true;
}
// Prompt is `> ' at beginning of line
return answer == "> " || ends_in(answer, "\n> ");
case GDB:
// Prompt is `> ' at beginning of line
return answer == "> " || ends_in(answer, "\n> ");
case XDB:
case JDB:
case PYDB:
case PERL:
// Is there any secondary prompt in these debuggers? (FIXME)
return false;
}
return false; // Never reached
}
// Check if ANSWER requires an immediate reply; return it.
string GDBAgent::requires_reply (const string& answer)
{
// GDB says: `---Type <return> to continue, or q <return> to quit---'
// DBX 3.0 says: `line N', `(END)', `(press RETURN)', and `More (n if no)?'
// XDB says: `--More--' and `Hit RETURN to continue'.
// Escape sequences may also be embedded, but the prompt never
// ends in a newline.
if (answer.contains('\n', -1) || ends_with_prompt(answer))
return "";
int last_line_index = answer.index('\n', -1) + 1;
string last_line = answer.chars() + last_line_index;
last_line.downcase();
strip_control(last_line);
if (last_line.contains("end")
|| last_line.contains("line")
|| last_line.contains("more")
|| last_line.contains("cont:")
|| last_line.contains("return"))
{
#if RUNTIME_REGEX
static regex rxq(".*[(]END[)][^\n]*");
#endif
if (answer.matches(rxq, last_line_index))
return "q"; // Stop this
#if RUNTIME_REGEX
static regex rxspace(".*(--More--|line [0-9])[^\n]*");
#endif
if (answer.matches(rxspace, last_line_index))
return " "; // Keep on scrolling
#if RUNTIME_REGEX
static regex rxreturn(".*([(]press RETURN[)]"
"|Hit RETURN to continue"
"|Type <return> to continue"
"| cont: "
"|More [(]n if no[)][?])[^\n]*");
#endif
if (answer.matches(rxreturn, last_line_index))
return "\n"; // Keep on scrolling
if (type() == XDB)
{
// Added regular expression for "Standard input: END" to
// GDBAgent::requires_reply
// -- wiegand@kong.gsfc.nasa.gov (Robert Wiegand)
#if RUNTIME_REGEX
static regex rxxdb(".*Standard input: END.*");
#endif
if (answer.matches(rxxdb, last_line_index))
return "\n"; // Keep on scrolling
}
}
return "";
}
//-----------------------------------------------------------------------------
// Filters
//-----------------------------------------------------------------------------
// Normalize answer - handle control characters, remove comments and prompt
void GDBAgent::normalize_answer(string& answer) const
{
strip_control(answer);
strip_dbx_comments(answer);
cut_off_prompt(answer);
}
// Remove GDB prompt
void GDBAgent::cut_off_prompt(string& answer) const
{
switch (type())
{
case GDB:
if (recording() && answer.contains('>', -1))
{
answer = answer.before('>', -1);
break;
}
// FALL THROUGH
case DBX:
case PYDB:
answer = answer.before('(', -1);
break;
case XDB:
answer = answer.before('>', -1);
break;
case PERL:
{
int i = answer.index("DB<", -1);
while (i > 0 && answer[i - 1] == ' ')
i--;
answer.from(i) = "";
break;
}
case JDB:
{
// Check for prompt at the end of the last line
if (answer.contains(last_prompt, -1))
{
answer = answer.before(int(answer.length()) -
int(last_prompt.length()));
}
break;
}
}
}
// Strip annoying DBX comments
void GDBAgent::strip_dbx_comments(string& s) const
{
if (type() == DBX)
{
// Weed out annoying DBX warnings like
// `WARNING: terminal cannot clear to end of line'
for (;;)
{
int warning = s.index("WARNING: terminal cannot ");
if (warning < 0)
break;
int eol = s.index('\n', warning) + 1;
if (eol <= 0)
eol = s.length();
s(warning, eol - warning) = "";
}
}
// If we're verbatim, leave output unchanged.
if (verbatim())
return;
// All remaining problems occur in Sun DBX 3.x only.
if (!has_print_r_option())
return;
if (s.contains('/'))
{
// Check for C and C++ comments
char quoted = '\0';
unsigned int i = 0;
while (i < s.length())
{
char c = s[i++];
switch (c)
{
case '\\':
if (i < s.length())
i++;
break;
case '\'':
case '\"':
if (c == quoted)
quoted = '\0';
else if (!quoted)
quoted = c;
break;
case '/':
if (i < s.length() && !quoted)
{
if (s[i] == '*')
{
/* C-style comment */
int end = s.index("*/", i + 1);
if (end == -1)
{
// unterminated comment -- keep it now
break;
}
// Remove comment
i--;
s.at(int(i), int(end - i + 2)) = "";
}
else if (s[i] == '/')
{
// C++-style comment
int end = s.index('\n', i + 1);
i--;
// Remove comment
if (end == -1)
s.from(int(i)) = "";
else
s.at(int(i), int(end - i)) = "";
}
}
}
}
}
if (s.contains("dbx: warning:"))
{
// Weed out annoying DBX warnings like
// `dbx: warning: -r option only recognized for C++' and
// `dbx: warning: unknown language, 'c' assumed'
#if RUNTIME_REGEX
static regex rxdbxwarn1("dbx: warning:[^\n]*"
"option only recognized for[^\n]*\n");
static regex rxdbxwarn2("dbx: warning:[^\n]*"
"unknown language[^\n]*\n");
#endif
s.gsub(rxdbxwarn1, "");
s.gsub(rxdbxwarn2, "");
}
}
// Strip control characters
void GDBAgent::strip_control(string& answer) const
{
int source_index = 0;
int target_index = 0;
for (source_index = 0; source_index < int(answer.length()); source_index++)
{
char c = answer[source_index];
switch (c)
{
case '\b':
// Delete last character
if (target_index > 0 && answer[target_index - 1] != '\n')
target_index--;
else
{
// Nothing to erase -- keep the '\b'.
goto copy;
}
break;
case '\r':
if (source_index + 1 < int(answer.length()))
{
if (answer[source_index + 1] == '\n' ||
answer[source_index + 1] == '\r')
{
// '\r' followed by '\n' or '\r' -- don't copy
break;
}
else
{
// '\r' followed by something else:
// Return to beginning of line
while (target_index > 0 &&
answer[target_index - 1] != '\n')
target_index--;
}
}
else
{
// Trailing '\r' -- keep it
goto copy;
}
break;
case '\032':
// In annotation level 2, GDB sends out `annotation'
// sequences like `\n\032\032prompt\n'. Future DDD
// versions might want to look at these annotations; right
// now, we simply weed them out.
if (target_index > 0 &&
answer[target_index - 1] == '\n' &&
source_index + 1 < int(answer.length()) &&
answer[source_index + 1] == '\032')
{
// Ignore everything up to and including the next '\n'.
int i = source_index;
while (i < int(answer.length()) &&
answer[i] != '\n' && answer[i] != ':')
i++;
if (i >= int(answer.length()))
{
// The annotation is not finished yet -- copy it
goto copy;
}
else if (answer[i] == ':')
{
// This is a normal `fullname' annotation, handled by DDD
goto copy;
}
else
{
// Annotation found -- ignore it
assert(answer[target_index - 1] == '\n');
target_index--;
assert(answer[i] == '\n');
source_index = i;
}
}
else
{
// Single or trailing `\032' -- keep it
goto copy;
}
break;
case '\033': // aka `\e'
// XDB `more' sends VT100 escape sequences like `\e[m',
// `\e[22;1H', `\e[7m', `\e[K', regardless of TERM
// settings. We simply weed out everything up to and
// including to the next letter.
while (source_index < int(answer.length()) &&
!isalpha(answer[source_index]))
source_index++;
if (source_index >= int(answer.length()))
{
// The escape sequence is not finished yet - keep the '\e'.
answer[target_index++] = c;
}
break;
copy:
default:
// Leave character unchanged
answer[target_index++] = answer[source_index];
break;
}
}
answer = answer.before(target_index);
}
//-----------------------------------------------------------------------------
// Event handlers
//-----------------------------------------------------------------------------
// Received data from GDB
void GDBAgent::InputHP(Agent *agent, void *, void *call_data)
{
GDBAgent* gdb = ptr_cast(GDBAgent, agent);
assert(gdb != 0);
DataLength* dl = (DataLength *) call_data;
string answer(dl->data, dl->length);
gdb->handle_input(answer);
}
void GDBAgent::handle_echo(string& answer)
{
// If we don't detect echos, leave output unchanged.
if (!detect_echos())
return;
// Check for echoed characters. Every now and then, the TTY setup
// fails such that we get characters echoed back. This may also
// happen with remote connections.
if (echoed_characters >= 0)
{
int i = 0;
int e = echoed_characters;
while (i < int(answer.length()) && e < int(last_written.length()))
{
if (answer[i] == '\r')
{
// Ignore '\r' in comparisons
i++;
}
else if (answer[i] == last_written[e])
{
i++, e++;
}
else
{
// No echo
break;
}
}
if (e >= int(last_written.length()))
{
// All characters written have been echoed.
// Remove echoed characters and keep on processing.
callHandlers(EchoDetected, (void *)true);
answer = answer.from(i);
echoed_characters = -1;
}
else if (i >= int(answer.length()))
{
// A prefix of the last characters written has been echoed.
// Wait for further echos.
answer = "";
echoed_characters = e;
}
else
{
// No echo.
// => Restore any echoed characters and keep on processing
answer.prepend(last_written.before(echoed_characters));
echoed_characters = -1;
// If a command of length ECHO_THRESHOLD is not echoed,
// disable echo detection. The idea behind this is that
// echo disabling seems to have succeeded and we thus no
// longer need to check for echos. This reduces the risk
// of echo detection altering output data.
// ECHO_THRESHOLD is 4 because the inferior debugger might
// not echo short interludes like `yes\n', `no\n' or `^C'.
const unsigned int ECHO_THRESHOLD = 4;
if (last_written.length() > ECHO_THRESHOLD)
{
// No more echos - disable echo detection until re-activated
callHandlers(EchoDetected, (void *)false);
detect_echos(false);
}
}
}
}
void GDBAgent::handle_more(string& answer)
{
// Check for `More' prompt
string reply = requires_reply(answer);
if (reply != "")
{
// Oops - this should not happen. Just hit the reply key.
write(reply);
flush();
// Ignore the last line (containing the `More' prompt)
int last_beginning_of_line = answer.index('\n', -1) + 1;
answer.from(last_beginning_of_line) = "";
}
}
void GDBAgent::handle_reply(string& answer)
{
if (recording())
return;
// Check for secondary prompt
if (ends_with_secondary_prompt(answer))
{
// GDB requires more information here: probably the
// selection of an ambiguous C++ name.
// We simply try the first alternative here:
// - in GDB, this means `all';
// - in DBX and XDB, this is a non-deterministic selection.
ReplyRequiredInfo info;
info.question = answer;
info.reply = "1\n";
// Allow handlers to override the default reply
callHandlers(ReplyRequired, (void *)&info);
// Send reply immediately
write(info.reply);
flush();
// Ignore the selection
answer = info.question;
}
}
bool GDBAgent::recording(bool val)
{
if (_recording != val)
{
_recording = val;
callHandlers(Recording, (void *)recording());
}
return recording();
}
void GDBAgent::handle_input(string& answer)
{
handle_echo(answer);
handle_more(answer);
handle_reply(answer);
// Handle all other GDB output, depending on current state.
switch (state)
{
case ReadyWithPrompt:
// We have a prompt, and still get input? Maybe this is an
// answer to a command sent before the prompt was received -
// or a reply to a control character (`Quit').
strip_control(answer);
callHandlers(AsyncAnswer, (void *)&answer);
break;
case BusyOnInitialCmds:
case BusyOnCmd:
complete_answer += answer;
if (_on_answer != 0)
{
if (ends_with_prompt(complete_answer))
normalize_answer(answer);
else
{
strip_control(answer);
strip_dbx_comments(answer);
}
_on_answer(answer, _user_data);
}
if (ends_with_prompt(complete_answer))
{
// Received complete answer (GDB issued prompt)
// Set new state and call answer procedure
if (state == BusyOnInitialCmds)
{
if (!questions_waiting)
{
state = ReadyWithPrompt;
callHandlers(ReadyForCmd, (void *)true);
callHandlers(ReadyForQuestion, (void *)true);
if (_on_answer_completion != 0)
_on_answer_completion (_user_data);
}
else
{
state = BusyOnQuArray;
// Send first question
write(cmd_array[0]);
flush();
}
}
else if (!questions_waiting)
{
state = ReadyWithPrompt;
callHandlers(ReadyForQuestion, (void *)true);
if (_on_answer_completion != 0)
_on_answer_completion (_user_data);
}
else
{
state = BusyOnQuArray;
callHandlers(ReadyForCmd, (void *)false);
// Send first question
write(cmd_array[0]);
flush();
}
}
break;
case BusyOnQuestion:
complete_answer += answer;
if (ends_with_prompt(complete_answer))
{
// Received complete answer (GDB issued prompt)
normalize_answer(complete_answer);
// Set new state
state = ReadyWithPrompt;
callHandlers(ReadyForQuestion, (void *)true);
callHandlers(ReadyForCmd, (void *)true);
if (_on_question_completion != 0)
{
// We use a local copy of COMPLETE_ANSWER here, since
// the callback may submit a new query, overriding
// the original value.
string c(complete_answer);
_on_question_completion(c, _qu_data);
}
}
break;
case BusyOnQuArray:
complete_answers[qu_index] += answer;
if (ends_with_prompt(complete_answers[qu_index]))
{
// Answer is complete (GDB issued prompt)
normalize_answer(complete_answers[qu_index]);
if (qu_index == _qu_count - 1)
{
// Received all answers -- we're ready again
state = ReadyWithPrompt;
callHandlers(ReadyForQuestion, (void *)true);
callHandlers(ReadyForCmd, (void *)true);
if (questions_waiting || _on_qu_array_completion != 0)
{
// We use a local copy of the answers and user
// data here, since the callback may submit a new
// query, overriding the original value.
StringArray answers(complete_answers);
VoidArray datas(_qu_datas);
OQACProc array_completion = _on_qu_array_completion;
OACProc answer_completion = _on_answer_completion;
void *array_data = _qa_data;
if (questions_waiting)
{
// We did not call the OACProc yet.
questions_waiting = false;
if (answer_completion != 0)
answer_completion(_user_data);
}
if (array_completion != 0)
array_completion(answers, datas, array_data);
}
}
else
{
// Send next question
write(cmd_array[++qu_index]);
flush();
}
}
break;
default:
assert(0);
break;
}
}
// GDB died
void GDBAgent::DiedHP(Agent *agent, void *, void *)
{
GDBAgent *gdb = ptr_cast(GDBAgent, agent);
gdb->handle_died();
}
void GDBAgent::handle_died()
{
// We have no prompt
last_prompt = "";
// Call answer completion handlers
switch (state)
{
case ReadyWithPrompt:
break;
case BusyOnInitialCmds:
case BusyOnCmd:
if (_on_answer_completion != 0)
_on_answer_completion (_user_data);
break;
case BusyOnQuestion:
if (_on_question_completion != 0)
_on_question_completion (complete_answer, _qu_data);
break;
case BusyOnQuArray:
if (_on_qu_array_completion != 0)
_on_qu_array_completion(complete_answers, _qu_datas, _qa_data);
break;
}
// We're not ready anymore
state = BusyOnCmd;
complete_answer = "";
callHandlers(ReadyForQuestion, (void *)false);
callHandlers(ReadyForCmd, (void *)false);
}
//-----------------------------------------------------------------------------
// Configuration
//-----------------------------------------------------------------------------
// DBX 3.0 wants `print -r' instead of `print' for C++
string GDBAgent::print_command(string expr, bool internal) const
{
string cmd;
switch (type())
{
case GDB:
case DBX:
if (internal && has_output_command())
cmd = "output";
else
cmd = "print";
if (has_print_r_option())
cmd += " -r";
break;
case XDB:
case PYDB:
cmd = "p";
break;
case PERL:
cmd = "x";
break;
case JDB:
if (internal)
cmd = "dump";
else
cmd = "print";
break;
}
if (expr != "")
cmd += " " + expr;
return cmd;
}
// DBX 3.0 wants `display -r' instead of `display' for C++
string GDBAgent::display_command(string expr) const
{
if (!has_display_command())
return "";
string cmd;
if (has_print_r_option() && expr != "")
cmd = "display -r";
else
cmd = "display";
if (expr != "")
cmd += " " + expr;
return cmd;
}
// DBX 3.0 wants `where -h' instead of `where'
string GDBAgent::where_command(int count) const
{
string cmd;
switch (type())
{
case GDB:
case DBX:
case JDB:
case PYDB:
if (has_where_h_option())
cmd = "where -h";
else
cmd = "where";
break;
case PERL:
cmd = "T";
break;
case XDB:
cmd = "t";
break;
}
if (count != 0)
cmd += " " + itostring(count);
return cmd;
}
string GDBAgent::info_locals_command() const
{
switch (type())
{
case GDB:
case PYDB:
return "info locals";
case DBX:
return "dump";
case XDB:
return "l";
case JDB:
return "locals";
case PERL:
return "V";
}
return ""; // Never reached
}
string GDBAgent::info_args_command() const
{
switch (type())
{
case GDB:
case PYDB:
return "info args";
default:
break;
}
return info_locals_command();
}
string GDBAgent::info_display_command() const
{
if (type() == GDB)
return "info display";
else
return display_command();
}
// Some DBXes want `sh pwd' instead of `pwd'
string GDBAgent::pwd_command() const
{
switch (type())
{
case GDB:
case DBX:
case PYDB:
if (has_pwd_command())
return "pwd";
else
return "sh pwd";
case XDB:
return "!pwd";
case PERL:
return "p $ENV{'PWD'} || `pwd`";
case JDB:
return "";
}
return ""; // Never reached
}
// Some DBXes want `sh make' instead of `make'
string GDBAgent::make_command(string args) const
{
string cmd;
switch (type())
{
case GDB:
case DBX:
if (has_make_command())
cmd = "make";
else
cmd = "sh make";
break;
case XDB:
cmd = "!make";
break;
case PERL:
if (args == "")
return "system 'make'";
else
return "system 'make " + args + "'";
case JDB:
case PYDB:
return ""; // Not available
}
if (args == "")
return cmd;
else
return cmd + " " + args;
}
// Move PC to POS
string GDBAgent::jump_command(string pos) const
{
if (!has_jump_command())
return "";
switch (type())
{
case GDB:
return "jump " + pos;
case XDB:
{
if (pos.contains('*', 0))
pos = pos.after('*');
return "g " + pos;
}
case DBX:
return "cont at " + pos;
case JDB:
case PYDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Show registers
string GDBAgent::regs_command(bool all) const
{
if (!has_regs_command())
return "";
switch (type())
{
case GDB:
if (all)
return "info all-registers";
else
return "info registers";
case DBX:
if (all)
return "regs -F"; // Sun DBX 4.0
else
return "regs";
case XDB:
case JDB:
case PYDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Watch expressions
string GDBAgent::watch_command(string expr, WatchMode w) const
{
if ((has_watch_command() & w) != w)
return "";
switch (type())
{
case GDB:
if ((w & WATCH_CHANGE) == WATCH_CHANGE)
return "watch " + expr;
if ((w & WATCH_ACCESS) == WATCH_ACCESS)
return "awatch " + expr;
if ((w & WATCH_READ) == WATCH_READ)
return "rwatch " + expr;
return "";
case DBX:
if ((w & WATCH_CHANGE) == WATCH_CHANGE)
{
if (has_handler_command())
{
// Solaris 2.6 DBX wants `stop change VARIABLE'
return "stop change " + expr;
}
else
{
// `Traditional' DBX notation
return "stop " + expr;
}
}
return "";
case XDB:
// Not available. (There is the `assertion' concept which is
// similar but won't fit into the DDD GUI.)
return "";
case JDB:
case PYDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
string GDBAgent::kill_command() const
{
switch (type())
{
case GDB:
case DBX:
return "kill";
case XDB:
return "k";
case PYDB:
return "kill";
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
string GDBAgent::frame_command() const
{
switch (type())
{
case GDB:
case DBX:
if (has_frame_command())
return "frame";
else
return where_command(1);
case XDB:
return print_command("$depth");
case PYDB:
return where_command(0);
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
string GDBAgent::frame_command(int num) const
{
if (!has_frame_command())
return "";
switch (type())
{
case GDB:
case DBX:
return frame_command() + " " + itostring(num);
case XDB:
return "V " + itostring(num);
case JDB:
case PYDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Add OFFSET to current frame (i.e. OFFSET > 0: up, OFFSET < 0: down)
string GDBAgent::relative_frame_command(int offset) const
{
if (gdb->type() == PERL)
return ""; // No such command in Perl
if (offset == -1)
return "down";
else if (offset < 0)
return "down " + itostring(-offset);
else if (offset == 1)
return "up";
else if (offset > 0)
return "up " + itostring(offset);
return ""; // Offset == 0
}
string GDBAgent::func_command() const
{
switch (type())
{
case GDB:
case XDB:
case JDB:
case PYDB:
case PERL:
return frame_command();
case DBX:
return has_func_command() ? "func" : "";
}
return ""; // Never reached
}
// Each debugger has its own way of echoing (sigh)
string GDBAgent::echo_command(string text) const
{
switch (type())
{
case GDB:
return "echo " + cook(text);
case DBX:
return print_command(quote(text), false);
case XDB:
return quote(text);
case PERL:
if (text.contains('\n', -1))
text = text.before(-1);
return "p " + quote(text, '\'');
case JDB:
case PYDB:
return ""; // Not available
}
return ""; // Never reached
}
// Prefer `ptype' on `whatis' in GDB
string GDBAgent::whatis_command(string text) const
{
switch (type())
{
case GDB:
return "ptype " + text;
case DBX:
case PYDB:
if (has_print_r_option())
return "whatis -r " + text;
else
return "whatis " + text;
case XDB:
return "p " + text + "\\T";
case JDB:
return "dump " + text; // As close as we can get
case PERL:
return ""; // Who knows?
}
return ""; // Never reached
}
// Enable breakpoint BP
string GDBAgent::enable_command(string bp) const
{
if (bp != "")
bp.prepend(' ');
switch (type())
{
case GDB:
case PYDB:
return "enable" + bp;
case DBX:
if (has_handler_command())
return "handler -enable" + bp;
else
return "";
case XDB:
return "ab" + bp;
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Disable breakpoint BP
string GDBAgent::disable_command(string bp) const
{
if (bp != "")
bp.prepend(' ');
switch (type())
{
case GDB:
case PYDB:
return "disable" + bp;
case DBX:
if (has_handler_command())
return "handler -disable" + bp;
else
return "";
case XDB:
return "sb" + bp;
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Delete breakpoint BP
string GDBAgent::delete_command(string bp) const
{
if (bp != "")
bp.prepend(' ');
switch (type())
{
case DBX:
case GDB:
case PYDB:
return "delete" + bp;
case XDB:
return "db" + bp;
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Set ignore count of breakpoint BP to COUNT
string GDBAgent::ignore_command(string bp, int count) const
{
switch (type())
{
case GDB:
case PYDB:
return "ignore " + bp + " " + itostring(count);
case DBX:
if (has_handler_command())
return "handler -count " + bp + " " + itostring(count);
else
return "";
case XDB:
return "bc " + bp + " " + itostring(count);
case JDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Set condition of breakpoint BP to EXPR
string GDBAgent::condition_command(string bp, string expr) const
{
switch (type())
{
case GDB:
case PYDB:
return "condition " + bp + " " + expr;
case DBX:
case XDB:
case JDB:
case PERL:
return ""; // FIXME
}
return ""; // Never reached
}
// Return shell escape command
string GDBAgent::shell_command(string cmd) const
{
switch (type())
{
case GDB:
return "shell " + cmd;
case DBX:
return "sh " + cmd;
case XDB:
return "!" + cmd;
case PERL:
return "system " + quote(cmd, '\'');
case JDB:
case PYDB:
return ""; // Not available
}
return ""; // Never reached
}
// Return command to debug PROGRAM
string GDBAgent::debug_command(string program, string args) const
{
if (args != "" && !args.contains(' ', 0))
args = " " + args;
switch (type())
{
case GDB:
case PYDB:
return "file " + program;
case DBX:
if (has_givenfile_command())
return "givenfile " + program; // SGI DBX
else
return "debug " + program; // SUN DBX
case XDB:
return "#file " + program; // just a dummy
case JDB:
return "load " + program;
case PERL:
return "exec " + quote("perl -d " + program + args);
}
return ""; // Never reached
}
// Return command to send signal SIG
string GDBAgent::signal_command(int sig) const
{
string n = itostring(sig);
switch (type())
{
case GDB:
return "signal " + n;
case DBX:
if (has_cont_sig_command())
return "cont sig " + n; // SUN DBX
else
return "cont " + n; // Other DBXes
case XDB:
return "p $signal = " + n + "; C";
case JDB:
case PYDB:
case PERL:
return ""; // Not available
}
return ""; // Never reached
}
// Return a command that does nothing.
string GDBAgent::nop_command(string comment) const
{
return "# " + comment; // Works for all inferior debuggers
}
// Run program with given ARGS
string GDBAgent::run_command(string args) const
{
if (args != "" && !args.contains(' ', 0))
args = " " + args;
switch (type())
{
case GDB:
{
string c;
if (args == "")
c = "set args\n";
return c + "run" + args;
}
case DBX:
if (args == "" && has_rerun_command() && rerun_clears_args())
return "rerun";
else
return "run" + args;
case JDB:
case PYDB:
return "run" + args;
case XDB:
if (args == "")
return "R";
else
return "r" + args;
case PERL:
{
string c = "R\n@ARGV = (";
while (args != "")
{
strip_leading_space(args);
string arg = read_token(args);
c += quote(arg, '\'') + ", ";
}
c += ")";
return c;
}
}
return ""; // Never reached
}
// Re-run program with previous arguments
string GDBAgent::rerun_command() const
{
switch (type())
{
case GDB:
case JDB:
case PYDB:
return "run";
case DBX:
if (has_rerun_command() && !rerun_clears_args())
return "rerun";
else
return "run";
case XDB:
return "r";
case PERL:
return "R";
}
return ""; // Never reached
}
// Return PREFIX + EXPR, parenthesizing EXPR if needed
string GDBAgent::prepend_prefix(const string& prefix, const string& expr)
{
if (expr.matches(rxidentifier)
|| expr.contains("(", 0) && expr.contains(")", -1))
return prefix + expr;
else if (expr == "")
return prefix;
else
return prefix + "(" + expr + ")";
}
// Return EXPR + SUFFIX, parenthesizing EXPR if needed
string GDBAgent::append_suffix(const string& expr, const string& suffix)
{
if (expr.matches(rxidentifier)
|| expr.contains("(", 0) && expr.contains(")", -1))
return expr + suffix;
else if (expr == "")
return suffix;
else
return "(" + expr + ")" + suffix;
}
// Dereference an expression.
string GDBAgent::dereferenced_expr(string expr) const
{
switch (program_language())
{
case LANGUAGE_C:
return prepend_prefix("*", expr);
case LANGUAGE_PERL:
// Perl has three `dereferencing' operators, depending on the
// type of reference. The `deref()' function provides a
// better solution.
return prepend_prefix("$", expr);
case LANGUAGE_FORTRAN:
// GDB prints dereferenced pointers as `**X', but accepts them as `*X'.
return prepend_prefix("*", expr);
case LANGUAGE_JAVA:
if (type() == GDB)
{
// GDB dereferences JAVA references by prepending `*'
return prepend_prefix("*", expr);
}
// JDB (and others?) dereference automatically
return expr;
case LANGUAGE_CHILL:
return append_suffix(expr, "->");
case LANGUAGE_PASCAL:
return append_suffix(expr, "^");
case LANGUAGE_ADA:
// GDB 4.16.gnat.1.13 prepends `*' as in C
return prepend_prefix("*", expr);
case LANGUAGE_PYTHON:
return ""; // No such thing in Python/PYDB
case LANGUAGE_OTHER:
return expr; // All other languages
}
return expr; // All other languages
}
// Give the address of an expression.
string GDBAgent::address_expr(string expr) const
{
if (expr.contains('/', 0))
expr = expr.after(' ');
switch (program_language())
{
case LANGUAGE_C:
return prepend_prefix("&", expr);
case LANGUAGE_PASCAL:
return "ADR(" + expr + ")"; // Modula-2 address operator
case LANGUAGE_CHILL: // FIXME: untested.
return prepend_prefix("->", expr);
case LANGUAGE_FORTRAN:
return prepend_prefix("&", expr);
case LANGUAGE_JAVA:
return ""; // Not supported in GDB
case LANGUAGE_PYTHON:
return ""; // Not supported in Python
case LANGUAGE_PERL:
return ""; // No such thing in Perl
case LANGUAGE_ADA:
return ""; // Not supported in GNAT/Ada
case LANGUAGE_OTHER:
return ""; // All other languages
}
return ""; // All other languages
}
// Give the index of an expression.
string GDBAgent::index_expr(string expr, string index) const
{
switch (program_language())
{
case LANGUAGE_FORTRAN:
case LANGUAGE_ADA:
return expr + "(" + index + ")";
case LANGUAGE_PERL:
if (expr != "" && expr[0] == '@')
return '$' + expr.after(0) + "[" + index + "]";
else
return expr + "[" + index + "]";
default:
break;
}
// All other languages
return expr + "[" + index + "]";
}
// Return default index base
int GDBAgent::default_index_base() const
{
switch (program_language())
{
case LANGUAGE_FORTRAN:
case LANGUAGE_PASCAL:
case LANGUAGE_CHILL:
return 1;
case LANGUAGE_ADA:
case LANGUAGE_C:
case LANGUAGE_JAVA:
case LANGUAGE_PYTHON:
case LANGUAGE_PERL:
case LANGUAGE_OTHER:
return 0;
}
return 0; // Never reached
}
// Return member separator
string GDBAgent::member_separator() const
{
switch (program_language())
{
case LANGUAGE_FORTRAN:
case LANGUAGE_PASCAL:
case LANGUAGE_CHILL:
case LANGUAGE_C:
case LANGUAGE_PYTHON:
case LANGUAGE_OTHER:
case LANGUAGE_JAVA:
return " = ";
case LANGUAGE_ADA:
case LANGUAGE_PERL:
return " => ";
}
return ""; // Never reached
}
// Return assignment command
string GDBAgent::assign_command(string var, string expr) const
{
string cmd;
switch (type())
{
case GDB:
cmd = "set variable";
break;
case DBX:
cmd = "assign";
break;
case XDB:
cmd = "pq";
break;
case PYDB:
cmd = ""; // No command needed
break;
case PERL:
if (var != "" || !is_perl_prefix(var[0]))
return ""; // Cannot set this
cmd = ""; // No command needed
break;
case JDB:
return ""; // Not available
}
cmd += " " + var + " ";
switch (program_language())
{
case LANGUAGE_C:
case LANGUAGE_JAVA:
case LANGUAGE_FORTRAN:
case LANGUAGE_PYTHON: // FIXME: vrbl names can conflict with commands
case LANGUAGE_PERL:
case LANGUAGE_OTHER:
cmd += "=";
break;
case LANGUAGE_ADA:
case LANGUAGE_PASCAL:
case LANGUAGE_CHILL:
cmd += ":=";
break;
}
return cmd + " " + expr;
}
void GDBAgent::normalize_address(string& addr) const
{
// In C, hexadecimal integers are specified by a leading "0x".
// In Modula-2, hexadecimal integers are specified by a trailing "H".
// In Chill, hexadecimal integers are specified by a leading "H'".
addr.downcase();
if (addr.contains("0", 0))
addr = addr.after("0");
if (addr.contains("x", 0))
addr = addr.after("x");
if (addr.contains("h'", 0))
addr = addr.after("h'");
if (addr.contains("h", -1))
addr = addr.before(int(addr.length()) - 1);
if (addr != "")
{
switch (program_language())
{
case LANGUAGE_C:
case LANGUAGE_JAVA:
case LANGUAGE_FORTRAN:
case LANGUAGE_ADA:
case LANGUAGE_PYTHON:
case LANGUAGE_PERL:
case LANGUAGE_OTHER:
addr.prepend("0x");
break;
case LANGUAGE_CHILL:
addr = "H'0" + upcase(addr);
break;
case LANGUAGE_PASCAL:
addr = "0" + upcase(addr) + "H";
break;
}
}
}
// Return disassemble command
string GDBAgent::disassemble_command(string start, string end) const
{
if (type() != GDB)
return "";
normalize_address(start);
string cmd = "disassemble " + start;
if (end != "")
{
normalize_address(end);
cmd += " " + end;
}
return cmd;
}
string GDBAgent::history_file() const
{
switch (type())
{
case GDB:
{
char *g = getenv("GDBHISTFILE");
if (g != 0)
return g;
else
return "./.gdb_history";
}
case DBX:
case JDB:
case PYDB:
case PERL:
return ""; // Unknown
case XDB:
{
char *g = getenv("XDBHIST");
if (g != 0)
return g;
else
return string(gethome()) + "/.xdbhist";
}
}
return ""; // Unknown
}
// Return true iff A contains the word S
static bool contains_word(const string& a, const string& s)
{
int index = -1;
for (;;)
{
index = a.index(s, index + 1);
if (index < 0)
break;
if (index > 0 && isalpha(a[index - 1]))
continue; // Letter before S
int next = index + s.length();
if (next < int(a.length()) && isalpha(a[next]))
continue; // Letter after S
return true; // Okay
}
return false; // Not found
}
// Determine language from TEXT
ProgramLanguage GDBAgent::program_language(string text)
{
text.downcase();
if (type() == GDB && text.contains("language"))
text = text.after("language");
if (text.contains("\n"));
text = text.before("\n");
static struct {
char *name;
ProgramLanguage language;
} language_table[] = {
{ "fortran", LANGUAGE_FORTRAN },
{ "f", LANGUAGE_FORTRAN }, // F77, F90, F
{ "java", LANGUAGE_JAVA },
{ "chill", LANGUAGE_CHILL },
{ "pascal", LANGUAGE_PASCAL },
{ "modula", LANGUAGE_PASCAL },
{ "m", LANGUAGE_PASCAL }, // M2, M3 or likewise
{ "ada", LANGUAGE_ADA },
{ "python", LANGUAGE_PYTHON },
{ "perl", LANGUAGE_PERL },
{ "c", LANGUAGE_C },
{ "c++", LANGUAGE_C },
{ "auto", LANGUAGE_OTHER } // Keep current language
};
for (int i = 0;
i < int(sizeof(language_table) / sizeof(language_table[0])); i++)
{
if (contains_word(text, language_table[i].name))
{
ProgramLanguage new_language = language_table[i].language;
if (new_language != LANGUAGE_OTHER)
program_language(new_language);
return program_language();
}
}
// Unknown language - switch to default setting
program_language(LANGUAGE_C);
return program_language();
}
//-----------------------------------------------------------------------------
// Perl specials
//-----------------------------------------------------------------------------
void GDBAgent::split_perl_var(const string& var,
string& prefix,
string& package,
string& name)
{
name = var.from(rxalpha);
if (name == "")
name = var.from("_");
if (name == "")
return;
prefix = var.before(name);
package = "";
if (name.contains("::"))
{
package = name.before("::", -1);
name = name.after(package + "::");
}
}
// Bring VALUE into a form that might be recognized by DDD
void GDBAgent::munch_perl_scalar(string& value)
{
strip_leading_space(value);
if (value.contains("0 ", 0))
value = value.after("0");
strip_leading_space(value);
if (value.contains(rxperlref, 0))
value = value.before('\n');
}
// Bring VALUE into a form that might be recognized by DDD
void GDBAgent::munch_perl_array(string& value, bool hash)
{
int n = value.freq('\n');
string *lines = new string[n + 1];
split(value, lines, n + 1, '\n');
bool compact = false;
bool first_elem = true;
string new_value;
bool arrow = true;
for (int i = 0; i < n; i++)
{
string line = lines[i];
if (!compact && line.contains(' ', 0))
continue; // Sub-element; ignore line
strip_space(line);
if (!compact && line.contains(rxint, 0))
{
// Strip index
line = line.after(rxint);
if (line.contains("..", 0))
{
// In compact representation, Perl omits individual
// indexes, and puts a START..END index instead.
compact = true;
line = line.after(rxint);
}
strip_space(line);
}
if (!first_elem)
{
if (hash && arrow)
new_value += " => ";
else
new_value += ", ";
arrow = !arrow;
}
first_elem = false;
new_value += line;
}
delete[] lines;
if (!new_value.contains('(', 0))
new_value = '(' + new_value + ')';
value = new_value;
}
// Bring VALUE of VAR into a form understood by DDD
void GDBAgent::munch_value(string& value, const string& var) const
{
while (value.contains(var + " = ", 0))
value = value.after(" = ");
strip_leading_space(value);
if (gdb->type() != PERL || !value.contains(rxint, 0))
return;
// Special treatment
if (var != "" && var[0] == '@')
munch_perl_array(value, false);
else if (var != "" && var[0] == '%')
munch_perl_array(value, true);
else
munch_perl_scalar(value);
}
//-----------------------------------------------------------------------------
// Handle error messages
//-----------------------------------------------------------------------------
void GDBAgent::PanicHP(Agent *source, void *, void *call_data)
{
string msg = (char *)call_data;
string path = source->path();
GDBAgent *gdb = ptr_cast(GDBAgent, source);
if (gdb != 0)
path = downcase(gdb->title());
cerr << path << ": " << msg << "\n";
}
void GDBAgent::StrangeHP(Agent *source, void *client_data, void *call_data)
{
string msg = (char *)call_data;
msg.prepend("warning: ");
PanicHP(source, client_data, (char *)msg);
}