2867 lines
89 KiB
C
2867 lines
89 KiB
C
/* Pawn debugger
|
|
*
|
|
* Simple (minimalistic) debugger that supports source-level debugging with
|
|
* several interfaces:
|
|
* - a console interface with terminal support: VT100/ANSI terminal (or the
|
|
* xterm terminal emulator), Win32 pseudo-terminal and curses (for the Apple
|
|
* Macintosh)
|
|
* - hooks to use other pseudo-terminals, such as the Win32 GUI terminal and
|
|
* the GraphApp terminal, both of which support Unicode
|
|
* - a GDB-style "streaming" interface, to hook GUI shells to the debugger (in
|
|
* a similar way as how KDbg and DDD do this with GDB)
|
|
* - a dual-screen mode where the abstract machine uses a different (pseudo)
|
|
* terminal than the debugger --especially useful when combined with a GUI
|
|
* shell
|
|
* - ability to do remote debugging over an RS232 connection
|
|
*
|
|
*
|
|
* Copyright (c) ITB CompuPhase, 1998-2006
|
|
*
|
|
* This software is provided "as-is", without any express or implied warranty.
|
|
* In no event will the authors be held liable for any damages arising from
|
|
* the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software in
|
|
* a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
* Version: $Id: pawndbg.c 3662 2006-11-07 08:44:33Z thiadmer $
|
|
*
|
|
*
|
|
* Command line options:
|
|
* -rs232=1 set remote debugging over a serial line (port number
|
|
* may be 1,2,... for Windows; 0,1,... for Linux)
|
|
* -term=x,y set terminal size
|
|
* -term=off force terminal off (if environments where it is on by
|
|
* default)
|
|
* -term=hide hide the window with the terminal, useful when using
|
|
* a "dual terminal" debugger from an IDE
|
|
* -transfer transfer file on start-up to the remote host (this
|
|
* requires remote debugging to be set first)
|
|
* -quit terminate debugger immediately, used when the debugger
|
|
* needs only to transfer a file:
|
|
* pawndbg myprog.amx -rs232=1 -transfer -quit
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <setjmp.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#if defined READLINE
|
|
#define __READLINE_IMPORT__ 1
|
|
#define __P(protos) protos
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#endif
|
|
#include "osdefs.h" /* for _MAX_PATH */
|
|
#include "amx.h"
|
|
#include "amxdbg.h"
|
|
|
|
#if defined __WIN32 || defined _WIN32 || defined __WIN32__ || defined __MSDOS__ || defined __WATCOMC__
|
|
#include <conio.h>
|
|
#if defined __WIN32 || defined _WIN32 || defined __WIN32__ || defined __WATCOMC__
|
|
#if !defined __WIN32__
|
|
#define __WIN32__ 1
|
|
#endif
|
|
#include <windows.h>
|
|
#if !defined amx_Init && !defined NO_WIN32_CONSOLE && !defined AMX_TERMINAL
|
|
#define WIN32_CONSOLE
|
|
#endif
|
|
#endif
|
|
#elif !defined macintosh
|
|
#include "../linux/getch.h"
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if !defined AMX_NODYNALOAD && (defined LINUX || defined __FreeBSD__ || defined __OpenBSD__)
|
|
#include <binreloc.h> /* from BinReloc, see www.autopackage.org */
|
|
#endif
|
|
|
|
|
|
#define MAXSTACKTRACE 128
|
|
#define MAXLINELENGTH 128
|
|
#define MAX_DIMS 3 /* number of array dimensions */
|
|
#define TABSIZE 8
|
|
#define EATLINE 5 /* the number of characters for the line number */
|
|
#define STD_COLUMNS 80 /* number of characters that fit on a line */
|
|
#if defined amx_Init
|
|
#define STD_LINES 29 /* number of lines that fit on a screen */
|
|
#else
|
|
#define STD_LINES 24 /* number of lines that fit on a screen */
|
|
#endif
|
|
#define WATCHLINES 4 /* default number of watches displayed */
|
|
#define LISTLINES (STD_LINES - WATCHLINES - 10) /* default code lines */
|
|
|
|
#if (defined AMX_TERMINAL || defined amx_Init) && !defined DBG_STREAMTERM
|
|
/* required functions are implemented elsewhere */
|
|
int amx_printf(char *,...);
|
|
int amx_putchar(int);
|
|
int amx_fflush(void);
|
|
int amx_getch(void);
|
|
char *amx_gets(char *,int);
|
|
int amx_termctl(int,int);
|
|
void amx_clrscr(void);
|
|
void amx_clreol(void);
|
|
void amx_gotoxy(int x,int y);
|
|
void amx_wherexy(int *x,int *y);
|
|
unsigned int amx_setattr(int foregr,int backgr,int highlight);
|
|
void amx_console(int columns, int lines, int flags);
|
|
#if defined amx_Init
|
|
#define STR_PROMPT "dbg\xbb "
|
|
#define CHR_HLINE '\x97'
|
|
#else
|
|
#define STR_PROMPT "dbg> "
|
|
#define CHR_HLINE '-'
|
|
#endif
|
|
#define CHR_VLINE '|'
|
|
#elif defined VT100 || defined LINUX || defined ANSITERM
|
|
/* ANSI/VT100 terminal, or shell emulating "xterm" */
|
|
#if !defined VT100 && !defined ANSITERM && defined LINUX
|
|
#define VT100
|
|
#endif
|
|
#define amx_printf printf
|
|
#define amx_putchar(c) putchar(c)
|
|
#define amx_fflush() fflush(stdout)
|
|
#define amx_getch() getch()
|
|
#define amx_gets(s,n) fgets(s,n,stdin)
|
|
int amx_termctl(int,int);
|
|
void amx_clrscr(void);
|
|
void amx_clreol(void);
|
|
void amx_gotoxy(int x,int y);
|
|
void amx_wherexy(int *x,int *y);
|
|
unsigned int amx_setattr(int foregr,int backgr,int highlight);
|
|
void amx_console(int columns, int lines, int flags);
|
|
#define STR_PROMPT "dbg> "
|
|
#define CHR_HLINE '-'
|
|
#define CHR_VLINE '|'
|
|
#elif defined WIN32_CONSOLE
|
|
/* Win32 console */
|
|
#define amx_printf printf
|
|
#define amx_putchar(c) putchar(c)
|
|
#define amx_fflush() fflush(stdout)
|
|
#define amx_getch() getch()
|
|
#define amx_gets(s,n) fgets(s,n,stdin)
|
|
int amx_termctl(int,int);
|
|
void amx_clrscr(void);
|
|
void amx_clreol(void);
|
|
void amx_gotoxy(int x,int y);
|
|
void amx_wherexy(int *x,int *y);
|
|
unsigned int amx_setattr(int foregr,int backgr,int highlight);
|
|
void amx_console(int columns, int lines, int flags);
|
|
#define STR_PROMPT "dbg> "
|
|
#define CHR_HLINE '\xc4'
|
|
#define CHR_VLINE '\xb3'
|
|
#elif defined USE_CURSES
|
|
/* Use the "curses" library to implement the console */
|
|
const int _False = 0; /* to avoid compiler warnings */
|
|
#define amx_printf printw
|
|
#define amx_putchar(c) addch(c)
|
|
#define amx_fflush() (0)
|
|
#define amx_getch() getch()
|
|
#define amx_gets(s,n) getnstr(s,n)
|
|
#define amx_clrscr() (void)(0)
|
|
#define amx_clreol() (void)(0)
|
|
#define amx_gotoxy(x,y) (void)(0)
|
|
#define amx_wherexy(x,y) (*(x)=*(y)=0)
|
|
#define amx_setattr(c,b,h) (_False)
|
|
#define amx_termctl(c,v) (_False)
|
|
#define amx_console(c,l,f) (void)(0)
|
|
#define STR_PROMPT "dbg> "
|
|
#define CHR_HLINE '-'
|
|
#define CHR_VLINE '|'
|
|
#else
|
|
/* assume a streaming terminal; limited features (no colour, no cursor
|
|
* control)
|
|
*/
|
|
#define amx_printf printf
|
|
#define amx_putchar(c) putchar(c)
|
|
#define amx_fflush() fflush(stdout)
|
|
#define amx_getch() getch()
|
|
#define amx_gets(s,n) fgets(s,n,stdin)
|
|
#define amx_clrscr() (void)(0)
|
|
#define amx_clreol() (void)(0)
|
|
#define amx_gotoxy(x,y) ((void)(x),(void)(y),(void)(0))
|
|
#define amx_wherexy(x,y) (*(x)=*(y)=0)
|
|
#define amx_setattr(c,b,h) ((void)(c),(void)(b),(void)(h),(0))
|
|
#define amx_termctl(c,v) ((void)(c),(void)(v),(0))
|
|
#define amx_console(c,l,f) ((void)(c),(void)(l),(void)(f),(void)(0))
|
|
#define STR_PROMPT "dbg> "
|
|
#define CHR_HLINE '-'
|
|
#define CHR_VLINE '|'
|
|
#endif
|
|
#define CHR_CURLINE '*'
|
|
#if defined VT100
|
|
#define CHR_HLINE_VT100 'q' // in alternate font
|
|
#define CHR_VLINE_VT100 'x'
|
|
#endif
|
|
|
|
enum {
|
|
BP_NONE,
|
|
BP_CODE,
|
|
BP_DATA, /* ??? not implemented */
|
|
/* --- */
|
|
BP_TYPES
|
|
};
|
|
|
|
enum {
|
|
DISP_DEFAULT = 0x10,
|
|
DISP_STRING = 0x20,
|
|
DISP_BIN = 0x30, /* ??? not implemented */
|
|
DISP_HEX = 0x40,
|
|
DISP_BOOL = 0x50,
|
|
DISP_FIXED = 0x60,
|
|
DISP_FLOAT = 0x70,
|
|
};
|
|
#define DISP_MASK 0x0f
|
|
|
|
typedef struct tagBREAKPOINT {
|
|
struct tagBREAKPOINT *next;
|
|
int type; /* one of the BP_xxx types */
|
|
ucell addr; /* address (in code or data segment) */
|
|
const char *name; /* name of the symbol (function) */
|
|
int number; /* sequential breakpoint number (to refer to the breakpoint) */
|
|
} BREAKPOINT;
|
|
|
|
typedef struct tagNAMELIST {
|
|
struct tagNAMELIST *next;
|
|
char *name;
|
|
int number;
|
|
} NAMELIST;
|
|
|
|
enum {
|
|
REMOTE_NONE, /* this means "not remote" */
|
|
REMOTE_RS232,
|
|
REMOTE_UDP, /* ??? not implemented */
|
|
/* --- */
|
|
REMOTE_TYPES
|
|
};
|
|
|
|
enum {
|
|
STEPPING, /* step into functions */
|
|
STEPOVER, /* step over functions */
|
|
STEPOUT, /* run until the function returns */
|
|
RUNNING, /* just run */
|
|
};
|
|
static int runmode; /* running or tracing */
|
|
|
|
static char amx_filename[_MAX_PATH];
|
|
static const char *curfilename; /* pointer to the name of the "active" file */
|
|
static char **cursource;
|
|
static int autolist=1;
|
|
static int screencolumns=STD_COLUMNS;
|
|
static int screenlines=STD_LINES;
|
|
static int listlines=LISTLINES;
|
|
static int watchlines=WATCHLINES;
|
|
static BREAKPOINT breakpoints={ NULL };
|
|
static NAMELIST watches={ NULL };
|
|
static int curtopline; /* current line that is on top in the list */
|
|
static int recentline=-1;
|
|
static int terminal=0;
|
|
static int remote=REMOTE_NONE;
|
|
static char chr_hline=CHR_HLINE;
|
|
static char chr_vline=CHR_VLINE;
|
|
#if defined __WIN32__
|
|
HANDLE hCom=INVALID_HANDLE_VALUE;
|
|
#elif !defined __MSDOS__
|
|
int fdCom=-1;
|
|
struct termios oldtio, newtio;
|
|
#endif
|
|
static char remote_pendingbuf[30];
|
|
static int remote_pendingsize=0;
|
|
static jmp_buf restart_buf;
|
|
|
|
|
|
static void draw_hline(int forceinit)
|
|
{
|
|
static char hline_str[256] = ""; /* number of columns in a window should
|
|
* never exceed 255 */
|
|
|
|
/* initialize the string, if not yet done */
|
|
if (forceinit || strlen(hline_str)==0) {
|
|
memset(hline_str,chr_hline,sizeof hline_str);
|
|
hline_str[screencolumns]='\0';
|
|
} /* if */
|
|
|
|
#if defined VT100
|
|
if (terminal>0)
|
|
amx_printf("\016"); /* SO code to select the graphic set */
|
|
#endif
|
|
|
|
amx_printf(hline_str);
|
|
|
|
#if defined VT100
|
|
if (terminal>0)
|
|
amx_printf("\017"); /* SI code to select the standard set */
|
|
#endif
|
|
|
|
amx_fflush();
|
|
}
|
|
|
|
#if defined WIN32_CONSOLE
|
|
void win32_getscreensize(int *width,int *height)
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
assert(width!=NULL && height!=NULL);
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&csbi);
|
|
*width=(int)csbi.dwSize.X;
|
|
*height=(int)(csbi.srWindow.Bottom-csbi.srWindow.Top+1);
|
|
}
|
|
#endif
|
|
|
|
static int csrsave_x, csrsave_y;
|
|
#if !defined NDEBUG
|
|
static int csrsave_flag=0;
|
|
#endif
|
|
|
|
static void term_csrsave(void)
|
|
{
|
|
assert(terminal==1);
|
|
#if !defined NDEBUG
|
|
assert(csrsave_flag==0);
|
|
csrsave_flag++;
|
|
#endif
|
|
amx_wherexy(&csrsave_x,&csrsave_y);
|
|
}
|
|
|
|
static void term_csrrestore(void)
|
|
{
|
|
assert(terminal==1);
|
|
#if !defined NDEBUG
|
|
assert(csrsave_flag==1);
|
|
csrsave_flag--;
|
|
#endif
|
|
amx_gotoxy(csrsave_x,csrsave_y);
|
|
}
|
|
|
|
static void term_refresh(int recalc)
|
|
{
|
|
assert(terminal==1);
|
|
|
|
if (recalc) {
|
|
listlines=(screenlines-watchlines)/2;
|
|
if (listlines<LISTLINES)
|
|
listlines=LISTLINES;
|
|
} /* if */
|
|
|
|
amx_clrscr();
|
|
amx_gotoxy(1,watchlines+1);
|
|
draw_hline(1);
|
|
amx_gotoxy(1,watchlines+listlines+2);
|
|
draw_hline(0);
|
|
amx_gotoxy(1,watchlines+listlines+3);
|
|
}
|
|
|
|
static void term_statusbar(int show)
|
|
{
|
|
#if defined __WIN32__
|
|
amx_gotoxy(1,screenlines+1);
|
|
if (show) {
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F1 Help ");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F3 Quit ");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
#if !defined __WIN32__
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf("F4 Output");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
#else
|
|
amx_printf(" ");
|
|
#endif
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F5 Go ");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F6 Show ");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F8 Step ");
|
|
(void)amx_setattr(7,0,0);
|
|
amx_printf(" ");
|
|
(void)amx_setattr(0,7,0);
|
|
amx_printf(" F10 Next");
|
|
(void)amx_setattr(7,0,0);
|
|
} else {
|
|
amx_clreol();
|
|
} /* if */
|
|
amx_gotoxy(6,screenlines);
|
|
#endif
|
|
}
|
|
|
|
static void term_open(int columns,int lines)
|
|
{
|
|
terminal=1;
|
|
amx_clrscr();
|
|
(void)amx_termctl(1,0); /* disable "auto-wrap" */
|
|
amx_console(columns,lines,0); /* try to fix the requested console size */
|
|
#if defined VT100
|
|
chr_hline=CHR_HLINE_VT100;
|
|
chr_vline=CHR_VLINE_VT100;
|
|
/* a trick to get the size of the terminal is to position the cursor far
|
|
* away and then read it back
|
|
*/
|
|
amx_gotoxy(999,999);
|
|
amx_wherexy(&screencolumns,&screenlines);
|
|
screenlines--; /* keep last line empty */
|
|
amx_printf("\033[%d;%dr",watchlines+listlines+3,STD_LINES-1); /* set window */
|
|
amx_printf("\033)0"); /* select graphics codes for set G1 */
|
|
#endif
|
|
#if defined WIN32_CONSOLE
|
|
#define ENABLE_INSERT_MODE 0x0020
|
|
#define ENABLE_QUICK_EDIT_MODE 0x0040
|
|
#define ENABLE_EXTENDED_FLAGS 0x0080
|
|
#define ENABLE_AUTO_POSITION 0x0100
|
|
win32_getscreensize(&screencolumns,&screenlines);
|
|
screenlines--; /* keep last line empty */
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE),
|
|
ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT
|
|
| ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE
|
|
| ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGS);
|
|
#elif defined READLINE
|
|
rl_get_screen_size(&screencolumns,&screenlines);
|
|
screenlines--; /* keep last line empty */
|
|
#endif
|
|
term_refresh(1);
|
|
}
|
|
|
|
static void term_close(void)
|
|
{
|
|
assert(terminal==1);
|
|
#if defined VT100
|
|
amx_printf("\033[r"); /* scrolling region is full screen */
|
|
screencolumns=STD_COLUMNS;
|
|
chr_hline=CHR_HLINE;
|
|
chr_vline=CHR_VLINE;
|
|
#endif
|
|
(void)amx_termctl(1,1); /* re-enable "auto-wrap" */
|
|
amx_clrscr();
|
|
screencolumns=STD_COLUMNS;
|
|
terminal=0;
|
|
}
|
|
|
|
static void term_scroll(int top, int bottom, int lines)
|
|
{
|
|
#if defined WIN32_CONSOLE
|
|
if (terminal>0) {
|
|
SMALL_RECT ScrollRectangle;
|
|
COORD Destination;
|
|
CHAR_INFO Fill;
|
|
int screenlines,screencolumns;
|
|
|
|
win32_getscreensize(&screencolumns,&screenlines);
|
|
ScrollRectangle.Left=0;
|
|
ScrollRectangle.Top=(short)(top-1);
|
|
ScrollRectangle.Right=(short)(screencolumns-1);
|
|
ScrollRectangle.Bottom=(short)(bottom-1);
|
|
Destination.X=0;
|
|
Destination.Y=(short)(top-1-lines);
|
|
Fill.Char.UnicodeChar=' ';
|
|
Fill.Attributes=0x07;
|
|
ScrollConsoleScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE),
|
|
&ScrollRectangle,&ScrollRectangle,
|
|
Destination,&Fill);
|
|
} /* if */
|
|
#else
|
|
(void)top;
|
|
(void)bottom;
|
|
(void)lines;
|
|
#endif
|
|
}
|
|
|
|
static int term_switch(int number)
|
|
{
|
|
if (terminal>0)
|
|
return amx_termctl(2,number);
|
|
return 0;
|
|
}
|
|
|
|
static void source_free(char **source)
|
|
{
|
|
int i;
|
|
|
|
assert(source!=NULL);
|
|
for (i=0; source[i]!=NULL; i++)
|
|
free(source[i]);
|
|
free(source);
|
|
}
|
|
|
|
static char **source_load(const char *filename)
|
|
{
|
|
char **source;
|
|
FILE *fp;
|
|
char line[256];
|
|
int lines,i;
|
|
|
|
/* open the file, number of characters */
|
|
assert(filename!=NULL);
|
|
if ((fp=fopen(filename,"rt"))==NULL)
|
|
return NULL;
|
|
lines=0;
|
|
while (fgets(line,sizeof(line),fp)!=NULL)
|
|
lines++;
|
|
|
|
/* allocate memory, reload the file */
|
|
if ((source=(char **)malloc((lines+1)*sizeof(char *)))==NULL) {
|
|
fclose(fp);
|
|
return NULL;
|
|
} /* if */
|
|
for (i=0; i<=lines; i++) /* initialize, so that source_free() works */
|
|
source[i]=NULL;
|
|
rewind(fp);
|
|
i=0;
|
|
while (fgets(line,sizeof(line),fp)!=NULL) {
|
|
assert(i<lines);
|
|
source[i]=strdup(line);
|
|
if (source[i]==NULL) {
|
|
fclose(fp);
|
|
source_free(source); /* free everything allocated so far */
|
|
return NULL;
|
|
} /* if */
|
|
i++;
|
|
} /* if */
|
|
|
|
fclose(fp);
|
|
return source;
|
|
}
|
|
|
|
static void source_list(AMX_DBG *amxdbg,int startline,int numlines,int curline)
|
|
{
|
|
/* cursource and curline should already have been set */
|
|
int result,lastline;
|
|
ucell address;
|
|
|
|
if (terminal>0) {
|
|
term_csrsave();
|
|
amx_gotoxy(1,watchlines+2);
|
|
numlines=listlines; /* override user setting */
|
|
} /* if */
|
|
|
|
if (startline<0)
|
|
startline=0;
|
|
lastline=startline+numlines;
|
|
curtopline=startline; /* save line that is currently displayed at the top */
|
|
|
|
/* seek to line number from the start (to avoid displaying something
|
|
* beyond the file)
|
|
*/
|
|
for (result=0; cursource[result]!=NULL && result<startline; result++)
|
|
/* nothing */;
|
|
if (cursource[result]!=NULL) {
|
|
assert(result==startline);
|
|
while (cursource[startline]!=NULL && startline<lastline) {
|
|
char c1='\0',c2='\0';
|
|
if (terminal>0)
|
|
amx_clreol();
|
|
if ((int)strlen(cursource[startline])>screencolumns-EATLINE) {
|
|
c1=cursource[startline][screencolumns-EATLINE-1];
|
|
c2=cursource[startline][screencolumns-EATLINE];
|
|
cursource[startline][screencolumns-EATLINE-1] = '\n';
|
|
cursource[startline][screencolumns-EATLINE] = '\0';
|
|
} /* if */
|
|
(void)amx_setattr(-1,-1,startline==curline);
|
|
if (terminal<=0) {
|
|
if (startline==curline)
|
|
amx_printf("%3d%c %s",startline+1,CHR_CURLINE,cursource[startline]);
|
|
else
|
|
amx_printf("%3d %s",startline+1,cursource[startline]);
|
|
} else {
|
|
/* see if there is a breakpoint on the line */
|
|
if (amxdbg!=NULL && dbg_GetLineAddress(amxdbg,startline,curfilename,&address)==AMX_ERR_NONE) {
|
|
BREAKPOINT *bp;
|
|
for (bp=breakpoints.next; bp!=NULL; bp=bp->next) {
|
|
assert(bp->type!=BP_NONE);
|
|
if (bp->type==BP_CODE && bp->addr==address)
|
|
(void)amx_setattr(-1,0x1,1);
|
|
} /* for */
|
|
} /* if */
|
|
amx_printf("%4d",startline+1);
|
|
#if defined VT100
|
|
if (terminal>0)
|
|
amx_printf("\016"); /* SO code to select the graphic set */
|
|
#endif
|
|
amx_printf("%c",chr_vline);
|
|
#if defined VT100
|
|
if (terminal>0)
|
|
amx_printf("\017"); /* SI code to select the standard set */
|
|
#endif
|
|
(void)amx_setattr(-1,0x0,startline==curline);
|
|
amx_printf("%s",cursource[startline]);
|
|
} /* if */
|
|
if (c1!='\0') {
|
|
cursource[startline][screencolumns-EATLINE-1] = c1;
|
|
cursource[startline][screencolumns-EATLINE] = c2;
|
|
} /* if */
|
|
startline++;
|
|
} /* while */
|
|
} /* if */
|
|
|
|
if (terminal>0) {
|
|
(void)amx_setattr(-1,-1,0);
|
|
while (startline<lastline) {
|
|
amx_clreol();
|
|
amx_printf("\n");
|
|
startline++;
|
|
} /* while */
|
|
term_csrrestore();
|
|
} /* if */
|
|
}
|
|
|
|
static int source_lines(void)
|
|
{
|
|
int i;
|
|
|
|
assert(cursource!=NULL);
|
|
for (i=0; cursource[i]!=NULL; i++)
|
|
/* nothing */;
|
|
return i;
|
|
}
|
|
|
|
static int gettopline(int line,int topline)
|
|
{
|
|
if (terminal<=0)
|
|
return topline;
|
|
if (line<curtopline || line>=curtopline+listlines)
|
|
return topline;
|
|
return curtopline;
|
|
}
|
|
|
|
static char *skipwhitespace(const char *str)
|
|
{
|
|
while (*str==' ' || *str=='\t')
|
|
str++;
|
|
return (char*)str;
|
|
}
|
|
|
|
static char *skipvalue(const char *str)
|
|
{
|
|
while (isdigit(*str))
|
|
str++;
|
|
return skipwhitespace(str);
|
|
}
|
|
|
|
static const char *skippath(const char *str)
|
|
{
|
|
const char *p1,*p2;
|
|
|
|
/* DOS/Windows pathnames */
|
|
if ((p1=strrchr(str,'\\'))!=NULL)
|
|
p1++;
|
|
else
|
|
p1=str;
|
|
if (p1==str && p1[1]==':')
|
|
p1=str+2;
|
|
/* Unix pathnames */
|
|
if ((p2=strrchr(str,'/'))!=NULL)
|
|
p2++;
|
|
else
|
|
p2=str;
|
|
return p1>p2 ? p1 : p2;
|
|
}
|
|
|
|
static void scroll_cmdwin(int lines)
|
|
{
|
|
if (terminal>0) {
|
|
int csrx,csry;
|
|
amx_wherexy(&csrx,&csry);
|
|
if (csry==screenlines) {
|
|
term_scroll(watchlines+listlines+3,screenlines-1,lines);
|
|
amx_gotoxy(csrx,csry-1);
|
|
} /* if */
|
|
} /* if */
|
|
}
|
|
|
|
#if !defined NO_REMOTE
|
|
/* For the remote debugging capabilities, the "device" is the apparatus/PC on
|
|
* which the script runs and the "host" is the PC on which the debugger runs.
|
|
* Both the device and the host load the script. The device typically does not
|
|
* load the debugging info. The host would not really need to load the opcodes,
|
|
* but its taks becomes a lot simpler when it has locally an image of what runs
|
|
* on the device.
|
|
*
|
|
* Handshake
|
|
*
|
|
* The first step, after opening the connection, is the handshake. For that,
|
|
* the host must send a packet with the single character "!". Since the device
|
|
* may not be "on-line" immediately and packets may get lost, the host should
|
|
* repeat sending these characters until the device reponds.
|
|
*
|
|
* When the device starts up and loads a script, it should check whether this
|
|
* script contains debugging information (even if it does not load the debugging
|
|
* information). If debugging information is present, it should check for the
|
|
* reception of packets with the character "!". Upon reception, it replies with
|
|
* a return packed with the character "@". The device should have a time-out on
|
|
* the polling loop for receivng the "!" packets, because the script should run
|
|
* normally when no debugger is attached.
|
|
*
|
|
* Running
|
|
*
|
|
* After entering in debug mode, the device will send a packed with the
|
|
* character "@" followed by the new instruction pointer address (CIP) at every
|
|
* "BREAK" instruction. The device then waits for a response from the host (the
|
|
* script is halted). To continue running, the host simply sends a packet with
|
|
* the single character "!".
|
|
*
|
|
* If, instead of allowing the device to continue running the script, you want
|
|
* to halt execution (e.g. because a breakpoint is reached), the first command
|
|
* that the host should send is the string "?R\n", where \n stands for a
|
|
* newline. Upon reception, the device must reply with a more complete state of
|
|
* the abstract machine: the stack address, the frame pointer and the heap top.
|
|
*
|
|
* With the few registers sent over, the state of the abstract machine (on the
|
|
* device) is now known to the host, but it does not know the contents of the
|
|
* data memory on the device: the values of global and local variables. It was
|
|
* estimated too costly to transfer all of this data memory at every "stop".
|
|
* Instead, the host must query for the range of memory that it wants. It does
|
|
* so by sending a packet with the contents:
|
|
*
|
|
* ?Maddress,size\n
|
|
*
|
|
* where "address" and "size" are hexadecimal numbers. The size is in cells;
|
|
* the address does not have to be cell-aligned, but it typically is. The
|
|
* device responds to this command by sending a packet with an "@" character
|
|
* followed by one or more hexadecimal values, separated with commas and
|
|
* terminated with a newline.
|
|
*
|
|
* Other (suggested) commands are:
|
|
*
|
|
* ?M address,size
|
|
* ?R (registers) send the status of FRM, STK and HEA
|
|
*
|
|
* ?G name\n (get) retrieve file
|
|
* ?P size,name\n (put) send over new script (or other file)
|
|
* ?B value\n (baud) set baud rate (restarts the connection)
|
|
* ?L\n (list) retrieve a list of files on the device
|
|
*
|
|
* ?U\n (unhook) close the debugger down
|
|
* ?U*\n unhook the debugger and restart (do this after a transfer of an updated script)
|
|
*
|
|
* When sending over scripts (or perhaps other files), the reply of the
|
|
* ?P command carries the block size that the debugger should use to send
|
|
* the data (like all numbers in the debugger interface, this value is in
|
|
* hexadecimal). For example, if the remote host replies with @100, the debugger
|
|
* should transfer the file in blocks of 256 bytes. The file itself is then
|
|
* sent as binary data (and in blocks). After each block, the debugger must
|
|
* wait for the reply.
|
|
*
|
|
* Before sending the block, the debugger sends a "start code". This code is
|
|
* either an ACK (ASCII 6) or a NAK (ASCII 21). If the debugger sends an ACK,
|
|
* the block that follows is the next sequential block of data for the file. If
|
|
* it sends a NAK, the block that follows is a repeated send of the preceding block.
|
|
* The debugger should resend a block on a checksum mismatch. After sending a
|
|
* block, the remote host replies. The checksum of the block is in the value
|
|
* that follows the @ sign. Due to the way that the checksum is computed, the
|
|
* checksum is never zero. The debugger can now compare the checksum with the
|
|
* one it calculated itself. On a mismatch, the debugger can then send a NAK
|
|
* and resend the block.
|
|
*
|
|
* The checksum is the "Internet checksum", but as an 8-bit variant. This
|
|
* checksum is often called the "one's complement", because it wraps the carry
|
|
* around on overflow.
|
|
*
|
|
*
|
|
* Notes
|
|
*
|
|
* The rationale for the debugger communication protocol is performance, and
|
|
* especially attempting to avoid that the communication link becomes the bottleneck. Therefore, the
|
|
* device and the host send over as little as possible until it is known that
|
|
* the device should be halted. In the case of a serial link, data is sent byte
|
|
* by byte. When the device is halted (on a breakpoint) and
|
|
* waiting for user interaction, performance is no longer as important.
|
|
*
|
|
* There is a special case in the handshaking stage when the device is already
|
|
* running in debug mode (because an earlier session was aborted). In that case,
|
|
* it will respond with "@" and the new instruction pointer (CIP) address.
|
|
*/
|
|
|
|
static int send_rs232(const char *buffer, int len)
|
|
{
|
|
unsigned long size;
|
|
|
|
#if defined __WIN32__
|
|
WriteFile(hCom,buffer,len,&size,NULL);
|
|
#else
|
|
size=write(fdCom,buffer,len);
|
|
#endif
|
|
assert((unsigned long)len==size);
|
|
return size;
|
|
}
|
|
|
|
static int getresponse_rs232(char *buffer, int buffersize, long retries)
|
|
{
|
|
int len=0;
|
|
unsigned long size;
|
|
|
|
do {
|
|
|
|
#if defined __WIN32__
|
|
ReadFile(hCom,buffer+len,buffersize-len,&size,NULL);
|
|
#else
|
|
size=read(fdCom,buffer+len,buffersize-len);
|
|
#endif
|
|
len+=size;
|
|
|
|
/* throw away dummy input characters */
|
|
while (buffer[0]!='@' && len>0) {
|
|
int idx;
|
|
for (idx=0; idx<len && buffer[idx]!='@'; idx++)
|
|
/* nothing */;
|
|
memmove(buffer,buffer+idx,len-idx);
|
|
len-=idx;
|
|
} /* while */
|
|
|
|
if (len<buffersize)
|
|
buffer[len]='\0'; /* force zero termination */
|
|
|
|
if (size==0) {
|
|
#if defined __WIN32__
|
|
Sleep(50);
|
|
#else
|
|
usleep(50*1000);
|
|
#endif
|
|
retries--;
|
|
} /* if */
|
|
|
|
} while ((len==0 || strchr(buffer,'\n')==NULL) && retries>=0);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int settimestamp_rs232(unsigned long sec1970)
|
|
{
|
|
char str[30];
|
|
|
|
#if defined __WIN32__
|
|
assert(hCom!=INVALID_HANDLE_VALUE);
|
|
#else
|
|
assert(fdCom>=0);
|
|
#endif
|
|
sprintf(str,"?T%lx\n",sec1970);
|
|
send_rs232(str,strlen(str));
|
|
getresponse_rs232(str,sizeof str,10);
|
|
return strlen(str)>0 && atoi(str+1)>0;
|
|
}
|
|
|
|
static int remote_rs232(int port,int baud)
|
|
{
|
|
#if defined __WIN32__
|
|
DCB dcb;
|
|
COMMTIMEOUTS commtimeouts;
|
|
#endif
|
|
unsigned long size,cip;
|
|
char buffer[40];
|
|
|
|
/* optionally issue a "close-down" request for the remote host */
|
|
#if defined __WIN32__
|
|
if (baud==0 && hCom!=INVALID_HANDLE_VALUE) {
|
|
#else
|
|
if (baud==0 && fdCom>=0) {
|
|
#endif
|
|
sprintf(buffer,"?U\n");
|
|
#if defined __WIN32__
|
|
WriteFile(hCom,buffer,strlen(buffer),&size,NULL);
|
|
#else
|
|
write(fdCom,buffer,strlen(buffer));
|
|
#endif
|
|
/* do not wait for a reply */
|
|
} /* if */
|
|
|
|
/* set up the connection */
|
|
#if defined __WIN32__
|
|
if (hCom!=INVALID_HANDLE_VALUE)
|
|
CloseHandle(hCom);
|
|
if (baud==0)
|
|
return 0;
|
|
sprintf(buffer,"com%d:",port);
|
|
hCom=CreateFile(buffer,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (hCom==INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
GetCommState(hCom,&dcb);
|
|
dcb.BaudRate=baud;
|
|
dcb.ByteSize=8;
|
|
dcb.StopBits=ONESTOPBIT;
|
|
dcb.Parity=NOPARITY;
|
|
dcb.fBinary=TRUE;
|
|
dcb.fDtrControl=DTR_CONTROL_DISABLE;
|
|
dcb.fOutX=FALSE;
|
|
dcb.fInX=FALSE;
|
|
dcb.fNull=FALSE;
|
|
dcb.fRtsControl=RTS_CONTROL_DISABLE;
|
|
SetCommState(hCom,&dcb);
|
|
SetCommMask(hCom,EV_RXCHAR|EV_TXEMPTY);
|
|
commtimeouts.ReadIntervalTimeout =0x7fffffff;
|
|
commtimeouts.ReadTotalTimeoutMultiplier =0;
|
|
commtimeouts.ReadTotalTimeoutConstant =1;
|
|
commtimeouts.WriteTotalTimeoutMultiplier=0;
|
|
commtimeouts.WriteTotalTimeoutConstant =0;
|
|
SetCommTimeouts(hCom,&commtimeouts);
|
|
#else
|
|
if (fdCom>=0) {
|
|
tcflush(fdCom,TCOFLUSH);
|
|
tcflush(fdCom,TCIFLUSH);
|
|
tcsetattr(fdCom,TCSANOW,&oldtio);
|
|
close(fdCom);
|
|
} /* if */
|
|
if (baud==0)
|
|
return 0;
|
|
sprintf(buffer,"/dev/ttyS%d",port);
|
|
fdCom = open(buffer,O_RDWR|O_NOCTTY|O_NONBLOCK);
|
|
if (fdCom<0)
|
|
return 0;
|
|
/* clear input & output buffers, then switch to "blocking mode" */
|
|
tcflush(fdCom,TCOFLUSH);
|
|
tcflush(fdCom,TCIFLUSH);
|
|
fcntl(fdCom,F_SETFL,fcntl(fdCom,F_GETFL) & ~O_NONBLOCK);
|
|
|
|
tcgetattr(fdCom,&oldtio); /* save current port settings */
|
|
bzero(&newtio,sizeof newtio);
|
|
newtio.c_cflag=CS8 | CLOCAL | CREAD;
|
|
|
|
switch (baud) {
|
|
case 1152000: newtio.c_cflag |= B1152000; break;
|
|
case 576000: newtio.c_cflag |= B576000; break;
|
|
case 230400: newtio.c_cflag |= B230400; break;
|
|
case 115200: newtio.c_cflag |= B115200; break;
|
|
case 57600: newtio.c_cflag |= B57600; break;
|
|
case 38400: newtio.c_cflag |= B38400; break;
|
|
case 19200: newtio.c_cflag |= B19200; break;
|
|
case 9600: newtio.c_cflag |= B9600; break;
|
|
default: return 0;
|
|
} /* switch */
|
|
newtio.c_iflag=IGNPAR | IGNBRK | IXON | IXOFF;
|
|
newtio.c_oflag=0;
|
|
|
|
/* set input mode (non-canonical, no echo,...) */
|
|
newtio.c_lflag=0;
|
|
|
|
cfmakeraw(&newtio);
|
|
newtio.c_cc[VTIME]=1; /* inter-character timer used */
|
|
newtio.c_cc[VMIN] =0; /* blocking read until 0 chars received */
|
|
tcflush(fdCom,TCIFLUSH);
|
|
tcsetattr(fdCom,TCSANOW,&newtio);
|
|
#endif
|
|
|
|
/* handshake, send token and wait for a reply */
|
|
do {
|
|
#if defined __WIN32__
|
|
WriteFile(hCom,"!",1,&size,NULL);
|
|
Sleep(100);
|
|
do {
|
|
ReadFile(hCom,buffer,1,&size,NULL);
|
|
} while (size>0 && buffer[0]!='@');
|
|
Sleep(100);
|
|
/* read remaining buffer (if any) */
|
|
ReadFile(hCom,buffer+1,sizeof buffer - 1,&size,NULL);
|
|
size++; /* add size of the handshake character */
|
|
#else
|
|
write(fdCom,"!",1);
|
|
usleep(100*1000);
|
|
do {
|
|
size=read(fdCom,buffer,1);
|
|
} while (size>0 && buffer[0]!='@');
|
|
usleep(100*1000);
|
|
/* read remaining buffer (if any) */
|
|
size=read(fdCom,buffer+1,sizeof buffer - 1);
|
|
size++; /* add size of the handshake character */
|
|
#endif
|
|
} while (size==0 || buffer[0]!='@');
|
|
if (sscanf(buffer,"@%lx",&cip)>0) {
|
|
/* if the script is already running, give a "go" command */
|
|
#if defined __WIN32__
|
|
WriteFile(hCom,"!",1,&size,NULL);
|
|
#else
|
|
write(fdCom,"!",1);
|
|
#endif
|
|
} else if (size>1 && size<sizeof remote_pendingbuf) {
|
|
remote_pendingsize=size-1;
|
|
memcpy(remote_pendingbuf,buffer+1,remote_pendingsize);
|
|
/* give a "sync time" command, so that the device has the same time as the computer */
|
|
settimestamp_rs232(time(NULL));
|
|
} /* if */
|
|
|
|
remote=REMOTE_RS232;
|
|
return 1;
|
|
}
|
|
|
|
static int remote_wait_rs232(AMX *amx)
|
|
{
|
|
char buffer[50],*ptr;
|
|
unsigned long size;
|
|
long cip;
|
|
int offs,state;
|
|
enum { SCAN, START, FINISH };
|
|
|
|
for ( ;; ) {
|
|
offs=0;
|
|
size=0;
|
|
state=SCAN;
|
|
while (state!=FINISH) {
|
|
offs+=size;
|
|
if (offs>=sizeof buffer - 1) {
|
|
amx_printf("%s",buffer);
|
|
offs=0;
|
|
state=SCAN;
|
|
} /* if */
|
|
/* read a buffer, see if we can find the start condition */
|
|
do {
|
|
if (remote_pendingsize>0) {
|
|
assert(remote_pendingsize<sizeof buffer);
|
|
size=remote_pendingsize;
|
|
memcpy(buffer,remote_pendingbuf,remote_pendingsize);
|
|
remote_pendingsize=0;
|
|
} else {
|
|
#if defined __WIN32__
|
|
ReadFile(hCom,buffer+offs,sizeof buffer - offs - 1,&size,NULL);
|
|
#else
|
|
size=read(fdCom,buffer+offs,sizeof buffer - offs - 1);
|
|
#endif
|
|
} /* if */
|
|
} while (size==0 && remote_pendingsize==0);
|
|
assert(size+offs<sizeof buffer);
|
|
buffer[size+offs]='\0'; /* force zero-termination */
|
|
if (state==SCAN) {
|
|
for (ptr=buffer; (unsigned long)(ptr-buffer)<size && *ptr!='@'; ptr++)
|
|
/* nothing */;
|
|
if ((unsigned long)(ptr-buffer)>=size) {
|
|
amx_printf("%s",buffer);
|
|
} else {
|
|
memmove(buffer,ptr,offs);
|
|
state=START;
|
|
} /* if */
|
|
} /* if */
|
|
if (state==START) {
|
|
for (ptr=buffer; (unsigned long)(ptr-buffer)<(size+offs) && *ptr!='\n'; ptr++)
|
|
/* nothing */;
|
|
if (*ptr=='\n') {
|
|
state=FINISH;
|
|
if (strlen(++ptr)>0)
|
|
amx_printf("%s",ptr);
|
|
} /* if */
|
|
} /* if */
|
|
} /* while */
|
|
amx_fflush();
|
|
/* we found a line starting with '@' and ending '\n'; now check the validity
|
|
* of the line
|
|
*/
|
|
if (sscanf(buffer,"@%lx",&cip)==1) {
|
|
amx->cip=(cell)cip;
|
|
return 1;
|
|
} else {
|
|
/* unknown buffer format; print and continue */
|
|
amx_printf("%s",buffer);
|
|
} /* if */
|
|
} /* for */
|
|
}
|
|
|
|
static void remote_resume_rs232(void)
|
|
{
|
|
#if defined __WIN32__
|
|
unsigned long size;
|
|
WriteFile(hCom,"!",1,&size,NULL);
|
|
#else
|
|
write(fdCom,"!",1);
|
|
#endif
|
|
}
|
|
|
|
static void remote_sync_rs232(AMX *amx)
|
|
{
|
|
char buffer[128];
|
|
long frm,stk,hea;
|
|
#if defined __WIN32__
|
|
unsigned long size;
|
|
#endif
|
|
|
|
for ( ;; ) {
|
|
#if defined __WIN32__
|
|
WriteFile(hCom,"?R\n",3,&size,NULL);
|
|
#else
|
|
write(fdCom,"?R\n",3);
|
|
#endif
|
|
|
|
buffer[0]='\0';
|
|
getresponse_rs232(buffer,sizeof buffer,10);
|
|
if (sscanf(buffer,"@%lx,%lx,%lx",&frm,&stk,&hea)==3) {
|
|
amx->frm=(cell)frm;
|
|
amx->stk=(cell)stk;
|
|
amx->hea=(cell)hea;
|
|
return;
|
|
} /* if */
|
|
} /* for */
|
|
}
|
|
|
|
static void remote_read_rs232(AMX *amx,cell vaddr,int number)
|
|
{
|
|
char buffer[128];
|
|
char *ptr;
|
|
int len;
|
|
cell val;
|
|
cell *cptr;
|
|
|
|
while (number>0) {
|
|
sprintf(buffer,"?M%lx,%x\n",(long)vaddr,(number>10) ? 10 : number);
|
|
len=strlen(buffer);
|
|
send_rs232(buffer,len);
|
|
getresponse_rs232(buffer,sizeof buffer,100);
|
|
ptr=buffer+1; /* skip '@' */
|
|
while (number>0 && (ptr-buffer)<sizeof buffer && *ptr!='\0') {
|
|
val=strtol(ptr,&ptr,16);
|
|
if (amx_GetAddr(amx,vaddr,&cptr)==AMX_ERR_NONE)
|
|
*cptr=val;
|
|
number--;
|
|
vaddr+=sizeof(cell);
|
|
while ((*ptr!='\0' && *ptr<=' ') || *ptr==',')
|
|
ptr++; /* skip optional comma (and whitespace) */
|
|
} /* while */
|
|
} /* while */
|
|
}
|
|
|
|
static void remote_write_rs232(AMX *amx,cell vaddr,int number)
|
|
{
|
|
char buffer[128];
|
|
int len,num;
|
|
cell *cptr;
|
|
|
|
while (number>0) {
|
|
num=(number>10) ? 10 : number;
|
|
number-=num;
|
|
sprintf(buffer,"?W%lx",(long)vaddr);
|
|
while (num>0) {
|
|
if (amx_GetAddr(amx,vaddr,&cptr)!=AMX_ERR_NONE)
|
|
return;
|
|
strcat(buffer,",");
|
|
sprintf(buffer+strlen(buffer),"%x",*cptr);
|
|
num--;
|
|
vaddr+=sizeof(cell);
|
|
} /* while */
|
|
strcat(buffer,"\n");
|
|
len=strlen(buffer);
|
|
send_rs232(buffer,len);
|
|
getresponse_rs232(buffer,sizeof buffer,100);
|
|
if (strtol(buffer+1,NULL,16)==0)
|
|
return;
|
|
} /* while */
|
|
}
|
|
|
|
static int remote_transfer_rs232(const char *filename)
|
|
{
|
|
#define ACK ((unsigned char)6)
|
|
#define NAK ((unsigned char)21)
|
|
unsigned char *buffer;
|
|
char str[128];
|
|
FILE *fp;
|
|
size_t bytes,block;
|
|
unsigned long size,chksum;
|
|
int len,err;
|
|
|
|
#if defined __WIN32__
|
|
if (hCom==INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
#else
|
|
if (fdCom<0)
|
|
return 0;
|
|
#endif
|
|
|
|
if ((fp=fopen(filename,"rb"))==NULL)
|
|
return 0;
|
|
/* determine the file size */
|
|
fseek(fp,0,SEEK_END);
|
|
size=ftell(fp);
|
|
fseek(fp,0,SEEK_SET);
|
|
|
|
/* set up */
|
|
sprintf(str,"?P %lx,%s\n",size,skippath(filename));
|
|
len=strlen(str);
|
|
send_rs232(str,len);
|
|
getresponse_rs232(str,sizeof str,100);
|
|
if (sscanf(str,"@%x",&block)!=1)
|
|
block=0;
|
|
/* allocate 1 byte more, for the ACK/NAK prefix */
|
|
if (block==0 || (buffer=malloc((block+1)*sizeof(char)))==NULL) {
|
|
fclose(fp);
|
|
return 0;
|
|
} /* if */
|
|
|
|
/* file transfer acknowledged, transfer data per block */
|
|
amx_printf("Transferring ");
|
|
amx_fflush();
|
|
while (size>0 && (bytes=fread(buffer+1,1,block,fp))!=0) {
|
|
buffer[0]=ACK;
|
|
/* calculate the checksum */
|
|
chksum=1;
|
|
for (len=1; len<=(int)bytes; len++)
|
|
chksum+=buffer[len];
|
|
while (chksum>0xff)
|
|
chksum=(chksum&0xff)+(chksum>>8);
|
|
do {
|
|
/* send block */
|
|
send_rs232((char*)buffer,bytes+1); /* also send the ACK/NAK prefix */
|
|
getresponse_rs232(str,sizeof str,100);
|
|
err=(str[0]!='\0') ? strtol(str+1,NULL,16) : 0;
|
|
assert(err>=0 && err<=255);
|
|
if (err==0) {
|
|
free(buffer);
|
|
fclose(fp);
|
|
return 0;
|
|
} /* if */
|
|
buffer[0]=NAK; /* preset for failure (if err!=chksum => failure) */
|
|
} while (err!=(int)chksum);
|
|
size-=block;
|
|
if (size<block)
|
|
block=size;
|
|
} /* while */
|
|
buffer[0]=ACK;
|
|
send_rs232(buffer,1); /* ACK the last block */
|
|
|
|
free(buffer);
|
|
fclose(fp);
|
|
|
|
/* reboot the device */
|
|
strcpy(str,"?U*\n");
|
|
len=strlen(str);
|
|
send_rs232(str,len);
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static void namelist_init(NAMELIST *root)
|
|
{
|
|
NAMELIST *next;
|
|
|
|
while (root->next!=NULL) {
|
|
next=root->next;
|
|
root->next=next->next; /* unlink */
|
|
free(next->name); /* then free */
|
|
free(next);
|
|
} /* while */
|
|
}
|
|
|
|
static NAMELIST *namelist_find(NAMELIST *root,int number)
|
|
{
|
|
NAMELIST *name;
|
|
|
|
for (name=root->next; name!=NULL && name->number!=number; name=name->next)
|
|
/* nothing */;
|
|
return name;
|
|
}
|
|
|
|
static int namelist_delete(NAMELIST *root,NAMELIST *item)
|
|
{
|
|
NAMELIST *cur;
|
|
|
|
/* find the item */
|
|
assert(root!=NULL);
|
|
cur=root;
|
|
while (cur->next!=NULL && cur->next!=item)
|
|
cur=cur->next;
|
|
assert(cur!=NULL);
|
|
if (cur->next==NULL)
|
|
return 0; /* item not found */
|
|
|
|
cur->next=item->next; /* unlink */
|
|
free(item->name); /* then free */
|
|
free(item);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static NAMELIST *namelist_add(NAMELIST *root,char *name,int number)
|
|
{
|
|
NAMELIST *cur, *pred;
|
|
|
|
/* allocate a structure */
|
|
cur=malloc(sizeof(NAMELIST));
|
|
if (cur==NULL)
|
|
return NULL;
|
|
cur->name=strdup(name);
|
|
cur->number=number;
|
|
if (cur->name==NULL) {
|
|
free(cur);
|
|
return NULL;
|
|
} /* if */
|
|
|
|
/* link it in the list */
|
|
assert(root!=NULL);
|
|
for (pred=root; pred->next!=NULL && pred->next->number<number; pred=pred->next)
|
|
/* nothing */;
|
|
cur->next=pred->next;
|
|
pred->next=cur;
|
|
|
|
return cur;
|
|
}
|
|
|
|
static int namelist_count(NAMELIST *root)
|
|
{
|
|
int count = 0;
|
|
NAMELIST *name;
|
|
|
|
for (name=root->next; name!=NULL; name=name->next)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
static int get_symbolvalue(AMX *amx,AMX_DBG_SYMBOL *sym,int index,cell *value)
|
|
{
|
|
cell *vptr;
|
|
cell base=sym->address;
|
|
if (sym->vclass & DISP_MASK)
|
|
base+=amx->frm; /* addresses of local vars are relative to the frame */
|
|
if (sym->ident==iREFERENCE || sym->ident==iREFARRAY) { /* a reference */
|
|
amx_GetAddr(amx,base,&vptr);
|
|
base=*vptr;
|
|
} /* if */
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_read_rs232(amx,(cell)(base+index*sizeof(cell)),1);
|
|
#endif
|
|
if (amx_GetAddr(amx,(cell)(base+index*sizeof(cell)),&vptr)!=AMX_ERR_NONE)
|
|
return 0;
|
|
*value=*vptr;
|
|
return 1;
|
|
}
|
|
|
|
static int set_symbolvalue(AMX *amx,const AMX_DBG_SYMBOL *sym,int index,cell value)
|
|
{
|
|
cell *vptr;
|
|
cell base=sym->address;
|
|
if (sym->vclass & DISP_MASK)
|
|
base+=amx->frm; /* addresses of local vars are relative to the frame */
|
|
if (sym->ident==iREFERENCE || sym->ident==iREFARRAY) { /* a reference */
|
|
amx_GetAddr(amx,base,&vptr);
|
|
base=*vptr;
|
|
} /* if */
|
|
if (amx_GetAddr(amx,(cell)(base+index*sizeof(cell)),&vptr)!=AMX_ERR_NONE)
|
|
return 0;
|
|
*vptr=value;
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_write_rs232(amx,(cell)(base+index*sizeof(cell)),1);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static char *get_string(AMX *amx,AMX_DBG_SYMBOL *sym,int maxlength)
|
|
{
|
|
static char string[MAXLINELENGTH];
|
|
char *ptr;
|
|
cell *addr;
|
|
cell base;
|
|
int length,num;
|
|
|
|
assert(sym->ident==iARRAY || sym->ident==iREFARRAY);
|
|
assert(sym->dim==1);
|
|
assert(maxlength<MAXLINELENGTH);
|
|
string[0]='\0';
|
|
|
|
/* get the starting address and the length of the string */
|
|
base=sym->address;
|
|
if (sym->vclass)
|
|
base+=amx->frm; /* addresses of local vars are relative to the frame */
|
|
if (sym->ident==iREFARRAY) { /* a reference */
|
|
amx_GetAddr(amx,base,&addr);
|
|
base=*addr;
|
|
} /* if */
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_read_rs232(amx,base,MAXLINELENGTH);
|
|
#endif
|
|
if (amx_GetAddr(amx,base,&addr)==AMX_ERR_NONE) {
|
|
amx_StrLen(addr,&length);
|
|
|
|
/* allocate a temporary buffer */
|
|
ptr=malloc(length+1);
|
|
if (ptr!=NULL) {
|
|
amx_GetString(ptr,addr,0,maxlength);
|
|
num=length;
|
|
if (num>=maxlength) {
|
|
num=maxlength-1;
|
|
if (num>3)
|
|
num-=3; /* make space for the ... terminator */
|
|
} /* if */
|
|
assert(num>=0);
|
|
strncpy(string,ptr,num);
|
|
string[num]='\0';
|
|
if (num<length && num==maxlength-3)
|
|
strcat(string,"...");
|
|
free(ptr);
|
|
} /* if */
|
|
} else {
|
|
strcpy(string,"?");
|
|
} /* if */
|
|
return string;
|
|
}
|
|
|
|
static void printvalue(long value, int disptype)
|
|
{
|
|
if (disptype==DISP_FLOAT) {
|
|
amx_printf("%f", amx_ctof(value));
|
|
} else if (disptype==DISP_FIXED) {
|
|
#define MULTIPLIER 1000
|
|
long ipart=value/MULTIPLIER;
|
|
value-=MULTIPLIER*ipart;
|
|
if (value<0)
|
|
value=-value;
|
|
amx_printf("%ld.%03ld", ipart, value);
|
|
} else if (disptype==DISP_HEX) {
|
|
amx_printf("%lx", value);
|
|
} else if (disptype==DISP_BOOL) {
|
|
switch (value) {
|
|
case 0:
|
|
amx_printf("false");
|
|
break;
|
|
case 1:
|
|
amx_printf("true");
|
|
break;
|
|
default:
|
|
amx_printf("%ld (true)",value);
|
|
break;
|
|
} /* switch */
|
|
} else {
|
|
amx_printf("%ld", value);
|
|
} /* if */
|
|
}
|
|
|
|
static void display_variable(AMX *amx,AMX_DBG *amxdbg,AMX_DBG_SYMBOL *sym,int index[3],int idxlevel)
|
|
{
|
|
const AMX_DBG_SYMDIM *symdim;
|
|
cell value;
|
|
|
|
assert(index!=NULL);
|
|
/* set default display type for the symbol (if none was set) */
|
|
if ((sym->vclass & ~DISP_MASK)==0) {
|
|
const char *tagname;
|
|
if (dbg_GetTagName(amxdbg,sym->tag,&tagname)==AMX_ERR_NONE) {
|
|
if (stricmp(tagname,"bool")==0)
|
|
sym->vclass |= DISP_BOOL;
|
|
else if (stricmp(tagname,"fixed")==0)
|
|
sym->vclass |= DISP_FIXED;
|
|
else if (stricmp(tagname,"float")==0)
|
|
sym->vclass |= DISP_FLOAT;
|
|
} /* if */
|
|
if ((sym->vclass & ~DISP_MASK)==0 && (sym->ident==iARRAY || sym->ident==iREFARRAY) && sym->dim==1) {
|
|
/* untagged array with a single dimension, walk through all elements
|
|
* and check whether this could be a string
|
|
*/
|
|
unsigned char *ptr=(unsigned char*)get_string(amx,sym,MAXLINELENGTH-1);
|
|
int i;
|
|
for (i=0; i<MAXLINELENGTH-1 && ptr[i]!='\0'; i++) {
|
|
if ((ptr[i]<' ' && ptr[i]!='\n' && ptr[i]!='\r' && ptr[i]!='\t')
|
|
|| ptr[i]>=128)
|
|
break; /* non-ASCII character */
|
|
if (i==0 && !isalpha(ptr[i]))
|
|
break; /* want a letter at the start */
|
|
} /* for */
|
|
if (i>0 && i<MAXLINELENGTH-1 && ptr[i]=='\0')
|
|
sym->vclass |= DISP_STRING;
|
|
} /* if */
|
|
} /* if */
|
|
|
|
if ((sym->ident==iARRAY || sym->ident==iREFARRAY)) {
|
|
int dim;
|
|
dbg_GetArrayDim(amxdbg, sym, &symdim);
|
|
/* check whether any of the indices are out of range */
|
|
for (dim=0; dim<idxlevel; dim++)
|
|
if (symdim[dim].size>0 && (ucell)index[dim]>=symdim[dim].size)
|
|
break;
|
|
if (dim<idxlevel) {
|
|
amx_printf("(index out of range)");
|
|
return;
|
|
} /* if */
|
|
} /* if */
|
|
|
|
if ((sym->ident==iARRAY || sym->ident==iREFARRAY) && idxlevel==0) {
|
|
if ((sym->vclass & ~DISP_MASK)==DISP_STRING) {
|
|
amx_printf("\"%s\"", get_string(amx,sym,40));
|
|
} else if (sym->dim==1) {
|
|
ucell len,i;
|
|
len=symdim[0].size;
|
|
if (len>5)
|
|
len=5;
|
|
else if (len==0)
|
|
len=1; /* unknown array length, assume at least 1 element */
|
|
amx_printf("{");
|
|
for (i=0; i<len; i++) {
|
|
if (i>0)
|
|
amx_printf(",");
|
|
if (get_symbolvalue(amx,sym,(int)i,&value))
|
|
printvalue(value,(sym->vclass & ~DISP_MASK));
|
|
else
|
|
amx_printf("?");
|
|
} /* for */
|
|
if (len<symdim[0].size || symdim[0].size==0)
|
|
amx_printf(",...");
|
|
amx_printf("}");
|
|
} else {
|
|
amx_printf("(multi-dimensional array)");
|
|
} /* if */
|
|
} else if (sym->ident!=iARRAY && sym->ident!=iREFARRAY && idxlevel>0) {
|
|
/* index used on a non-array */
|
|
amx_printf("(invalid index, not an array)");
|
|
} else {
|
|
/* simple variable, or indexed array element */
|
|
int base=0;
|
|
int dim;
|
|
assert(idxlevel>0 || index[0]==0); /* index should be zero if non-array */
|
|
for (dim=0; dim<idxlevel-1; dim++) {
|
|
base+=index[dim];
|
|
if (!get_symbolvalue(amx,sym,base,&value))
|
|
break;
|
|
base+=value/sizeof(cell);
|
|
} /* while */
|
|
if (get_symbolvalue(amx,sym,base+index[dim],&value) && sym->dim==idxlevel)
|
|
printvalue(value,(sym->vclass & ~DISP_MASK));
|
|
else if (sym->dim!=idxlevel)
|
|
amx_printf("(invalid number of dimensions)");
|
|
else
|
|
amx_printf("?");
|
|
} /* if */
|
|
}
|
|
|
|
static void watch_init(void)
|
|
{
|
|
namelist_init(&watches);
|
|
}
|
|
|
|
static void watch_list(AMX *amx,AMX_DBG *amxdbg)
|
|
{
|
|
int num,dim,idx[MAX_DIMS];
|
|
char *indexptr;
|
|
char name[sNAMEMAX+20]; /* extra space for an array index */
|
|
NAMELIST *watch;
|
|
const AMX_DBG_SYMBOL *sym;
|
|
|
|
if (terminal>0) {
|
|
term_csrsave();
|
|
amx_gotoxy(1,1);
|
|
printf("\n"); /* clear "tab state" if the terminal driver forgot */
|
|
amx_gotoxy(1,1); /* and set the cursor position again */
|
|
} /* if */
|
|
|
|
num=0;
|
|
for (watch=watches.next; watch!=NULL; watch=watch->next) {
|
|
assert(watch->name!=NULL && strlen(watch->name)>0);
|
|
strcpy(name,watch->name);
|
|
indexptr=strchr(name,'[');
|
|
dim=0;
|
|
memset(idx,0,sizeof idx);
|
|
while (indexptr!=NULL && dim<MAX_DIMS) {
|
|
*indexptr++='\0';
|
|
idx[dim++]=atoi(indexptr);
|
|
indexptr=strchr(indexptr,'[');
|
|
} /* if */
|
|
amx_printf("%d %-12s ",num++,watch->name);
|
|
/* find the symbol with the given range with the smallest scope */
|
|
if (dbg_GetVariable(amxdbg,watch->name,amx->cip,&sym)==AMX_ERR_NONE)
|
|
display_variable(amx,amxdbg,(AMX_DBG_SYMBOL *)sym,idx,dim);
|
|
else
|
|
amx_printf("(unknown symbol)");
|
|
if (terminal>0)
|
|
amx_clreol();
|
|
amx_printf("\n");
|
|
} /* for */
|
|
|
|
if (terminal>0) {
|
|
if (num==0)
|
|
amx_printf("(no watches)");
|
|
for ( ; num<watchlines; num++) {
|
|
amx_clreol();
|
|
amx_printf("\n");
|
|
} /* for */
|
|
term_csrrestore();
|
|
} else {
|
|
if (num>0)
|
|
draw_hline(0);
|
|
} /* if */
|
|
}
|
|
|
|
static int watch_set(int number, char *name)
|
|
{
|
|
NAMELIST *cur;
|
|
|
|
/* find a free number */
|
|
if (number<0) {
|
|
int changed;
|
|
number=0;
|
|
do {
|
|
changed=0;
|
|
for (cur=watches.next; cur!=NULL; cur=cur->next) {
|
|
if (cur->number==number) {
|
|
number++;
|
|
changed=1;
|
|
} /* if */
|
|
} /* for */
|
|
} while (changed);
|
|
} /* if */
|
|
|
|
/* add the watch */
|
|
return namelist_add(&watches,name,number)!=NULL;
|
|
}
|
|
|
|
static int watch_clear(int number)
|
|
{
|
|
NAMELIST *name=namelist_find(&watches,number);
|
|
if (name!=NULL) {
|
|
namelist_delete(&watches,name);
|
|
return 1;
|
|
} /* if */
|
|
return 0;
|
|
}
|
|
|
|
static int watch_count(void)
|
|
{
|
|
return namelist_count(&watches);
|
|
}
|
|
|
|
static void break_init(void)
|
|
{
|
|
BREAKPOINT *next;
|
|
|
|
while (breakpoints.next!=NULL) {
|
|
next=breakpoints.next;
|
|
breakpoints.next=next->next; /* unlink */
|
|
free(next); /* then free */
|
|
} /* while */
|
|
}
|
|
|
|
static int break_clear(int number)
|
|
{
|
|
BREAKPOINT *cur;
|
|
|
|
/* find the breakpoint */
|
|
cur=&breakpoints;
|
|
while (cur->next!=NULL && cur->next->number!=number)
|
|
cur=cur->next;
|
|
if (cur->next==NULL)
|
|
return 0; /* breakpoint not found */
|
|
|
|
cur->next=cur->next->next; /* unlink */
|
|
free(cur->next); /* then free */
|
|
return 1;
|
|
}
|
|
|
|
static int break_set(AMX_DBG *amxdbg,const char *symaddr,int flags)
|
|
{
|
|
BREAKPOINT *cur,*newbp, bp;
|
|
int number,changed,err;
|
|
|
|
/* find type */
|
|
memset(&bp,0,sizeof(BREAKPOINT));
|
|
symaddr=(const char*)skipwhitespace(symaddr);
|
|
if (isdigit(*symaddr)) {
|
|
bp.type=BP_CODE;
|
|
if ((flags & 1)!=0)
|
|
bp.number=0; /* for temporary breakpoint, use number 0 */
|
|
err=dbg_GetLineAddress(amxdbg,strtol(symaddr,NULL,10)-1,curfilename,&bp.addr);
|
|
} else {
|
|
bp.type=BP_CODE;
|
|
err=dbg_GetFunctionAddress(amxdbg,symaddr,curfilename,&bp.addr);
|
|
dbg_LookupFunction(amxdbg, bp.addr, &bp.name);
|
|
} /* if */
|
|
if (err!=AMX_ERR_NONE)
|
|
return -1;
|
|
|
|
/* walk through the list, see if the breakpoint already exists */
|
|
for (cur=breakpoints.next; cur!=NULL; cur=cur->next) {
|
|
if (cur->addr==bp.addr) {
|
|
if (flags & 2)
|
|
break_clear(cur->number); /* toggle the breakpoint */
|
|
return AMX_ERR_NONE;
|
|
} /* if */
|
|
} /* for */
|
|
|
|
/* find an unused breakpoint number */
|
|
number=1;
|
|
do {
|
|
changed=0;
|
|
for (cur=breakpoints.next; cur!=NULL; cur=cur->next) {
|
|
if (cur->number==number) {
|
|
number++;
|
|
changed=1;
|
|
} /* if */
|
|
} /* for */
|
|
} while (changed);
|
|
|
|
/* allocate a new breakpoint, add the entry parsed earlier to the list */
|
|
newbp=malloc(sizeof(BREAKPOINT));
|
|
if (newbp==0)
|
|
return -1;
|
|
memcpy(newbp,&bp,sizeof(BREAKPOINT));
|
|
for (cur=&breakpoints; cur->next!=NULL && cur->next->number<number; cur=cur->next)
|
|
/* nothing */;
|
|
assert(cur!=NULL);
|
|
newbp->next=cur->next;
|
|
cur->next=newbp;
|
|
newbp->number=number;
|
|
|
|
return number;
|
|
}
|
|
|
|
static void break_list(AMX_DBG *amxdbg)
|
|
{
|
|
BREAKPOINT *cur;
|
|
|
|
for (cur=breakpoints.next; cur!=NULL; cur=cur->next) {
|
|
assert(cur->type!=BP_NONE);
|
|
scroll_cmdwin(1);
|
|
amx_printf("%2d ",cur->number);
|
|
if (cur->type==BP_CODE) {
|
|
long line;
|
|
const char *filename;
|
|
dbg_LookupLine(amxdbg,cur->addr,&line);
|
|
amx_printf("line: %ld",line);
|
|
if (cur->name!=NULL) {
|
|
amx_printf("\tfunc: %s",cur->name);
|
|
} else {
|
|
dbg_LookupFile(amxdbg,cur->addr,&filename);
|
|
if (filename!=NULL)
|
|
amx_printf("\tfile: %s",skippath(filename));
|
|
} /* if */
|
|
} /* if */
|
|
amx_printf("\n");
|
|
} /* for */
|
|
}
|
|
|
|
static int break_check(AMX *amx)
|
|
{
|
|
BREAKPOINT *cur;
|
|
ucell cip;
|
|
|
|
/* when the "break" statement comes, the instruction pointer is already
|
|
* incremented; we must adjust for this
|
|
*/
|
|
cip=(ucell)amx->cip-sizeof(cell);
|
|
for (cur=breakpoints.next; cur!=NULL; cur=cur->next) {
|
|
if (cur->type==BP_CODE) {
|
|
if (cur->addr==cip) {
|
|
int number=cur->number;
|
|
if (number==0)
|
|
break_clear(number);
|
|
return number;
|
|
} /* if */
|
|
} /* if */
|
|
} /* for */
|
|
return -1;
|
|
}
|
|
|
|
#if defined READLINE
|
|
/* Read a string, and return a pointer to it. Returns NULL on EOF. */
|
|
char *rl_gets(char *str,int length)
|
|
{
|
|
char *line_read=readline(STR_PROMPT);
|
|
if (line_read==NULL)
|
|
return NULL;
|
|
|
|
/* copy the line (with possible truncation) */
|
|
strncpy(str,line_read,length);
|
|
if ((int)strlen(line_read)>length)
|
|
str[length-1]='\0';
|
|
#if !defined __WIN32__
|
|
/* readline() allocates memory internally, which it asks you to deallocate
|
|
* with free(). This is an acceptable design with a statically linked
|
|
* library, and perhaps even with shared libraries in Unix/Linux, but it
|
|
* is problematic with DLLs under Microsoft Windows. In Microsoft Windows,
|
|
* the standard C library was never part of the OS, so each compiler
|
|
* vendor provided its own. If your DLL allocates memory and your program
|
|
* (that uses the DLL) frees it, chances are that the malloc() and free()
|
|
* implementations assume a different heap lay-out (because they originate
|
|
* from different vendors, or from different versions of the compiler, or
|
|
* even because of different compilation options/flags).
|
|
* Now if malloc() and free() disagree, trouble is never far.
|
|
* To illustruate the problem: I downloaded a readline Win32 port (a DLL),
|
|
* but calling free() on the returned pointer consistently crashes the
|
|
* program. I must have a different compiler than the one used to compile
|
|
* the DLL, or a different version of the compiler, or different compiler
|
|
* options, or a different MSVCRT.DLL.
|
|
* As a temporary solution, I have chosen to "leak" memory (instead of
|
|
* crashing).
|
|
*/
|
|
free(line_read);
|
|
#endif
|
|
|
|
/* If the line has any text in it, save it on the history. */
|
|
if (strlen(str)>0)
|
|
add_history(line_read);
|
|
|
|
return str;
|
|
}
|
|
#else
|
|
char *rl_gets(char *str,int length)
|
|
{
|
|
#if defined __WIN32__
|
|
unsigned long num;
|
|
INPUT_RECORD rec;
|
|
CONSOLE_CURSOR_INFO csrinfo;
|
|
HANDLE stdinput=GetStdHandle(STD_INPUT_HANDLE);
|
|
HANDLE stdoutput=GetStdHandle(STD_OUTPUT_HANDLE);
|
|
#endif
|
|
|
|
amx_printf("%s",STR_PROMPT);
|
|
amx_fflush();
|
|
#if defined __WIN32__
|
|
if (terminal>0) {
|
|
/* first wait for a function key */
|
|
GetConsoleCursorInfo(stdoutput,&csrinfo);
|
|
csrinfo.bVisible=FALSE;
|
|
SetConsoleCursorInfo(stdoutput,&csrinfo);
|
|
term_statusbar(1);
|
|
assert(length>5);
|
|
for ( ;; ) {
|
|
while (PeekConsoleInput(stdinput,&rec,1,&num) && num==0)
|
|
/* nothing */;
|
|
if (num==0)
|
|
break;
|
|
/* analyze the event, keep only the keyboard events */
|
|
switch (rec.EventType) {
|
|
case KEY_EVENT:
|
|
if (!rec.Event.KeyEvent.bKeyDown) {
|
|
ReadConsoleInput(stdinput,&rec,1,&num);
|
|
continue;
|
|
} /* if */
|
|
str[0]='\0';
|
|
switch (rec.Event.KeyEvent.wVirtualKeyCode) {
|
|
case VK_F1:
|
|
strcpy(str,"\033[11~"); /* \e[11~ on VT220, \eOP on VT100 */
|
|
break;
|
|
case VK_F2:
|
|
strcpy(str,"\033[12~"); /* \e[12~ on VT220, \eOQ on VT100 */
|
|
break;
|
|
case VK_F3:
|
|
strcpy(str,"\033[13~"); /* \e[13~ on VT220, \eOR on VT100 */
|
|
break;
|
|
case VK_F4:
|
|
strcpy(str,"\033[14~"); /* \e[14~ on VT220, \eOS on VT100 */
|
|
break;
|
|
case VK_F5:
|
|
strcpy(str,"\033[15~");
|
|
break;
|
|
case VK_F6:
|
|
strcpy(str,"\033[17~");
|
|
break;
|
|
case VK_F7:
|
|
strcpy(str,"\033[18~");
|
|
break;
|
|
case VK_F8:
|
|
strcpy(str,"\033[19~");
|
|
break;
|
|
case VK_F9:
|
|
strcpy(str,"\033[20~");
|
|
break;
|
|
case VK_F10:
|
|
strcpy(str,"\033[21~");
|
|
break;
|
|
case VK_F11:
|
|
strcpy(str,"\033[23~");
|
|
break;
|
|
case VK_F12:
|
|
strcpy(str,"\033[24~");
|
|
break;
|
|
case VK_PRIOR: /* PgUp */
|
|
strcpy(str,"\033[5~");
|
|
break;
|
|
case VK_NEXT: /* PgDn */
|
|
strcpy(str,"\033[6~");
|
|
break;
|
|
#if 0//???
|
|
case VK_UP: /* Arrow up */
|
|
strcpy(str,"\033[A");
|
|
break;
|
|
case VK_DOWN: /* Arrow down */
|
|
strcpy(str,"\033[B");
|
|
break;
|
|
case VK_LEFT: /* Arrow left */
|
|
strcpy(str,"\033[C");
|
|
break;
|
|
case VK_RIGHT:/* Arrow right */
|
|
strcpy(str,"\033[D");
|
|
break;
|
|
#endif
|
|
} /* switch */
|
|
if (str[0]!='\0') {
|
|
ReadConsoleInput(stdinput,&rec,1,&num); /* remove from queue */
|
|
csrinfo.bVisible=TRUE;
|
|
SetConsoleCursorInfo(stdoutput,&csrinfo);
|
|
term_statusbar(0);
|
|
return str;
|
|
} /* if */
|
|
break;
|
|
case MOUSE_EVENT:
|
|
ReadConsoleInput(stdinput,&rec,1,&num);
|
|
if (rec.Event.MouseEvent.dwEventFlags==0) {
|
|
/* mouse click or release */
|
|
int y=rec.Event.MouseEvent.dwMousePosition.Y;
|
|
if (terminal>0) {
|
|
int line=y-watchlines-1;
|
|
if (line>=0 && line<listlines)
|
|
recentline=line+curtopline;
|
|
} /* if */
|
|
} /* if */
|
|
continue;
|
|
default:
|
|
ReadConsoleInput(stdinput,&rec,1,&num);
|
|
continue; /* ignore event, check for a next event */
|
|
} /* switch */
|
|
/* there are explicit "continues", default action is to NOT loop */
|
|
break;
|
|
} /* for */
|
|
csrinfo.bVisible=TRUE;
|
|
SetConsoleCursorInfo(stdoutput,&csrinfo);
|
|
term_statusbar(0);
|
|
} else if (terminal<0) {
|
|
amx_gets(str,length);
|
|
return str;
|
|
} /* if */
|
|
ReadConsole(stdinput,str,length,&num,NULL);
|
|
#else
|
|
amx_gets(str,length);
|
|
#endif
|
|
return str;
|
|
}
|
|
#endif
|
|
|
|
static void listcommands(char *command,AMX_DBG *amxdbg,int curline)
|
|
{
|
|
if (command==NULL)
|
|
command="";
|
|
if (terminal>0) {
|
|
term_csrsave();
|
|
amx_clrscr();
|
|
if (strlen(command)==0 || strcmp(command,"?")==0)
|
|
amx_printf("At the prompt, you can type debug commands. For example, the word \"step\" is a\n"
|
|
"command to execute a single line in the source code. The commands that you will\n"
|
|
"use most frequently may be abbreviated to a single letter: instead of the full\n"
|
|
"word \"step\", you can also type the letter \"s\" followed by the enter key.\n\n"
|
|
"Available commands:\n");
|
|
else
|
|
amx_printf("Options for command \"%s\":\n",command);
|
|
} /* if */
|
|
|
|
if (stricmp(command,"break")==0) {
|
|
amx_printf("\tBREAK\t\tlist all breakpoints\n"
|
|
"\tBREAK n\t\tset a breakpoint at line \"n\"\n"
|
|
"\tBREAK func\tset a breakpoint at function with name \"func\"\n");
|
|
} else if (stricmp(command,"cbreak")==0) {
|
|
amx_printf("\tCBREAK n\tremove breakpoint number \"n\"\n"
|
|
"\tCBREAK *\tremove all breakpoints\n");
|
|
} else if (stricmp(command,"cw")==0 || stricmp(command,"cwatch")==0) {
|
|
amx_printf("\tCWATCH may be abbreviated to CW\n\n"
|
|
"\tCWATCH n\tremove watch number \"n\"\n"
|
|
"\tCWATCH *\tremove all watches\n");
|
|
} else if (stricmp(command,"d")==0 || stricmp(command,"disp")==0) {
|
|
amx_printf("\tDISP may be abbreviated to D\n\n"
|
|
"\tDISP\t\tdisplay all variables that are currently in scope\n"
|
|
"\tDISP var\tdisplay the value of variable \"var\"\n"
|
|
"\tDISP var[i]\tdisplay the value of array element \"var[i]\"\n");
|
|
} else if (stricmp(command,"file")==0) {
|
|
amx_printf("\tFILE\t\tlist all files that this program is composed off\n"
|
|
"\tFILE name\tset the current file to \"name\"\n");
|
|
} else if (stricmp(command,"g")==0 || stricmp(command,"go")==0) {
|
|
amx_printf("\tGO may be abbreviated to G\n\n"
|
|
"\tGO\t\trun until the next breakpoint or program termination\n"
|
|
"\tGO n\t\trun until line number \"n\"\n"
|
|
"\tGO func\t\trun until the current function returns (\"step out\")\n");
|
|
} else if (stricmp(command,"l")==0 || stricmp(command,"list")==0) {
|
|
amx_printf("\tLIST may be abbreviated to L\n\n"
|
|
"\tLIST\t\tdisplay source code lines around the current line\n"
|
|
"\tLIST n\t\tdisplay lines, starting from line \"n\"\n"
|
|
"\tLIST n m\tdisplay \"m\" lines, starting from line \"n\" (not\n"
|
|
"\t\t\tsupported in terminal emulation modes)\n"
|
|
"\tLIST UP\tdisplay preceding lines\n"
|
|
"\tLIST DOWN\tdisplay following lines\n"
|
|
"\tLIST FUNCS\tdisplay all functions\n"
|
|
"\tLIST ON\t\tautomatically list source code lines after each step\n"
|
|
"\tLIST OFF\tturn off automatic list (not supported in terminal\n"
|
|
"\t\t\temulation modes)\n"
|
|
"\tLIST STATES\tdisplay all automatons and the state that these are in\n");
|
|
} else if (stricmp(command,"set")==0) {
|
|
amx_printf("\tSET var=value\t\tset variable \"var\" to the numeric value \"value\"\n"
|
|
"\tSET var[i]=value\tset array item \"var\" to a numeric value\n");
|
|
} else if (stricmp(command,"term")==0) {
|
|
amx_printf("\tTERM OFF\tdisable VT100/Win32 terminal functions\n"
|
|
"\tTERM ON\t\tuse terminal capabilities, default console size\n"
|
|
"\tTERM x y\tset terminal size (x columns, y lines)\n");
|
|
#if !defined NO_REMOTE
|
|
} else if (stricmp(command,"transfer")==0) {
|
|
amx_printf("\tTRANSFER\ttransfer the program to the remote host (for remote\n"
|
|
"\t\t\tdebugging)\n");
|
|
#endif
|
|
} else if (stricmp(command,"type")==0) {
|
|
amx_printf("\tTYPE var STRING\tdisplay \"var\" as string\n"
|
|
"\tTYPE var STD\tset default display format (decimal integer)\n"
|
|
"\tTYPE var HEX\tset hexadecimal integer format\n"
|
|
"\tTYPE var FIXED\tset fixed point format (3 decimals)\n"
|
|
"\tTYPE var FLOAT\tset floating point format\n");
|
|
} else if (stricmp(command,"watch")==0 || stricmp(command,"w")==0) {
|
|
amx_printf("\tWATCH may be abbreviated to W\n\n"
|
|
"\tWATCH var\tset a new watch at variable \"var\"\n"
|
|
"\tWATCH n var\tchange watch \"n\" to variable \"var\"\n");
|
|
} else if (stricmp(command,"n")==0 || stricmp(command,"next")==0
|
|
|| stricmp(command,"quit")==0
|
|
|| stricmp(command,"s")==0 || stricmp(command,"step")==0)
|
|
{
|
|
amx_printf("\tno additional information\n");
|
|
} else {
|
|
amx_printf("\tBREAK\t\tset breakpoint at line number or variable name\n"
|
|
"\tCBREAK\t\tremove breakpoint\n"
|
|
"\tCW(atch)\tremove a \"watchpoint\"\n"
|
|
"\tD(isp)\t\tdisplay the value of a variable, list variables\n"
|
|
"\tFILE\t\tswitch to a file\n"
|
|
"\tG(o)\t\trun program (until breakpoint)\n"
|
|
"\tL(ist)\t\tdisplay source file, automatons and functions\n"
|
|
"\tN(ext)\t\tRun until next line, step over functions\n"
|
|
"\tOUTPUT\t\tShow program output (if supported by terminal)\n"
|
|
"\tQUIT\t\texit debugger, terminate program\n"
|
|
"\tSET\t\tSet a variable to a value\n"
|
|
"\tS(tep)\t\tsingle step, step into functions\n"
|
|
"\tTERM\t\tset terminal type\n"
|
|
#if !defined NO_REMOTE
|
|
"\tTRANSFER\ttransfer program to remote host\n"
|
|
#endif
|
|
"\tTYPE\t\tset the \"display type\" of a symbol\n"
|
|
"\tW(atch)\t\tset a \"watchpoint\" on a variable\n"
|
|
"\n\tUse \"? <command name>\" to view more information on a command\n");
|
|
} /* if */
|
|
amx_fflush();
|
|
if (terminal>0) {
|
|
amx_printf("\nPress a key to continue...");
|
|
amx_fflush();
|
|
amx_getch();
|
|
term_refresh(0);
|
|
term_csrrestore();
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} /* if */
|
|
}
|
|
|
|
static int docommand(AMX *amx,AMX_DBG *amxdbg,int curline)
|
|
{
|
|
static char lastcommand[32] = "";
|
|
char line[MAXLINELENGTH], command[32];
|
|
int result,i;
|
|
char *params;
|
|
|
|
for ( ;; ) {
|
|
if (terminal>0) {
|
|
term_csrsave();
|
|
amx_gotoxy(1,screenlines);
|
|
amx_clreol();
|
|
} /* if */
|
|
rl_gets(line,sizeof(line));
|
|
if (terminal>0)
|
|
term_csrrestore();
|
|
if (line[0]=='\033') {
|
|
assert(terminal==1);
|
|
if (strcmp(line+1,"[11~")==0 || strcmp(line+1,"OP")==0) {
|
|
strcpy(line,"?"); /* F1 == Help */
|
|
} else if (strcmp(line+1,"[13~")==0 || strcmp(line+1,"OR")==0) {
|
|
strcpy(line,"QUIT"); /* F3 == List */
|
|
} else if (strcmp(line+1,"[14~")==0 || strcmp(line+1,"OS")==0) {
|
|
strcpy(line,"Output"); /* F4 == Output */
|
|
} else if (strcmp(line+1,"[15~")==0) {
|
|
strcpy(line,"G"); /* F5 == Go */
|
|
} else if (strcmp(line+1,"[17~")==0) {
|
|
strcpy(line,"D"); /* F6 == Disp */
|
|
} else if (strcmp(line+1,"[19~")==0) {
|
|
strcpy(line,"S"); /* F8 == Step */
|
|
#if 0 //???
|
|
} else if (strcmp(line+1,"[20~")==0) {
|
|
if (recentline<0)
|
|
recentline=curline;
|
|
sprintf(line,"BREAK %d",recentline+1); /* F9 == Breakpoint */
|
|
//??? should toggle if a breakpoint is already set
|
|
#endif
|
|
} else if (strcmp(line+1,"[21~")==0) {
|
|
strcpy(line,"N"); /* F10 == Next */
|
|
} else if (strcmp(line+1,"[5~")==0) {
|
|
strcpy(line,"L UP"); /* PgUp */
|
|
} else if (strcmp(line+1,"[6~")==0) {
|
|
strcpy(line,"L DOWN"); /* PgDn */
|
|
} else {
|
|
continue;
|
|
} /* if */
|
|
} /* if */
|
|
if ((params=strchr(line,'\n'))!=NULL)
|
|
*params='\0'; /* strip newline character */
|
|
if ((params=strchr(line,'\r'))!=NULL)
|
|
*params='\0'; /* strip carriage return character too */
|
|
if (strlen(line)==0) {
|
|
if (terminal==0) {
|
|
int csrx,csry;
|
|
amx_wherexy(&csrx,&csry);
|
|
amx_gotoxy(1,csry-1);
|
|
amx_clreol();
|
|
} /* if */
|
|
strcpy(line,lastcommand);
|
|
} /* if */
|
|
lastcommand[0]='\0';
|
|
|
|
result=sscanf(line,"%8s",command);
|
|
if (result<=0) {
|
|
if (terminal>=0)
|
|
listcommands(NULL,amxdbg,curline);
|
|
continue;
|
|
} /* if */
|
|
params=strchr(line,' ');
|
|
params=(params!=NULL) ? skipwhitespace(params) : "";
|
|
|
|
if (stricmp(command,"?")==0) {
|
|
result=sscanf(line,"%*s %30s",command);
|
|
listcommands(result ? command : NULL,amxdbg,curline);
|
|
} else if (stricmp(command,"quit")==0) {
|
|
if (terminal>0) {
|
|
amx_clrscr();
|
|
term_close();
|
|
} /* if */
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_rs232(0,0);
|
|
#endif
|
|
exit(0);
|
|
} else if (stricmp(command,"g")==0 || stricmp(command,"go")==0) {
|
|
if (isdigit(*params))
|
|
break_set(amxdbg,params,0x1);
|
|
recentline=-1;
|
|
if (stricmp(params,"func")==0)
|
|
return STEPOUT;
|
|
return RUNNING;
|
|
} else if (stricmp(command,"s")==0 || stricmp(command,"step")==0) {
|
|
strcpy(lastcommand,"s");
|
|
recentline=-1;
|
|
return STEPPING;
|
|
} else if (stricmp(command,"n")==0 || stricmp(command,"next")==0) {
|
|
strcpy(lastcommand,"n");
|
|
recentline=-1;
|
|
return STEPOVER;
|
|
} else if (stricmp(command,"l")==0 || stricmp(command,"list")==0) {
|
|
/* first check a few hard cases */
|
|
if (stricmp(params,"funcs")==0) {
|
|
for (i=0; i<amxdbg->hdr->symbols; i++) {
|
|
if (amxdbg->symboltbl[i]->ident == iFUNCTN) {
|
|
const char *filename;
|
|
scroll_cmdwin(1);
|
|
amx_printf("%s",amxdbg->symboltbl[i]->name);
|
|
if (dbg_LookupFile(amxdbg,amxdbg->symboltbl[i]->address,&filename)==AMX_ERR_NONE)
|
|
amx_printf("\t(%s)",skippath(filename));
|
|
//??? also say what automaton this function is part of
|
|
amx_printf("\n");
|
|
} /* if */
|
|
} /* for */
|
|
} else if (stricmp(params,"states")==0) {
|
|
const char *statename;
|
|
cell *cptr;
|
|
if (amxdbg->hdr->automatons==0) {
|
|
scroll_cmdwin(1);
|
|
amx_printf("\t(no states)\n");
|
|
} /* if */
|
|
for (i=0; i<amxdbg->hdr->automatons; i++) {
|
|
scroll_cmdwin(1);
|
|
amx_printf("%s",strlen(amxdbg->automatontbl[i]->name)==0 ? "(anonymous)" : amxdbg->automatontbl[i]->name);
|
|
/* read the variable at the address */
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_read_rs232(amx,amxdbg->automatontbl[i]->address,1);
|
|
#endif
|
|
if (amx_GetAddr(amx,amxdbg->automatontbl[i]->address,&cptr)==AMX_ERR_NONE)
|
|
dbg_GetStateName(amxdbg,(int)*cptr,&statename);
|
|
amx_printf("\t-> %s\n",(statename==NULL) ? "(none)" : statename);
|
|
} /* for */
|
|
} else if (stricmp(params,"on")==0) {
|
|
autolist=listlines;
|
|
watch_list(amx,amxdbg);
|
|
source_list(amxdbg,curline-autolist/2,autolist,curline);
|
|
} else if (stricmp(params,"off")==0) {
|
|
if (terminal<=0)
|
|
autolist=1;
|
|
else
|
|
amx_printf("\tCommand not supported on terminals\n");
|
|
} else {
|
|
int lnum,numlines;
|
|
lnum=curline-(listlines/2-1); /* preset */
|
|
numlines=listlines;
|
|
if (stricmp(params,"up")==0) {
|
|
lnum=curtopline-listlines;
|
|
if (lnum<0)
|
|
lnum=0;
|
|
} else if (stricmp(params,"down")==0) {
|
|
lnum=curtopline+listlines;
|
|
if (lnum>source_lines()-3)
|
|
lnum=source_lines()-3;
|
|
} else {
|
|
sscanf(line,"%*s %d %d",&lnum,&numlines);
|
|
lnum--; /* if user filled in a line number, subtract 1 */
|
|
} /* if */
|
|
#if !defined VT100
|
|
if (terminal>0) {
|
|
int csrx,csry;
|
|
amx_wherexy(&csrx,&csry);
|
|
if (csry>=screenlines)
|
|
term_refresh(0);
|
|
} /* if */
|
|
#endif
|
|
source_list(amxdbg,lnum,numlines,curline);
|
|
} /* if */
|
|
} else if (stricmp(command,"break")==0 || stricmp(command,"tbreak")==0) {
|
|
if (*params=='\0') {
|
|
break_list(amxdbg);
|
|
} else {
|
|
result=break_set(amxdbg,params,stricmp(command,"tbreak")==0);
|
|
if (result<0)
|
|
amx_printf("Invalid breakpoint\n");
|
|
else if (terminal>0)
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} /* if */
|
|
} else if (stricmp(command,"cbreak")==0) {
|
|
if (*params=='*') {
|
|
/* clear all breakpoints */
|
|
break_init();
|
|
} else if (isdigit(*params)) {
|
|
if (!break_clear(atoi(params)))
|
|
amx_printf("\tUnknown breakpoint\n");
|
|
} else {
|
|
amx_printf("\tInvalid command\n");
|
|
} /* if */
|
|
if (terminal>0)
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} else if (stricmp(command,"disp")==0 || stricmp(command,"d")==0) {
|
|
int idx[MAX_DIMS],dim;
|
|
dim=0;
|
|
memset(idx,0,sizeof idx);
|
|
if (*params=='\0') {
|
|
/* display all variables that are in scope */
|
|
for (i=0; i<amxdbg->hdr->symbols; i++) {
|
|
if (amxdbg->symboltbl[i]->ident!=iFUNCTN
|
|
&& amxdbg->symboltbl[i]->codestart<=(ucell)amx->cip
|
|
&& amxdbg->symboltbl[i]->codeend>(ucell)amx->cip)
|
|
{
|
|
scroll_cmdwin(1);
|
|
amx_printf("%s\t%s\t",(amxdbg->symboltbl[i]->vclass & DISP_MASK)>0 ? "loc" : "glb",amxdbg->symboltbl[i]->name);
|
|
display_variable(amx,amxdbg,amxdbg->symboltbl[i],idx,0);
|
|
amx_printf("\n");
|
|
} /* if */
|
|
} /* for */
|
|
} else {
|
|
const AMX_DBG_SYMBOL *sym;
|
|
char *indexptr=strchr(params,'[');
|
|
char *behindname=NULL;
|
|
assert(dim==0);
|
|
while (indexptr!=NULL && dim<MAX_DIMS) {
|
|
if (behindname==NULL)
|
|
behindname=indexptr;
|
|
indexptr++;
|
|
idx[dim++]=atoi(indexptr);
|
|
indexptr=strchr(indexptr,'[');
|
|
} /* if */
|
|
/* find the symbol with the smallest scope */
|
|
if (behindname!=NULL)
|
|
*behindname='\0';
|
|
if (dbg_GetVariable(amxdbg,params,amx->cip,&sym)==AMX_ERR_NONE) {
|
|
scroll_cmdwin(1);
|
|
if (behindname!=NULL)
|
|
*behindname='[';
|
|
amx_printf("%s\t%s\t",(sym->vclass & DISP_MASK)>0 ? "loc" : "glb",params/*sym->name*/);
|
|
display_variable(amx,amxdbg,(AMX_DBG_SYMBOL*)sym,idx,dim);
|
|
amx_printf("\n");
|
|
} else {
|
|
amx_printf("\tSymbol not found, or not a variable\n");
|
|
} /* if */
|
|
} /* if */
|
|
} else if (stricmp(command,"set")==0) {
|
|
char varname[32];
|
|
long index,value;
|
|
const AMX_DBG_SYMBOL *sym;
|
|
if (sscanf(params," %[^[ ][%ld] = %ld",varname,&index,&value)!=3) {
|
|
index=0;
|
|
if (sscanf(params," %[^= ] = %ld",varname,&value)!=2)
|
|
varname[0]='\0';
|
|
} /* if */
|
|
if (varname[0]!='\0') {
|
|
/* find the symbol with the given range with the smallest scope */
|
|
if (dbg_GetVariable(amxdbg,varname,amx->cip,(const AMX_DBG_SYMBOL**)&sym)==AMX_ERR_NONE)
|
|
set_symbolvalue(amx,sym,(int)index,(cell)value);
|
|
} /* if */
|
|
} else if (stricmp(command,"file")==0) {
|
|
if (*params=='\0') {
|
|
/* browse through the file table */
|
|
for (i=0; i<amxdbg->hdr->files; i++) {
|
|
scroll_cmdwin(1);
|
|
amx_printf("%s\n",amxdbg->filetbl[i]->name);
|
|
} /* for */
|
|
} else {
|
|
/* find the file */
|
|
for (i=0; i<amxdbg->hdr->files; i++)
|
|
if (stricmp(params,amxdbg->filetbl[i]->name)==0 || stricmp(params,skippath(amxdbg->filetbl[i]->name))==0)
|
|
break;
|
|
if (i<amxdbg->hdr->files) {
|
|
if (curfilename!=amxdbg->filetbl[i]->name) {
|
|
curfilename=amxdbg->filetbl[i]->name;
|
|
curline=0;
|
|
if (cursource!=NULL)
|
|
source_free(cursource);
|
|
assert(amxdbg->filetbl[i]->name!=NULL);
|
|
cursource=source_load(amxdbg->filetbl[i]->name);
|
|
if (cursource==NULL) {
|
|
amx_printf("\tSource file not found (or insufficient memory)\n");
|
|
continue;
|
|
} /* if */
|
|
} /* if */
|
|
} else {
|
|
amx_printf("\tunknown file\n");
|
|
} /* if */
|
|
} /* if */
|
|
} else if (stricmp(command,"term")==0) {
|
|
int new_term=-1;
|
|
int columns=screencolumns;
|
|
int lines=screenlines+1;
|
|
if (stricmp(params,"off")==0)
|
|
new_term=0;
|
|
else if (stricmp(params,"on")==0)
|
|
new_term=1;
|
|
else if (sscanf(params,"%d %d",&columns,&lines)==2)
|
|
new_term=2;
|
|
if (new_term==-1) {
|
|
amx_printf("\tTerminal support is %s\n",terminal>0 ? "ON" : "OFF");
|
|
} else if (terminal!=new_term) {
|
|
curtopline=0;
|
|
if (terminal>0)
|
|
term_close();
|
|
if (new_term && amx_termctl(0,0)) {
|
|
term_open(columns,lines);
|
|
autolist=listlines;
|
|
} else if (new_term) {
|
|
amx_printf("\tTerminal not supported\n");
|
|
} /* if */
|
|
watch_list(amx,amxdbg);
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} /* if */
|
|
} else if (stricmp(command,"output")==0) {
|
|
if (term_switch(0)) /* switch back to user screen */
|
|
amx_getch(); /* if it worked, wait for a key press */
|
|
term_switch(1); /* switch to debugger screen */
|
|
} else if (stricmp(command,"transfer")==0) {
|
|
#if defined NO_REMOTE
|
|
amx_printf("\tRemote file transfer is not supported.\n");
|
|
#else
|
|
if (remote!=REMOTE_RS232) {
|
|
amx_printf("\tRemote file transfer is not supported.\n");
|
|
} else {
|
|
if (remote_transfer_rs232(amx_filename)) {
|
|
/* restart the debugger */
|
|
longjmp(restart_buf,1);
|
|
} else {
|
|
amx_printf("\tRemote file transfer failed.\n");
|
|
} /* if */
|
|
} /* if */
|
|
#endif
|
|
} else if (stricmp(command,"type")==0) {
|
|
char symname[sNAMEMAX+1],*ptr;
|
|
int len;
|
|
for (ptr=params; *ptr!='\0' && *ptr!=' ' && *ptr!='\t'; ptr++)
|
|
/* nothing */;
|
|
len=(int)(ptr-params);
|
|
if (len==0 || len>sNAMEMAX) {
|
|
amx_printf("\tInvalid (or missing) symbol name\n");
|
|
} else {
|
|
AMX_DBG_SYMBOL *sym;
|
|
strncpy(symname,params,len);
|
|
symname[len]='\0';
|
|
params=skipwhitespace(ptr);
|
|
/* look up the symbol */
|
|
if (dbg_GetVariable(amxdbg,symname,amx->cip,(const AMX_DBG_SYMBOL**)&sym)==AMX_ERR_NONE) {
|
|
assert(sym!=NULL);
|
|
if (stricmp(params,"std")==0) {
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_DEFAULT);
|
|
} else if (stricmp(params,"string")==0) {
|
|
/* check array with single dimension */
|
|
if (!(sym->ident==iARRAY || sym->ident==iREFARRAY) || sym->dim!=1)
|
|
amx_printf("\t\"string\" display type is only valid for arrays with one dimension\n");
|
|
else
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_STRING);
|
|
} else if (stricmp(params,"bin")==0) {
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_BIN);
|
|
} else if (stricmp(params,"hex")==0) {
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_HEX);
|
|
} else if (stricmp(params,"fixed")==0) {
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_FIXED);
|
|
} else if (stricmp(params,"float")==0) {
|
|
sym->vclass = (char)((sym->vclass & DISP_MASK) | DISP_FLOAT);
|
|
} else {
|
|
amx_printf("\tUnknown (or missing) display type\n");
|
|
} /* if */
|
|
watch_list(amx,amxdbg);
|
|
} else {
|
|
amx_printf("\tUnknown symbol \"%s\"\n",symname);
|
|
} /* if */
|
|
} /* if */
|
|
} else if (stricmp(command,"w")==0 || stricmp(command,"watch")==0) {
|
|
/* see whether this is a new watch, or whether one is replaced */
|
|
if (isdigit(*params)) {
|
|
i=atoi(params);
|
|
params=skipvalue(params);
|
|
} else {
|
|
i = -1;
|
|
} /* if */
|
|
if (strlen(params)==0) {
|
|
amx_printf("Missing variable name\n");
|
|
continue;
|
|
} /* if */
|
|
/* is there space for another watch? (only when using terminal support) */
|
|
if (i<0 && terminal>0 && watch_count()>=WATCHLINES) {
|
|
int w=watch_count()+1; /* new number of watches */
|
|
int l=listlines;
|
|
int c=screenlines-w-l-3;
|
|
if (c<=1) {
|
|
/* command window cannot shrink, shrink code listing instead */
|
|
c=2;
|
|
l=screenlines-w-c-3;
|
|
if (l<5) {
|
|
/* code listing cannot shrink either, give a message */
|
|
amx_printf("Too many watches, sorry\n");
|
|
continue;
|
|
} /* if */
|
|
} /* if */
|
|
watchlines=w;
|
|
listlines=l;
|
|
term_refresh(0); /* reset */
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} /* if */
|
|
result=watch_set(i,params);
|
|
if (result>=0)
|
|
watch_list(amx,amxdbg);
|
|
else
|
|
amx_printf("Invalid watch\n");
|
|
} else if (stricmp(command,"cw")==0 || stricmp(command,"cwatch")==0) {
|
|
if (*params=='*') {
|
|
/* clear all watches */
|
|
watch_init();
|
|
} else if (isdigit(*params)) {
|
|
watch_clear(atoi(params));
|
|
} else {
|
|
amx_printf("\tInvalid command syntax, use \"? cw\" for details\n");
|
|
} /* if */
|
|
if (terminal>0 && watchlines>WATCHLINES) {
|
|
int w,l,c;
|
|
w=watch_count(); /* new number of watches */
|
|
if (w<WATCHLINES)
|
|
w=WATCHLINES;
|
|
l=LISTLINES; /* desired number of lines in code listing */
|
|
c=screenlines-w-l-3;
|
|
if (c<=1) {
|
|
/* command window too small, shrink code listing */
|
|
c=2;
|
|
l=screenlines-w-c-3;
|
|
} /* if */
|
|
watchlines=w;
|
|
listlines=l;
|
|
term_refresh(0); /* reset */
|
|
source_list(amxdbg,gettopline(curline,curline-autolist/2),autolist,curline);
|
|
} /* if */
|
|
watch_list(amx,amxdbg);
|
|
} else {
|
|
amx_printf("\tInvalid command \"%s\", use \"?\" to view all commands\n",command);
|
|
} /* if */
|
|
} /* for */
|
|
}
|
|
|
|
void sigabort(int sig)
|
|
{
|
|
/* set "trace mode" */
|
|
runmode=STEPPING;
|
|
signal(sig,sigabort); /* re-install the signal handler */
|
|
}
|
|
|
|
int AMXAPI amx_InternalDebugProc(AMX *amx)
|
|
{
|
|
static cell lastfrm;
|
|
static long lastline;
|
|
AMX_DBG *amxdbg;
|
|
const char *filename;
|
|
long line;
|
|
int breaknr,i,err;
|
|
char *msg;
|
|
|
|
if (amx == NULL) {
|
|
/* special case: re-initialize the abstract machine */
|
|
if (cursource!=NULL) {
|
|
source_free(cursource);
|
|
cursource=NULL;
|
|
} /* if */
|
|
curfilename=NULL;
|
|
break_init(); /* frees all existing breakpoints */
|
|
watch_init();
|
|
if (terminal==0 && amx_termctl(0,0)) {
|
|
curtopline=0;
|
|
term_switch(1); /* switch to the debugger console */
|
|
term_open(screencolumns,screenlines+1);
|
|
autolist=listlines;
|
|
term_switch(0); /* switch back to the program output */
|
|
} /* if */
|
|
lastfrm=0;
|
|
lastline=-1;
|
|
runmode=STEPPING; /* wait at the entry point */
|
|
return AMX_ERR_NONE;
|
|
} /* if */
|
|
|
|
/* when running until the function exit, check the frame address */
|
|
if (runmode==STEPOUT && amx->frm>lastfrm)
|
|
runmode=STEPPING;
|
|
|
|
/* when running, check the breakpoints */
|
|
breaknr=-1;
|
|
if (runmode!=STEPPING && runmode!=STEPOVER) {
|
|
/* check breakpoint address */
|
|
breaknr=break_check(amx);
|
|
if (breaknr<0) {
|
|
/* just print the address at the bottom line, so one can trace where the
|
|
* the program is
|
|
*/
|
|
if (terminal>0) {
|
|
term_csrsave();
|
|
amx_gotoxy(1,screenlines);
|
|
amx_printf("cip: %08lx",(long)amx->cip);
|
|
term_csrrestore();
|
|
} /* if */
|
|
return AMX_ERR_NONE;
|
|
} /* if */
|
|
runmode=STEPPING;
|
|
} /* for */
|
|
|
|
/* get more information from the remote target */
|
|
#if !defined NO_REMOTE
|
|
if (remote!=REMOTE_NONE)
|
|
remote_sync_rs232(amx);
|
|
#endif
|
|
|
|
/* get the debug information header */
|
|
err = amx_GetUserData(amx, AMX_USERTAG('P','D','b','g'), (void**)&amxdbg);
|
|
if (err!=AMX_ERR_NONE)
|
|
return AMX_ERR_DEBUG;
|
|
|
|
/* try to avoid halting on the same line twice */
|
|
dbg_LookupLine(amxdbg,amx->cip,&line);
|
|
if (line==lastline)
|
|
return AMX_ERR_NONE;
|
|
lastline=line;
|
|
|
|
/* check whether we are stepping through a sub-function */
|
|
if (runmode==STEPOVER) {
|
|
assert(lastfrm!=0);
|
|
if (amx->frm<lastfrm)
|
|
return AMX_ERR_NONE;
|
|
} /* if */
|
|
|
|
/* find the file at which we are (for loading the appropriate source file) */
|
|
dbg_LookupFile(amxdbg,amx->cip,&filename);
|
|
|
|
/* check breakpoints */
|
|
msg=NULL;
|
|
if (breaknr==0) {
|
|
msg="STOPPED at line %d\n";
|
|
i=(int)line+1;
|
|
} else if (breaknr>0) {
|
|
msg="STOPPED at breakpoint %d\n";
|
|
i=breaknr;
|
|
} /* if */
|
|
term_switch(1); /* switch to the debugger console */
|
|
if (msg!=NULL)
|
|
amx_printf(msg,i); /* print breakpoint number */
|
|
assert(filename!=NULL);
|
|
if (curfilename!=filename) {
|
|
curfilename=filename;
|
|
if (cursource!=NULL)
|
|
source_free(cursource);
|
|
cursource=source_load(filename);
|
|
if (cursource==NULL) {
|
|
amx_printf("\nCritical error: source file not found (or insufficient memory)\n");
|
|
exit(1);
|
|
} else if (terminal<=0) {
|
|
amx_printf("(%s)\n",curfilename);
|
|
} /* if */
|
|
} /* if */
|
|
#if !defined VT100
|
|
if (terminal>0) {
|
|
int csrx,csry;
|
|
amx_wherexy(&csrx,&csry);
|
|
if (csry>=screenlines)
|
|
term_refresh(0);
|
|
} /* if */
|
|
#endif
|
|
|
|
assert(cursource[(int)line]!=NULL);
|
|
watch_list(amx,amxdbg);
|
|
source_list(amxdbg,gettopline((int)line,(int)line-autolist/2),autolist,(int)line);
|
|
runmode=docommand(amx,amxdbg,(int)line);
|
|
term_switch(0); /* switch back to the program output */
|
|
if (runmode==STEPOVER)
|
|
lastfrm=amx->frm; /* step OVER functions (so save the stack frame) */
|
|
|
|
return AMX_ERR_NONE;
|
|
}
|
|
|
|
#if !defined amx_Init
|
|
|
|
extern int CreateConsoleThread(void);
|
|
|
|
static void *loadprogram(AMX *amx,const char *filename)
|
|
{
|
|
FILE *fp;
|
|
AMX_HEADER hdr;
|
|
void *program;
|
|
|
|
if ((fp = fopen(filename,"rb")) != NULL) {
|
|
fread(&hdr, sizeof hdr, 1, fp);
|
|
amx_Align32((uint32_t *)&hdr.stp);
|
|
amx_Align32((uint32_t *)&hdr.size);
|
|
if ((program = malloc((int)hdr.stp)) != NULL) {
|
|
rewind(fp);
|
|
fread(program, 1, (int)hdr.size, fp);
|
|
fclose(fp);
|
|
memset(amx, 0, sizeof *amx);
|
|
if (amx_Init(amx,program) == AMX_ERR_NONE)
|
|
return program;
|
|
free(program);
|
|
} /* if */
|
|
} /* if */
|
|
return NULL;
|
|
}
|
|
|
|
static int loaddbginfo(AMX *amx,AMX_DBG *amxdbg,const char *filename)
|
|
{
|
|
FILE *fp;
|
|
int err;
|
|
|
|
/* load the debug information from the file */
|
|
if ((fp = fopen(filename,"rb")) == NULL)
|
|
return AMX_ERR_DEBUG;
|
|
err = dbg_LoadInfo(amxdbg,fp);
|
|
fclose(fp);
|
|
if (err != AMX_ERR_NONE)
|
|
return err;
|
|
|
|
/* attach the debug information structure to the abstract machine */
|
|
err = amx_SetUserData(amx, AMX_USERTAG('P','D','b','g'), amxdbg);
|
|
return err;
|
|
}
|
|
|
|
int main(int argc,char *argv[])
|
|
{
|
|
extern AMX_NATIVE_INFO core_Natives[];
|
|
extern AMX_NATIVE_INFO console_Natives[];
|
|
|
|
AMX amx;
|
|
AMX_DBG amxdbg;
|
|
cell ret;
|
|
int err,i;
|
|
void *program=NULL;
|
|
unsigned short flags;
|
|
char *ptr;
|
|
|
|
#if !defined AMX_NODYNALOAD && (defined LINUX || defined __FreeBSD__ || defined __OpenBSD__)
|
|
/* see www.autopackage.org for the BinReloc module */
|
|
if (br_init(NULL)) {
|
|
char *libroot=br_find_exe_dir("");
|
|
setenv("AMXLIB",libroot,0);
|
|
free(libroot);
|
|
} /* if */
|
|
#endif
|
|
|
|
amx_printf("Pawn command line debugger\n\n");
|
|
if (setjmp(restart_buf)) {
|
|
assert(program!=NULL);
|
|
amx_InternalDebugProc(NULL); /* clean up debug information */
|
|
if (terminal>0)
|
|
amx_clrscr();
|
|
terminal=0;
|
|
dbg_FreeInfo(&amxdbg);
|
|
free(program);
|
|
} /* if */
|
|
|
|
/* get filename */
|
|
if (strlen(amx_filename)==0) {
|
|
if (argc >= 2) {
|
|
strcpy(amx_filename,argv[1]);
|
|
} else {
|
|
amx_printf("File: ");
|
|
amx_gets(amx_filename,sizeof amx_filename);
|
|
if ((ptr=strchr(amx_filename,'\n'))!=NULL)
|
|
*ptr='\0'; /* strip newline characters */
|
|
} /* if */
|
|
} /* if */
|
|
if (strlen(amx_filename)==0)
|
|
return 1;
|
|
if ((program = loadprogram(&amx,amx_filename)) == NULL) {
|
|
/* try adding an extension */
|
|
strcat(amx_filename, ".amx");
|
|
if ((program = loadprogram(&amx,amx_filename)) == NULL) {
|
|
amx_printf("Cannot load the program file \"%s\"\n", amx_filename);
|
|
return 1;
|
|
} /* if */
|
|
} /* if */
|
|
/* switch the current directory to that of the debugged script */
|
|
if ((ptr=strrchr(amx_filename,DIRSEP_CHAR))!=NULL) {
|
|
extern int chdir(const char *path);
|
|
char dir[_MAX_PATH];
|
|
int len=(int)(ptr-amx_filename);
|
|
if (len<sizeof dir) {
|
|
strncpy(dir,amx_filename,len);
|
|
dir[len]='\0';
|
|
chdir(dir);
|
|
} /* if */
|
|
} /* if */
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
if (strncmp(argv[i],"-rs232",6)==0) {
|
|
#if !defined NO_REMOTE
|
|
int port,baud;
|
|
#if defined __WIN32__
|
|
port=1; /* default = COM1 */
|
|
#else
|
|
port=0; /* default = /dev/ttyS0 */
|
|
#endif
|
|
baud=57600;
|
|
if ((ptr=strchr(argv[i],'='))!=NULL)
|
|
sscanf(ptr+1,"%d,%d",&port,&baud);
|
|
amx_printf("Waiting for connection...");
|
|
amx_fflush();
|
|
if (!remote_rs232(port,baud)) {
|
|
amx_printf(" unable to open port %d\n",port);
|
|
return 1;
|
|
} /* if */
|
|
printf("\n");
|
|
#if defined __WIN32__
|
|
Sleep(100);
|
|
#else
|
|
usleep(100*1000);
|
|
#endif
|
|
printf("Host connected\n");
|
|
#else
|
|
printf("Remote debugging is not supported\n");
|
|
return 1;
|
|
#endif
|
|
} else if (strncmp(argv[i],"-transfer",9)==0) {
|
|
#if !defined NO_REMOTE
|
|
if (!remote_transfer_rs232(amx_filename)) {
|
|
amx_printf("\tRemote file transfer failed.\n");
|
|
return 1;
|
|
} /* if */
|
|
#else
|
|
printf("Remote debugging is not supported\n");
|
|
return 1;
|
|
#endif
|
|
} else if (strncmp(argv[i],"-term",5)==0) {
|
|
if ((ptr=strchr(argv[i],'='))!=NULL) {
|
|
if (strcmp(ptr+1,"off")==0) {
|
|
terminal=-1;
|
|
} else if (strcmp(ptr+1,"hide")==0) {
|
|
#if defined __WIN32__
|
|
#define CONSOLE_TITLE "Pawn Debugger"
|
|
HWND hwnd;
|
|
SetConsoleTitle(CONSOLE_TITLE);
|
|
hwnd=FindWindow("ConsoleWindowClass",CONSOLE_TITLE);
|
|
if (hwnd==NULL)
|
|
hwnd=FindWindow("tty",CONSOLE_TITLE); /* Windows 9x */
|
|
if (hwnd!=NULL)
|
|
ShowWindow(hwnd,SW_HIDE);
|
|
#endif
|
|
terminal=-1;
|
|
} else {
|
|
sscanf(ptr+1,"%d,%d",&screencolumns,&screenlines);
|
|
screenlines--;
|
|
} /* if */
|
|
} /* if */
|
|
} else if (strcmp(argv[i],"-quit")==0) {
|
|
return 0;
|
|
} /* if */
|
|
} /* for */
|
|
|
|
amx_Flags(&amx,&flags);
|
|
if ((flags & AMX_FLAG_DEBUG)==0) {
|
|
amx_printf("This program has no debug information\n");
|
|
return 1;
|
|
} /* if */
|
|
if (loaddbginfo(&amx,&amxdbg,skippath(amx_filename))!=AMX_ERR_NONE) {
|
|
amx_printf("Error loading debug information\n");
|
|
return 1;
|
|
} /* if */
|
|
amx_SetDebugHook(&amx, amx_InternalDebugProc);
|
|
signal(SIGINT,sigabort);
|
|
amx_InternalDebugProc(NULL); /* initialize debug hook */
|
|
|
|
#if defined AMX_TERMINAL && defined DBG_STREAMTERM
|
|
if (!CreateConsoleThread()) {
|
|
amx_printf("Failed to create console thread\n");
|
|
return 1;
|
|
} /* if */
|
|
#endif
|
|
|
|
if (remote==REMOTE_NONE) {
|
|
/* libraries do not need to be present for remote debugging */
|
|
amx_Register(&amx, core_Natives, -1);
|
|
err = amx_Register(&amx, console_Natives, -1);
|
|
if (err != AMX_ERR_NONE)
|
|
amx_printf("Load error %d\n", err);
|
|
} else {
|
|
err = AMX_ERR_NONE;
|
|
} /* if */
|
|
|
|
/* run the script, except... when doing remote debugging */
|
|
if (err == AMX_ERR_NONE) {
|
|
if (remote==REMOTE_NONE) {
|
|
err = amx_Exec(&amx, &ret, AMX_EXEC_MAIN);
|
|
while (err == AMX_ERR_SLEEP) {
|
|
amx_printf("Paused execution on \"sleep\"\n");
|
|
runmode = STEPPING; /* use the "sleep" as a "coded" break point */
|
|
err = amx_Exec(&amx, &ret, AMX_EXEC_CONT);
|
|
} /* while */
|
|
} else {
|
|
#if !defined NO_REMOTE
|
|
for ( ;; ) {
|
|
/* wait for input from the host */
|
|
remote_wait_rs232(&amx);
|
|
/* call the debug procedure ourselves */
|
|
amx_InternalDebugProc(&amx);
|
|
/* reply, to say that the script may continue to run */
|
|
remote_resume_rs232();
|
|
} /* for */
|
|
#endif
|
|
} /* if */
|
|
} /* if */
|
|
|
|
amx_InternalDebugProc(NULL); /* clean up debug information */
|
|
if (terminal>0)
|
|
amx_clrscr();
|
|
#if !defined NO_REMOTE
|
|
if (remote==REMOTE_RS232)
|
|
remote_rs232(0,0);
|
|
#endif
|
|
if (err != AMX_ERR_NONE)
|
|
amx_printf("Run time error %d\n", err);
|
|
else
|
|
amx_printf("Normal termination, return value %ld\n", (long)ret);
|
|
|
|
//??? option for restart
|
|
|
|
dbg_FreeInfo(&amxdbg);
|
|
free(program);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* !defined amx_Init */
|