mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-29 12:13:33 +08:00
ac2068375f
been started before killing the NLM by pointing the PC at _exit().
1165 lines
29 KiB
C
1165 lines
29 KiB
C
/* i386-nlmstub.c -- NLM debugging stub for the i386.
|
|
|
|
This is originally based on an m68k software stub written by Glenn
|
|
Engel at HP, but has changed quite a bit. It was modified for the
|
|
i386 by Jim Kingdon, Cygnus Support. It was modified to run under
|
|
NetWare by Ian Lance Taylor, Cygnus Support.
|
|
|
|
This code is intended to produce an NLM (a NetWare Loadable Module)
|
|
to run under NetWare on an i386 platform. To create the NLM,
|
|
compile this code into an object file using the NLM SDK on any i386
|
|
host, and use the nlmconv program (available in the GNU binutils)
|
|
to transform the resulting object file into an NLM. */
|
|
|
|
/****************************************************************************
|
|
|
|
THIS SOFTWARE IS NOT COPYRIGHTED
|
|
|
|
HP offers the following for use in the public domain. HP makes no
|
|
warranty with regard to the software or it's performance and the
|
|
user accepts the software "AS IS" with all faults.
|
|
|
|
HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD
|
|
TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
*
|
|
* The following gdb commands are supported:
|
|
*
|
|
* command function Return value
|
|
*
|
|
* g return the value of the CPU registers hex data or ENN
|
|
* G set the value of the CPU registers OK or ENN
|
|
*
|
|
* mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN
|
|
* MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN
|
|
*
|
|
* c Resume at current address SNN ( signal NN)
|
|
* cAA..AA Continue at address AA..AA SNN
|
|
*
|
|
* s Step one instruction SNN
|
|
* sAA..AA Step one instruction from AA..AA SNN
|
|
*
|
|
* k kill
|
|
*
|
|
* ? What was the last sigval ? SNN (signal NN)
|
|
*
|
|
* All commands and responses are sent with a packet which includes a
|
|
* checksum. A packet consists of
|
|
*
|
|
* $<packet info>#<checksum>.
|
|
*
|
|
* where
|
|
* <packet info> :: <characters representing the command or response>
|
|
* <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>>
|
|
*
|
|
* When a packet is received, it is first acknowledged with either '+' or '-'.
|
|
* '+' indicates a successful transfer. '-' indicates a failed transfer.
|
|
*
|
|
* Example:
|
|
*
|
|
* Host: Reply:
|
|
* $m0,10#2a +$00010203040506070809101112131415#42
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <dfs.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <aio.h>
|
|
#include <conio.h>
|
|
#include <advanced.h>
|
|
#include <debugapi.h>
|
|
#include <process.h>
|
|
#include <errno.h>
|
|
|
|
/****************************************************/
|
|
/* This information is from Novell. It is not in any of the standard
|
|
NetWare header files. */
|
|
|
|
struct DBG_LoadDefinitionStructure
|
|
{
|
|
void *reserved1[4];
|
|
LONG reserved5;
|
|
LONG LDCodeImageOffset;
|
|
LONG LDCodeImageLength;
|
|
LONG LDDataImageOffset;
|
|
LONG LDDataImageLength;
|
|
LONG LDUninitializedDataLength;
|
|
LONG LDCustomDataOffset;
|
|
LONG LDCustomDataSize;
|
|
LONG reserved6[2];
|
|
LONG (*LDInitializationProcedure)(void);
|
|
};
|
|
|
|
#define LO_NORMAL 0x0000
|
|
#define LO_STARTUP 0x0001
|
|
#define LO_PROTECT 0x0002
|
|
#define LO_DEBUG 0x0004
|
|
#define LO_AUTO_LOAD 0x0008
|
|
|
|
/* Loader returned error codes */
|
|
#define LOAD_COULD_NOT_FIND_FILE 1
|
|
#define LOAD_ERROR_READING_FILE 2
|
|
#define LOAD_NOT_NLM_FILE_FORMAT 3
|
|
#define LOAD_WRONG_NLM_FILE_VERSION 4
|
|
#define LOAD_REENTRANT_INITIALIZE_FAILURE 5
|
|
#define LOAD_CAN_NOT_LOAD_MULTIPLE_COPIES 6
|
|
#define LOAD_ALREADY_IN_PROGRESS 7
|
|
#define LOAD_NOT_ENOUGH_MEMORY 8
|
|
#define LOAD_INITIALIZE_FAILURE 9
|
|
#define LOAD_INCONSISTENT_FILE_FORMAT 10
|
|
#define LOAD_CAN_NOT_LOAD_AT_STARTUP 11
|
|
#define LOAD_AUTO_LOAD_MODULES_NOT_LOADED 12
|
|
#define LOAD_UNRESOLVED_EXTERNAL 13
|
|
#define LOAD_PUBLIC_ALREADY_DEFINED 14
|
|
/****************************************************/
|
|
|
|
/* The main thread ID. */
|
|
static int mainthread;
|
|
|
|
/* An error message for the main thread to print. */
|
|
static char *error_message;
|
|
|
|
/* The AIO port handle. */
|
|
static int AIOhandle;
|
|
|
|
/* BUFMAX defines the maximum number of characters in inbound/outbound
|
|
buffers. At least NUMREGBYTES*2 are needed for register packets */
|
|
#define BUFMAX (REGISTER_BYTES * 2 + 16)
|
|
|
|
/* remote_debug > 0 prints ill-formed commands in valid packets and
|
|
checksum errors. */
|
|
static int remote_debug = 1;
|
|
|
|
static const char hexchars[] = "0123456789abcdef";
|
|
|
|
/* Register values. All of these values *MUST* agree with tm.h */
|
|
#define SP_REGNUM 4 /* Contains address of top of stack */
|
|
#define PC_REGNUM 8 /* Contains program counter */
|
|
#define FP_REGNUM 5 /* Virtual frame pointer */
|
|
#define NUM_REGS 16 /* Number of machine registers */
|
|
#define REGISTER_BYTES (NUM_REGS * 4) /* Total size of registers array */
|
|
|
|
#define ExceptionPC ExceptionEIP
|
|
#define DECR_PC_AFTER_BREAK 1 /* int 3 leaves PC pointing after insn */
|
|
#define BREAKPOINT {0xcc}
|
|
#define StackFrame T_TSS_StackFrame
|
|
|
|
unsigned char breakpoint_insn[] = BREAKPOINT;
|
|
#define BREAKPOINT_SIZE (sizeof breakpoint_insn)
|
|
|
|
static void flush_i_cache() {}
|
|
|
|
static char *mem2hex (void *mem, char *buf, int count, int may_fault);
|
|
static char *hex2mem (char *buf, void *mem, int count, int may_fault);
|
|
static void set_step_traps (struct StackFrame *);
|
|
static void clear_step_traps (struct StackFrame *);
|
|
|
|
#if 0
|
|
__main() {};
|
|
#endif
|
|
|
|
/* Read a character from the serial port. This must busy wait, but
|
|
that's OK because we will be the only thread running anyhow. */
|
|
|
|
static int
|
|
getDebugChar ()
|
|
{
|
|
int err;
|
|
LONG got;
|
|
unsigned char ret;
|
|
|
|
do
|
|
{
|
|
err = AIOReadData (AIOhandle, (char *) &ret, 1, &got);
|
|
if (err != 0)
|
|
{
|
|
error_message = "AIOReadData failed";
|
|
ResumeThread (mainthread);
|
|
return -1;
|
|
}
|
|
}
|
|
while (got == 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Write a character to the serial port. Returns 0 on failure,
|
|
non-zero on success. */
|
|
|
|
static int
|
|
putDebugChar (c)
|
|
unsigned char c;
|
|
{
|
|
int err;
|
|
LONG put;
|
|
|
|
put = 0;
|
|
while (put < 1)
|
|
{
|
|
err = AIOWriteData (AIOhandle, (char *) &c, 1, &put);
|
|
if (err != 0)
|
|
ConsolePrintf ("AIOWriteData: err = %d, put = %d\r\n", err, put);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Get the registers out of the frame information. */
|
|
|
|
static void
|
|
frame_to_registers (frame, regs)
|
|
struct StackFrame *frame;
|
|
char *regs;
|
|
{
|
|
/* Copy EAX -> EDI */
|
|
mem2hex (&frame->ExceptionEAX, ®s[0 * 4 * 2], 4 * 8, 0);
|
|
|
|
/* Copy EIP & PS */
|
|
mem2hex (&frame->ExceptionPC, ®s[8 * 4 * 2], 4 * 2, 0);
|
|
|
|
/* Copy CS, SS, DS */
|
|
mem2hex (&frame->ExceptionCS, ®s[10 * 4 * 2], 4 * 3, 0);
|
|
|
|
/* Copy ES */
|
|
mem2hex (&frame->ExceptionES, ®s[13 * 4 * 2], 4 * 1, 0);
|
|
|
|
/* Copy FS & GS */
|
|
mem2hex (&frame->ExceptionFS, ®s[14 * 4 * 2], 4 * 2, 0);
|
|
}
|
|
|
|
/* Put the registers back into the frame information. */
|
|
|
|
static void
|
|
registers_to_frame (regs, frame)
|
|
char *regs;
|
|
struct StackFrame *frame;
|
|
{
|
|
/* Copy EAX -> EDI */
|
|
hex2mem (®s[0 * 4 * 2], &frame->ExceptionEAX, 4 * 8, 0);
|
|
|
|
/* Copy EIP & PS */
|
|
hex2mem (®s[8 * 4 * 2], &frame->ExceptionPC, 4 * 2, 0);
|
|
|
|
/* Copy CS, SS, DS */
|
|
hex2mem (®s[10 * 4 * 2], &frame->ExceptionCS, 4 * 3, 0);
|
|
|
|
/* Copy ES */
|
|
hex2mem (®s[13 * 4 * 2], &frame->ExceptionES, 4 * 1, 0);
|
|
|
|
/* Copy FS & GS */
|
|
hex2mem (®s[14 * 4 * 2], &frame->ExceptionFS, 4 * 2, 0);
|
|
}
|
|
|
|
/* Turn a hex character into a number. */
|
|
|
|
static int
|
|
hex (ch)
|
|
char ch;
|
|
{
|
|
if ((ch >= 'a') && (ch <= 'f'))
|
|
return (ch-'a'+10);
|
|
if ((ch >= '0') && (ch <= '9'))
|
|
return (ch-'0');
|
|
if ((ch >= 'A') && (ch <= 'F'))
|
|
return (ch-'A'+10);
|
|
return (-1);
|
|
}
|
|
|
|
/* Scan for the sequence $<data>#<checksum>. Returns 0 on failure,
|
|
non-zero on success. */
|
|
|
|
static int
|
|
getpacket (buffer)
|
|
char * buffer;
|
|
{
|
|
unsigned char checksum;
|
|
unsigned char xmitcsum;
|
|
int i;
|
|
int count;
|
|
int ch;
|
|
|
|
do
|
|
{
|
|
/* wait around for the start character, ignore all other characters */
|
|
while ((ch = getDebugChar()) != '$')
|
|
if (ch == -1)
|
|
return 0;
|
|
checksum = 0;
|
|
xmitcsum = -1;
|
|
|
|
count = 0;
|
|
|
|
/* now, read until a # or end of buffer is found */
|
|
while (count < BUFMAX)
|
|
{
|
|
ch = getDebugChar();
|
|
if (ch == -1)
|
|
return 0;
|
|
if (ch == '#')
|
|
break;
|
|
checksum = checksum + ch;
|
|
buffer[count] = ch;
|
|
count = count + 1;
|
|
}
|
|
buffer[count] = 0;
|
|
|
|
if (ch == '#')
|
|
{
|
|
ch = getDebugChar ();
|
|
if (ch == -1)
|
|
return 0;
|
|
xmitcsum = hex(ch) << 4;
|
|
ch = getDebugChar ();
|
|
if (ch == -1)
|
|
return 0;
|
|
xmitcsum += hex(ch);
|
|
|
|
if (checksum != xmitcsum)
|
|
{
|
|
if (remote_debug)
|
|
ConsolePrintf ("bad checksum. My count = 0x%x, sent=0x%x. buf=%s\n",
|
|
checksum,xmitcsum,buffer);
|
|
/* failed checksum */
|
|
if (! putDebugChar('-'))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
/* successful transfer */
|
|
if (! putDebugChar('+'))
|
|
return 0;
|
|
/* if a sequence char is present, reply the sequence ID */
|
|
if (buffer[2] == ':')
|
|
{
|
|
if (! putDebugChar (buffer[0])
|
|
|| ! putDebugChar (buffer[1]))
|
|
return 0;
|
|
/* remove sequence chars from buffer */
|
|
count = strlen(buffer);
|
|
for (i=3; i <= count; i++)
|
|
buffer[i-3] = buffer[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (checksum != xmitcsum);
|
|
|
|
if (remote_debug)
|
|
ConsolePrintf ("Received packet \"%s\"\r\n", buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Send the packet in buffer. Returns 0 on failure, non-zero on
|
|
success. */
|
|
|
|
static int
|
|
putpacket (buffer)
|
|
char * buffer;
|
|
{
|
|
unsigned char checksum;
|
|
int count;
|
|
int ch;
|
|
|
|
if (remote_debug)
|
|
ConsolePrintf ("Sending packet \"%s\"\r\n", buffer);
|
|
|
|
/* $<packet info>#<checksum>. */
|
|
do
|
|
{
|
|
if (! putDebugChar('$'))
|
|
return 0;
|
|
checksum = 0;
|
|
count = 0;
|
|
|
|
while (ch=buffer[count])
|
|
{
|
|
if (! putDebugChar(ch))
|
|
return 0;
|
|
checksum += ch;
|
|
count += 1;
|
|
}
|
|
|
|
if (! putDebugChar('#')
|
|
|| ! putDebugChar(hexchars[checksum >> 4])
|
|
|| ! putDebugChar(hexchars[checksum % 16]))
|
|
return 0;
|
|
|
|
ch = getDebugChar ();
|
|
if (ch == -1)
|
|
return 0;
|
|
}
|
|
while (ch != '+');
|
|
|
|
return 1;
|
|
}
|
|
|
|
static char remcomInBuffer[BUFMAX];
|
|
static char remcomOutBuffer[BUFMAX];
|
|
static short error;
|
|
|
|
static void
|
|
debug_error (format, parm)
|
|
char *format;
|
|
char *parm;
|
|
{
|
|
if (remote_debug)
|
|
{
|
|
ConsolePrintf (format, parm);
|
|
ConsolePrintf ("\n");
|
|
}
|
|
}
|
|
|
|
/* This is set if we could get a memory access fault. */
|
|
static int mem_may_fault;
|
|
|
|
/* Indicate to caller of mem2hex or hex2mem that there has been an
|
|
error. */
|
|
static volatile int mem_err = 0;
|
|
|
|
/* These are separate functions so that they are so short and sweet
|
|
that the compiler won't save any registers (if there is a fault
|
|
to mem_fault, they won't get restored, so there better not be any
|
|
saved). */
|
|
|
|
static int
|
|
get_char (addr)
|
|
char *addr;
|
|
{
|
|
return *addr;
|
|
}
|
|
|
|
static void
|
|
set_char (addr, val)
|
|
char *addr;
|
|
int val;
|
|
{
|
|
*addr = val;
|
|
}
|
|
|
|
/* This bit of assembly language just returns from a function. If a
|
|
memory error occurs within get_char or set_char, the debugger
|
|
handler points EIP at these instructions to get out. */
|
|
|
|
extern void just_return ();
|
|
asm (".globl just_return");
|
|
asm (".globl _just_return");
|
|
asm ("just_return:");
|
|
asm ("_just_return:");
|
|
asm ("leave");
|
|
asm ("ret");
|
|
|
|
/* convert the memory pointed to by mem into hex, placing result in buf */
|
|
/* return a pointer to the last char put in buf (null) */
|
|
/* If MAY_FAULT is non-zero, then we should set mem_err in response to
|
|
a fault; if zero treat a fault like any other fault in the stub. */
|
|
|
|
static char *
|
|
mem2hex (mem, buf, count, may_fault)
|
|
void *mem;
|
|
char *buf;
|
|
int count;
|
|
int may_fault;
|
|
{
|
|
int i;
|
|
unsigned char ch;
|
|
char *ptr = mem;
|
|
|
|
mem_may_fault = may_fault;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
ch = get_char (ptr++);
|
|
if (may_fault && mem_err)
|
|
return (buf);
|
|
*buf++ = hexchars[ch >> 4];
|
|
*buf++ = hexchars[ch % 16];
|
|
}
|
|
*buf = 0;
|
|
mem_may_fault = 0;
|
|
return(buf);
|
|
}
|
|
|
|
/* convert the hex array pointed to by buf into binary to be placed in mem */
|
|
/* return a pointer to the character AFTER the last byte written */
|
|
|
|
static char *
|
|
hex2mem (buf, mem, count, may_fault)
|
|
char *buf;
|
|
void *mem;
|
|
int count;
|
|
int may_fault;
|
|
{
|
|
int i;
|
|
unsigned char ch;
|
|
char *ptr = mem;
|
|
|
|
mem_may_fault = may_fault;
|
|
for (i=0;i<count;i++)
|
|
{
|
|
ch = hex(*buf++) << 4;
|
|
ch = ch + hex(*buf++);
|
|
set_char (ptr++, ch);
|
|
if (may_fault && mem_err)
|
|
return (ptr);
|
|
}
|
|
mem_may_fault = 0;
|
|
return(mem);
|
|
}
|
|
|
|
/* This function takes the 386 exception vector and attempts to
|
|
translate this number into a unix compatible signal value. */
|
|
|
|
static int
|
|
computeSignal (exceptionVector)
|
|
int exceptionVector;
|
|
{
|
|
int sigval;
|
|
switch (exceptionVector)
|
|
{
|
|
case 0 : sigval = 8; break; /* divide by zero */
|
|
case 1 : sigval = 5; break; /* debug exception */
|
|
case 3 : sigval = 5; break; /* breakpoint */
|
|
case 4 : sigval = 16; break; /* into instruction (overflow) */
|
|
case 5 : sigval = 16; break; /* bound instruction */
|
|
case 6 : sigval = 4; break; /* Invalid opcode */
|
|
case 7 : sigval = 8; break; /* coprocessor not available */
|
|
case 8 : sigval = 7; break; /* double fault */
|
|
case 9 : sigval = 11; break; /* coprocessor segment overrun */
|
|
case 10 : sigval = 11; break; /* Invalid TSS */
|
|
case 11 : sigval = 11; break; /* Segment not present */
|
|
case 12 : sigval = 11; break; /* stack exception */
|
|
case 13 : sigval = 11; break; /* general protection */
|
|
case 14 : sigval = 11; break; /* page fault */
|
|
case 16 : sigval = 7; break; /* coprocessor error */
|
|
default:
|
|
sigval = 7; /* "software generated"*/
|
|
}
|
|
return (sigval);
|
|
}
|
|
|
|
/**********************************************/
|
|
/* WHILE WE FIND NICE HEX CHARS, BUILD AN INT */
|
|
/* RETURN NUMBER OF CHARS PROCESSED */
|
|
/**********************************************/
|
|
static int
|
|
hexToInt(ptr, intValue)
|
|
char **ptr;
|
|
int *intValue;
|
|
{
|
|
int numChars = 0;
|
|
int hexValue;
|
|
|
|
*intValue = 0;
|
|
|
|
while (**ptr)
|
|
{
|
|
hexValue = hex(**ptr);
|
|
if (hexValue >=0)
|
|
{
|
|
*intValue = (*intValue <<4) | hexValue;
|
|
numChars ++;
|
|
}
|
|
else
|
|
break;
|
|
|
|
(*ptr)++;
|
|
}
|
|
|
|
return (numChars);
|
|
}
|
|
|
|
static void
|
|
set_step_traps (frame)
|
|
struct StackFrame *frame;
|
|
{
|
|
frame->ExceptionSystemFlags |= 0x100;
|
|
}
|
|
|
|
static void
|
|
clear_step_traps (frame)
|
|
struct StackFrame *frame;
|
|
{
|
|
frame->ExceptionSystemFlags &= ~0x100;
|
|
}
|
|
|
|
static void
|
|
do_status (ptr, frame)
|
|
char *ptr;
|
|
struct StackFrame *frame;
|
|
{
|
|
int sigval;
|
|
|
|
sigval = computeSignal (frame->ExceptionNumber);
|
|
|
|
sprintf (ptr, "T%02x", sigval);
|
|
ptr += 3;
|
|
|
|
sprintf (ptr, "%02x:", PC_REGNUM);
|
|
ptr = mem2hex (&frame->ExceptionPC, ptr + 3, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
sprintf (ptr, "%02x:", SP_REGNUM);
|
|
ptr = mem2hex (&frame->ExceptionESP, ptr + 3, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
sprintf (ptr, "%02x:", FP_REGNUM);
|
|
ptr = mem2hex (&frame->ExceptionEBP, ptr + 3, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
*ptr = '\000';
|
|
}
|
|
|
|
/* This function does all command processing for interfacing to gdb.
|
|
It is called whenever an exception occurs in the module being
|
|
debugged. */
|
|
|
|
static LONG
|
|
handle_exception (frame)
|
|
struct StackFrame *frame;
|
|
{
|
|
int addr, length;
|
|
char *ptr;
|
|
static int thread_killed = 0;
|
|
static int thread_started = 0;
|
|
static struct DBG_LoadDefinitionStructure *ldinfo = 0;
|
|
static unsigned char first_insn[BREAKPOINT_SIZE]; /* The first instruction in the program. */
|
|
|
|
/* Apparently the bell can sometimes be ringing at this point, and
|
|
should be stopped. */
|
|
StopBell ();
|
|
|
|
if (remote_debug)
|
|
{
|
|
ConsolePrintf ("vector=%d: %s, sr=%08x, pc=%08x, thread=%08x\r\n",
|
|
frame->ExceptionNumber,
|
|
frame->ExceptionDescription,
|
|
frame->ExceptionSystemFlags,
|
|
frame->ExceptionPC,
|
|
GetThreadID ());
|
|
}
|
|
|
|
switch (frame->ExceptionNumber)
|
|
{
|
|
case START_NLM_EVENT:
|
|
/* If the NLM just started, we record the module load information
|
|
and the thread ID, and set a breakpoint at the first instruction
|
|
in the program. */
|
|
|
|
ldinfo = ((struct DBG_LoadDefinitionStructure *)
|
|
frame->ExceptionErrorCode);
|
|
memcpy (first_insn, ldinfo->LDInitializationProcedure,
|
|
BREAKPOINT_SIZE);
|
|
memcpy (ldinfo->LDInitializationProcedure, breakpoint_insn,
|
|
BREAKPOINT_SIZE);
|
|
flush_i_cache ();
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
case START_THREAD_EVENT:
|
|
thread_started = 1;
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
case TERMINATE_NLM_EVENT:
|
|
if (!thread_killed)
|
|
{
|
|
/* NetWare processes don't have an exit status so we
|
|
generate our own */
|
|
sprintf (remcomOutBuffer, "W%02x", 0);
|
|
putpacket(remcomOutBuffer);
|
|
}
|
|
ResumeThread (mainthread);
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
case ENTER_DEBUGGER_EVENT:
|
|
case KEYBOARD_BREAK_EVENT:
|
|
/* Pass some events on to the next debugger, in case it will handle
|
|
them. */
|
|
return RETURN_TO_NEXT_DEBUGGER;
|
|
|
|
case 3: /* Breakpoint */
|
|
/* After we've reached the initial breakpoint, reset it. */
|
|
if (frame->ExceptionPC - DECR_PC_AFTER_BREAK == (LONG) ldinfo->LDInitializationProcedure
|
|
&& memcmp (ldinfo->LDInitializationProcedure, breakpoint_insn,
|
|
BREAKPOINT_SIZE) == 0)
|
|
{
|
|
memcpy (ldinfo->LDInitializationProcedure, first_insn,
|
|
BREAKPOINT_SIZE);
|
|
frame->ExceptionPC -= DECR_PC_AFTER_BREAK;
|
|
flush_i_cache ();
|
|
}
|
|
/* Normal breakpoints end up here */
|
|
do_status (remcomOutBuffer, frame);
|
|
break;
|
|
|
|
default:
|
|
/* At the moment, we don't care about most of the unusual NetWare
|
|
exceptions. */
|
|
if (frame->ExceptionNumber > 31)
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
/* Most machine level exceptions end up here */
|
|
do_status (remcomOutBuffer, frame);
|
|
break;
|
|
|
|
case 11: /* Segment not present */
|
|
case 13: /* General protection */
|
|
case 14: /* Page fault */
|
|
/* If we get a GP fault, and mem_may_fault is set, and the
|
|
instruction pointer is near set_char or get_char, then we caused
|
|
the fault ourselves accessing an illegal memory location. */
|
|
if (mem_may_fault
|
|
&& ((frame->ExceptionPC >= (long) &set_char
|
|
&& frame->ExceptionPC < (long) &set_char + 50)
|
|
|| (frame->ExceptionPC >= (long) &get_char
|
|
&& frame->ExceptionPC < (long) &get_char + 50)))
|
|
{
|
|
mem_err = 1;
|
|
/* Point the instruction pointer at an assembly language stub
|
|
which just returns from the function. */
|
|
|
|
frame->ExceptionPC = (long) &just_return;
|
|
|
|
/* Keep going. This will act as though it returned from
|
|
set_char or get_char. The calling routine will check
|
|
mem_err, and do the right thing. */
|
|
return RETURN_TO_PROGRAM;
|
|
}
|
|
/* Random mem fault, report it */
|
|
do_status (remcomOutBuffer, frame);
|
|
break;
|
|
}
|
|
|
|
/* We point the PC at _exit() and continue to kill the NLM, but that
|
|
won't work until it's thread has been started. */
|
|
if (thread_started && thread_killed)
|
|
{
|
|
frame->ExceptionPC = &_exit;
|
|
return RETURN_TO_PROGRAM;
|
|
}
|
|
|
|
/* FIXME: How do we know that this exception has anything to do with
|
|
the program we are debugging? We can check whether the PC is in
|
|
the range of the module we are debugging, but that doesn't help
|
|
much since an error could occur in a library routine. */
|
|
|
|
clear_step_traps (frame);
|
|
|
|
if (! putpacket(remcomOutBuffer))
|
|
return RETURN_TO_NEXT_DEBUGGER;
|
|
|
|
while (1)
|
|
{
|
|
error = 0;
|
|
remcomOutBuffer[0] = 0;
|
|
if (! getpacket (remcomInBuffer))
|
|
return RETURN_TO_NEXT_DEBUGGER;
|
|
switch (remcomInBuffer[0])
|
|
{
|
|
case '?':
|
|
do_status (remcomOutBuffer, frame);
|
|
break;
|
|
case 'd':
|
|
remote_debug = !(remote_debug); /* toggle debug flag */
|
|
break;
|
|
case 'g':
|
|
/* return the value of the CPU registers */
|
|
frame_to_registers (frame, remcomOutBuffer);
|
|
break;
|
|
case 'G':
|
|
/* set the value of the CPU registers - return OK */
|
|
registers_to_frame (&remcomInBuffer[1], frame);
|
|
strcpy(remcomOutBuffer,"OK");
|
|
break;
|
|
|
|
case 'm':
|
|
/* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
|
|
/* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */
|
|
ptr = &remcomInBuffer[1];
|
|
if (hexToInt(&ptr,&addr))
|
|
if (*(ptr++) == ',')
|
|
if (hexToInt(&ptr,&length))
|
|
{
|
|
ptr = 0;
|
|
mem_err = 0;
|
|
mem2hex((char*) addr, remcomOutBuffer, length, 1);
|
|
if (mem_err)
|
|
{
|
|
strcpy (remcomOutBuffer, "E03");
|
|
debug_error ("memory fault");
|
|
}
|
|
}
|
|
|
|
if (ptr)
|
|
{
|
|
strcpy(remcomOutBuffer,"E01");
|
|
debug_error("malformed read memory command: %s",remcomInBuffer);
|
|
}
|
|
break;
|
|
|
|
case 'M':
|
|
/* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
|
|
/* TRY TO READ '%x,%x:'. IF SUCCEED, SET PTR = 0 */
|
|
ptr = &remcomInBuffer[1];
|
|
if (hexToInt(&ptr,&addr))
|
|
if (*(ptr++) == ',')
|
|
if (hexToInt(&ptr,&length))
|
|
if (*(ptr++) == ':')
|
|
{
|
|
mem_err = 0;
|
|
hex2mem(ptr, (char*) addr, length, 1);
|
|
|
|
if (mem_err)
|
|
{
|
|
strcpy (remcomOutBuffer, "E03");
|
|
debug_error ("memory fault");
|
|
}
|
|
else
|
|
{
|
|
strcpy(remcomOutBuffer,"OK");
|
|
}
|
|
|
|
ptr = 0;
|
|
}
|
|
if (ptr)
|
|
{
|
|
strcpy(remcomOutBuffer,"E02");
|
|
debug_error("malformed write memory command: %s",remcomInBuffer);
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
case 's':
|
|
/* cAA..AA Continue at address AA..AA(optional) */
|
|
/* sAA..AA Step one instruction from AA..AA(optional) */
|
|
/* try to read optional parameter, pc unchanged if no parm */
|
|
ptr = &remcomInBuffer[1];
|
|
if (hexToInt(&ptr,&addr))
|
|
{
|
|
/* registers[PC_REGNUM].lo = addr;*/
|
|
ConsolePrintf("Setting PC to 0x%x\n", addr);
|
|
while (1);
|
|
}
|
|
|
|
if (remcomInBuffer[0] == 's')
|
|
set_step_traps (frame);
|
|
|
|
flush_i_cache ();
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
case 'k':
|
|
/* The undocumented netware call KillMe() is supposed to
|
|
schedule the NLM to be killed when it next blocks. What
|
|
really happens is that the server hangs as it tries to
|
|
unload the NLM.
|
|
|
|
So, since netware won't cooperate, we just point the PC
|
|
at the start of _exit() and continue, while noting that
|
|
we've killed the process. */
|
|
|
|
thread_killed = 1;
|
|
if (thread_started)
|
|
frame->ExceptionPC = &_exit;
|
|
return RETURN_TO_PROGRAM;
|
|
|
|
case 'q': /* Query message */
|
|
if (strcmp (&remcomInBuffer[1], "Offsets") == 0)
|
|
{
|
|
sprintf (remcomOutBuffer, "Text=%x;Data=%x;Bss=%x",
|
|
ldinfo->LDCodeImageOffset,
|
|
ldinfo->LDDataImageOffset,
|
|
ldinfo->LDDataImageOffset + ldinfo->LDDataImageLength);
|
|
}
|
|
else
|
|
sprintf (remcomOutBuffer, "E04, Unknown query %s", &remcomInBuffer[1]);
|
|
break;
|
|
}
|
|
|
|
/* reply to the request */
|
|
if (! putpacket(remcomOutBuffer))
|
|
return RETURN_TO_NEXT_DEBUGGER;
|
|
}
|
|
}
|
|
|
|
|
|
char *progname;
|
|
|
|
struct bitRate {
|
|
BYTE bitRate;
|
|
const char *bitRateString;
|
|
};
|
|
|
|
struct bitRate bitRateTable[] =
|
|
{
|
|
{ AIO_BAUD_50 , "50" },
|
|
{ AIO_BAUD_75 , "75" },
|
|
{ AIO_BAUD_110 , "110" },
|
|
{ AIO_BAUD_134p5 , "134.5" },
|
|
{ AIO_BAUD_150 , "150" },
|
|
{ AIO_BAUD_300 , "300" },
|
|
{ AIO_BAUD_600 , "600" },
|
|
{ AIO_BAUD_1200 , "1200" },
|
|
{ AIO_BAUD_1800 , "1800" },
|
|
{ AIO_BAUD_2000 , "2000" },
|
|
{ AIO_BAUD_2400 , "2400" },
|
|
{ AIO_BAUD_3600 , "3600" },
|
|
{ AIO_BAUD_4800 , "4800" },
|
|
{ AIO_BAUD_7200 , "7200" },
|
|
{ AIO_BAUD_9600 , "9600" },
|
|
{ AIO_BAUD_19200 , "19200" },
|
|
{ AIO_BAUD_38400 , "38400" },
|
|
{ AIO_BAUD_57600 , "57600" },
|
|
{ AIO_BAUD_115200, "115200" },
|
|
{ -1, NULL }
|
|
};
|
|
|
|
char dataBitsTable[] = "5678";
|
|
|
|
char *stopBitsTable[] = { "1", "1.5", "2" };
|
|
|
|
char parity[] = "NOEMS";
|
|
|
|
/* Start up. The main thread opens the named serial I/O port, loads
|
|
the named NLM module and then goes to sleep. The serial I/O port
|
|
is named as a board number and a port number. It would be more DOS
|
|
like to provide a menu of available serial ports, but I don't want
|
|
to have to figure out how to do that. */
|
|
|
|
int
|
|
main (argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
int hardware, board, port;
|
|
BYTE bitRate;
|
|
BYTE dataBits;
|
|
BYTE stopBits;
|
|
BYTE parityMode;
|
|
LONG err;
|
|
struct debuggerStructure s;
|
|
int cmdindx;
|
|
char *cmdlin;
|
|
int i;
|
|
|
|
/* set progname */
|
|
progname = "nlmstub";
|
|
|
|
hardware = -1;
|
|
board = 0;
|
|
port = 0;
|
|
|
|
/* set default serial line characteristics */
|
|
bitRate = AIO_BAUD_9600;
|
|
dataBits = AIO_DATA_BITS_8;
|
|
stopBits = AIO_STOP_BITS_1;
|
|
parityMode = AIO_PARITY_NONE;
|
|
|
|
cmdindx = 0;
|
|
for (argc--, argv++; *argv; argc--, argv++)
|
|
{
|
|
char *bp;
|
|
char *ep;
|
|
|
|
if (strnicmp(*argv, "BAUD=", 5) == 0)
|
|
{
|
|
struct bitRate *brp;
|
|
|
|
bp = *argv + 5;
|
|
for (brp = bitRateTable; brp->bitRate != (BYTE) -1; brp++)
|
|
{
|
|
if (strcmp(brp->bitRateString, bp) == 0)
|
|
{
|
|
bitRate = brp->bitRate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (brp->bitRateString == NULL)
|
|
{
|
|
fprintf(stderr, "%s: %s: unknown or unsupported bit rate",
|
|
progname, bp);
|
|
exit (1);
|
|
}
|
|
}
|
|
else if (strnicmp(*argv, "NODE=", 5) == 0)
|
|
{
|
|
bp = *argv + 5;
|
|
board = strtol (bp, &ep, 0);
|
|
if (ep == bp || *ep != '\0')
|
|
{
|
|
fprintf (stderr, "%s: %s: expected integer argument\n",
|
|
progname, bp);
|
|
exit(1);
|
|
}
|
|
}
|
|
else if (strnicmp(*argv, "PORT=", 5) == 0)
|
|
{
|
|
bp = *argv + 5;
|
|
port = strtol (bp, &ep, 0);
|
|
if (ep == bp || *ep != '\0')
|
|
{
|
|
fprintf (stderr, "%s: %s: expected integer argument\n",
|
|
progname, bp);
|
|
exit(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
cmdindx++;
|
|
}
|
|
|
|
if (argc == 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Usage: load %s [options] program [arguments]\n", progname);
|
|
exit (1);
|
|
}
|
|
|
|
err = AIOAcquirePort (&hardware, &board, &port, &AIOhandle);
|
|
if (err != AIO_SUCCESS)
|
|
{
|
|
switch (err)
|
|
{
|
|
case AIO_PORT_NOT_AVAILABLE:
|
|
fprintf (stderr, "Port not available\n");
|
|
break;
|
|
|
|
case AIO_BOARD_NUMBER_INVALID:
|
|
case AIO_PORT_NUMBER_INVALID:
|
|
fprintf (stderr, "No such port\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf (stderr, "Could not open port: %d\n", err);
|
|
break;
|
|
}
|
|
|
|
exit (1);
|
|
}
|
|
|
|
err = AIOConfigurePort (AIOhandle, bitRate, dataBits, stopBits, parityMode,
|
|
AIO_HARDWARE_FLOW_CONTROL_OFF);
|
|
|
|
if (err == AIO_QUALIFIED_SUCCESS)
|
|
{
|
|
AIOPORTCONFIG portConfig;
|
|
|
|
fprintf (stderr, "Port configuration changed!\n");
|
|
|
|
portConfig.returnLength = sizeof(portConfig);
|
|
AIOGetPortConfiguration (AIOhandle, &portConfig, NULL);
|
|
|
|
fprintf (stderr,
|
|
" Bit Rate: %s, Data Bits: %c, Stop Bits: %s, Parity: %c,\
|
|
Flow:%s\n",
|
|
bitRateTable[portConfig.bitRate].bitRateString,
|
|
dataBitsTable[portConfig.dataBits],
|
|
stopBitsTable[portConfig.stopBits],
|
|
parity[portConfig.parityMode],
|
|
portConfig.flowCtrlMode ? "ON" : "OFF");
|
|
}
|
|
else if (err != AIO_SUCCESS)
|
|
{
|
|
fprintf (stderr, "Could not configure port: %d\n", err);
|
|
AIOReleasePort (AIOhandle);
|
|
exit (1);
|
|
}
|
|
|
|
if (AIOSetExternalControl(AIOhandle, AIO_EXTERNAL_CONTROL,
|
|
(AIO_EXTCTRL_DTR | AIO_EXTCTRL_RTS))
|
|
!= AIO_SUCCESS)
|
|
{
|
|
LONG extStatus, chgdExtStatus;
|
|
|
|
fprintf (stderr, "Could not set desired port controls!\n");
|
|
AIOGetExternalStatus (AIOhandle, &extStatus, &chgdExtStatus);
|
|
fprintf (stderr, "Port controls now: %d, %d\n", extStatus,
|
|
chgdExtStatus);
|
|
}
|
|
|
|
/* Register ourselves as an alternate debugger. */
|
|
memset (&s, 0, sizeof s);
|
|
s.DDSResourceTag = ((struct ResourceTagStructure *)
|
|
AllocateResourceTag (GetNLMHandle (),
|
|
(BYTE *)"gdbserver",
|
|
DebuggerSignature));
|
|
if (s.DDSResourceTag == 0)
|
|
{
|
|
fprintf (stderr, "AllocateResourceTag failed\n");
|
|
AIOReleasePort (AIOhandle);
|
|
exit (1);
|
|
}
|
|
s.DDSdebuggerEntry = handle_exception;
|
|
s.DDSFlags = TSS_FRAME_BIT;
|
|
|
|
err = RegisterDebuggerRTag (&s, AT_FIRST);
|
|
if (err != 0)
|
|
{
|
|
fprintf (stderr, "RegisterDebuggerRTag failed\n");
|
|
AIOReleasePort (AIOhandle);
|
|
exit (1);
|
|
}
|
|
|
|
/* Get the command line we were invoked with, and advance it past
|
|
our name and command line arguments. */
|
|
cmdlin = getcmd ((char *) NULL);
|
|
for (i = 0; i < cmdindx; i++)
|
|
{
|
|
while (! isspace (*cmdlin))
|
|
cmdlin++;
|
|
while (isspace (*cmdlin))
|
|
cmdlin++;
|
|
}
|
|
|
|
/* In case GDB is started before us, ack any packets (presumably
|
|
"$?#xx") sitting there. */
|
|
if (! putDebugChar ('+'))
|
|
{
|
|
fprintf (stderr, "putDebugChar failed\n");
|
|
UnRegisterDebugger (&s);
|
|
AIOReleasePort (AIOhandle);
|
|
exit (1);
|
|
}
|
|
|
|
mainthread = GetThreadID ();
|
|
|
|
if (remote_debug > 0)
|
|
ConsolePrintf ("About to call LoadModule with \"%s\" %08x\r\n",
|
|
cmdlin, __GetScreenID (GetCurrentScreen()));
|
|
|
|
/* Start up the module to be debugged. */
|
|
err = LoadModule ((struct ScreenStruct *) __GetScreenID (GetCurrentScreen()),
|
|
(BYTE *)cmdlin, LO_DEBUG);
|
|
if (err != 0)
|
|
{
|
|
fprintf (stderr, "LoadModule failed: %d\n", err);
|
|
UnRegisterDebugger (&s);
|
|
AIOReleasePort (AIOhandle);
|
|
exit (1);
|
|
}
|
|
|
|
/* Wait for the debugger to wake us up. */
|
|
if (remote_debug > 0)
|
|
ConsolePrintf ("Suspending main thread (%08x)\r\n", mainthread);
|
|
SuspendThread (mainthread);
|
|
if (remote_debug > 0)
|
|
ConsolePrintf ("Resuming main thread (%08x)\r\n", mainthread);
|
|
|
|
/* If we are woken up, print an optional error message, deregister
|
|
ourselves and exit. */
|
|
if (error_message != NULL)
|
|
fprintf (stderr, "%s\n", error_message);
|
|
UnRegisterDebugger (&s);
|
|
AIOReleasePort (AIOhandle);
|
|
exit (0);
|
|
}
|