Commit 911436bf authored by Eric Pouech's avatar Eric Pouech Committed by Alexandre Julliard

- cleaned-up break handling

- better integration of debugger inner loops (parser & events) - added attach command - improved parser so that it can be entered without any process loaded - added BreakOnFirstChance internal variable - disabled NE module symbol module (which is broken with ASS) - misc portability cleanups
parent fb949605
......@@ -369,6 +369,47 @@ void DEBUG_AddBreakpoint( const DBG_VALUE *_value, BOOL (*func)(void) )
DEBUG_Printf( DBG_CHN_MESG, "\n" );
}
/***********************************************************************
* DEBUG_AddBreakpointFromId
*
* Add a breakpoint from a function name (and eventually a line #)
*/
void DEBUG_AddBreakpointFromId(const char *name, int lineno)
{
DBG_VALUE value;
if (DEBUG_GetSymbolValue(name, lineno, &value, TRUE))
DEBUG_AddBreakpoint(&value, NULL);
else
DEBUG_Printf(DBG_CHN_MESG, "Unable to add breakpoint\n");
}
/***********************************************************************
* DEBUG_AddBreakpointFromLineno
*
* Add a breakpoint from a line number in current file
*/
void DEBUG_AddBreakpointFromLineno(int lineno)
{
DBG_VALUE value;
DEBUG_GetCurrentAddress(&value.addr);
if (lineno != -1) {
struct name_hash* nh;
DEBUG_FindNearestSymbol(&value.addr, TRUE, &nh, 0, NULL);
if (nh == NULL) {
DEBUG_Printf(DBG_CHN_MESG,"Unable to add breakpoint\n");
return;
}
DEBUG_GetLineNumberAddr(nh, lineno, &value.addr, TRUE);
}
value.type = NULL;
value.cookie = DV_TARGET;
DEBUG_AddBreakpoint( &value, NULL );
}
/***********************************************************************
* DEBUG_AddWatchpoint
......@@ -439,6 +480,21 @@ void DEBUG_AddWatchpoint( const DBG_VALUE *_value, BOOL is_write )
}
/***********************************************************************
* DEBUG_AddWathpointFromId
*
* Add a watchpoint from a symbol name (and eventually a line #)
*/
void DEBUG_AddWatchpointFromId(const char *name, int lineno)
{
DBG_VALUE value;
if( DEBUG_GetSymbolValue(name, lineno, &value, TRUE) )
DEBUG_AddWatchpoint( &value, 1 );
else
DEBUG_Printf(DBG_CHN_MESG, "Unable to add watchpoint\n");
}
/***********************************************************************
* DEBUG_DelBreakpoint
*
* Delete a breakpoint.
......@@ -921,8 +977,6 @@ enum exec_mode DEBUG_RestartExecution( enum exec_mode mode, int count )
DEBUG_context.EFlags |= STEP_FLAG;
#endif
break;
case EXEC_KILL:
break;
default:
RaiseException(DEBUG_STATUS_INTERNAL_ERROR, 0, 0, NULL);
}
......
......@@ -2,6 +2,7 @@
* Lexical scanner for command line parsing
*
* Copyright 1993 Eric Youngdale
* 2000 Eric Pouech
*/
%{
......@@ -10,12 +11,6 @@
#include "debugger.h"
#include "y.tab.h"
#ifdef DBG_need_heap
#define malloc(x) DBG_alloc(x)
#define realloc(x,y) DBG_realloc(x,y)
#define free(x) DBG_free(x)
#endif
#ifndef DONT_USE_READLINE
#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) \
......@@ -23,7 +18,7 @@
YY_FATAL_ERROR( "read() in flex scanner failed" );
static int dbg_read(char * buf, int size);
static char * make_symbol(char *);
static char * DEBUG_MakeSymbol(char *);
#endif /* DONT_USE_READLINE */
......@@ -47,40 +42,42 @@ STRING \"[^\n"]+\"
%s WALK_CMD
%s SHOW_CMD
%s NOCMD
%s DEBUGSTR
%x ASTRING_EXPECTED
%x NOPROCESS
%%
\n { BEGIN(0); syntax_error = 0;
return tEOL; } /*Indicates end of command. Reset state. */
"||" { return OP_LOR; }
"&&" { return OP_LAND; }
"==" { return OP_EQ; }
"!=" { return OP_NE; }
"<=" { return OP_LE; }
">=" { return OP_GE; }
"<<" { return OP_SHL; }
">>" { return OP_SHR; }
"->" { return OP_DRF; }
[-+<=>|&^()*/%:!~,\.] { return *yytext; }
"[" { return *yytext; }
"]" { return *yytext; }
"0x"{HEXDIGIT}+ { sscanf(yytext, "%x", &yylval.integer); return tNUM; }
{DIGIT}+ { sscanf(yytext, "%d", &yylval.integer); return tNUM; }
<FORMAT_EXPECTED>"/"{DIGIT}+{FORMAT} { char * last;
yylval.integer = strtol( yytext+1, &last, NULL );
yylval.integer = (yylval.integer << 8) | *last;
return tFORMAT; }
<FORMAT_EXPECTED>"/"{FORMAT} { yylval.integer = (1 << 8) | yytext[1]; return tFORMAT; }
{STRING} { yylval.string = make_symbol(yytext); return tSTRING; }
<DEBUGSTR>[a-z+\-,]* { yylval.string = yytext; return tDEBUGSTR; }
/* set to special state when no process is loaded. */
if (!DEBUG_CurrProcess && YYSTATE == INITIAL) {BEGIN(NOPROCESS);}
<*>\n { BEGIN(INITIAL); syntax_error = 0; return tEOL; }
/* Indicates end of command. Reset state. */
"||" { return OP_LOR; }
"&&" { return OP_LAND; }
"==" { return OP_EQ; }
"!=" { return OP_NE; }
"<=" { return OP_LE; }
">=" { return OP_GE; }
"<<" { return OP_SHL; }
">>" { return OP_SHR; }
"->" { return OP_DRF; }
[-+<=>|&^()*/%:!~,\.] { return *yytext; }
"[" { return *yytext; }
"]" { return *yytext; }
"0x"{HEXDIGIT}+ { sscanf(yytext, "%x", &yylval.integer); return tNUM; }
{DIGIT}+ { sscanf(yytext, "%d", &yylval.integer); return tNUM; }
<FORMAT_EXPECTED>"/"{DIGIT}+{FORMAT} { char* last;
yylval.integer = strtol( yytext+1, &last, NULL ) << 8;
yylval.integer |= *last;
return tFORMAT; }
<FORMAT_EXPECTED>"/"{FORMAT} { yylval.integer = (1 << 8) | yytext[1]; return tFORMAT; }
{STRING} { yylval.string = DEBUG_MakeSymbol(yytext); return tSTRING; }
<ASTRING_EXPECTED>[^\n]+ { char* p = yytext; while (*p == ' ' || *p == '\t') p++;
yylval.string = DEBUG_MakeSymbol(p); return tSTRING; }
<INITIAL>info|inf|in { BEGIN(INFO_CMD); return tINFO; }
<INITIAL>up { BEGIN(NOCMD); return tUP; }
......@@ -93,9 +90,9 @@ STRING \"[^\n"]+\"
<INITIAL,INFO_CMD,DEL_CMD>display|displa|displ|disp { BEGIN(FORMAT_EXPECTED); return tDISPLAY; }
<INITIAL>undisplay|undispla|undispl|undisp|undis|undi|und { BEGIN(NOCMD); return tUNDISPLAY; }
<INITIAL>delete|delet|dele|del { BEGIN(DEL_CMD); return tDELETE; }
<INITIAL>quit|qui|qu|q { BEGIN(NOCMD); return tQUIT; }
<INITIAL,NOPROCESS>quit|qui|qu|q { BEGIN(NOCMD); return tQUIT; }
<INITIAL>set|se { BEGIN(NOCMD); return tSET; }
<INITIAL>walk|w { BEGIN(WALK_CMD); return tWALK; }
<INITIAL,NOPROCESS>walk|w { BEGIN(WALK_CMD); return tWALK; }
<INITIAL>x { BEGIN(FORMAT_EXPECTED); return tEXAM; }
<INITIAL>help|hel|he|"?" { BEGIN(HELP_CMD); return tHELP; }
......@@ -121,11 +118,12 @@ STRING \"[^\n"]+\"
<INITIAL,INFO_CMD,DEL_CMD>break|brea|bre|br|b { BEGIN(PATH_EXPECTED); return tBREAK; }
<INITIAL>watch|watc|wat { BEGIN(PATH_EXPECTED); return tWATCH; }
<INITIAL>whatis|whati|what { BEGIN(PATH_EXPECTED); return tWHATIS; }
<INITIAL,NOPROCESS>run|ru|r { BEGIN(ASTRING_EXPECTED); return tRUN;}
<NOPROCESS>attach|attac|atta|att { BEGIN(NOCMD); return tATTACH; }
<INFO_CMD>share|shar|sha { return tSHARE; }
<INFO_CMD>locals|local|loca|loc { return tLOCAL; }
<INFO_CMD,WALK_CMD>class|clas|cla { return tCLASS; }
<INFO_CMD,WALK_CMD>module|modul|modu|mod { return tMODULE; }
<INFO_CMD,WALK_CMD>module|modul|modu|mod { return tMODULE; }
<INFO_CMD,WALK_CMD>queue|queu|que { return tQUEUE; }
<INFO_CMD,WALK_CMD>process|proces|proce|proc { return tPROCESS; }
<INFO_CMD,WALK_CMD>threads|thread|threa|thre|thr|th { return tTHREAD; }
......@@ -138,7 +136,7 @@ STRING \"[^\n"]+\"
<HELP_CMD>info|inf|in { return tINFO; }
<INITIAL,SHOW_CMD>directories|directorie|directori|director|directo|direct|direc|direc|dir {
BEGIN(PATH_EXPECTED); return tDIR; }
BEGIN(PATH_EXPECTED); return tDIR; }
char { return tCHAR; }
short { return tSHORT; }
......@@ -152,20 +150,18 @@ struct { return tSTRUCT; }
union { return tUNION; }
enum { return tENUM; }
{IDENTIFIER} { yylval.string = make_symbol(yytext); return tIDENTIFIER; }
"$"{IDENTIFIER} { yylval.string = make_symbol(yytext+1); return tINTVAR; }
{IDENTIFIER} { yylval.string = DEBUG_MakeSymbol(yytext); return tIDENTIFIER; }
"$"{IDENTIFIER} { yylval.string = DEBUG_MakeSymbol(yytext+1); return tINTVAR; }
<PATH_EXPECTED>{PATHNAME} { yylval.string = make_symbol(yytext); return tPATH; }
[ \t]+ /* Eat up whitespace */
. { if (syntax_error == 0)
{
syntax_error ++; DEBUG_Printf(DBG_CHN_MESG, "Syntax Error\n");
}
}
<PATH_EXPECTED>{PATHNAME} { yylval.string = DEBUG_MakeSymbol(yytext); return tPATH; }
<*>[ \t]+ /* Eat up whitespace */
<NOPROCESS>. { BEGIN(ASTRING_EXPECTED); yyless(0); return tNOPROCESS;}
<*>. { if (syntax_error == 0) {
syntax_error++;
DEBUG_Printf(DBG_CHN_MESG, "Syntax Error (%s)\n", yytext); }
}
%%
#ifndef yywrap
......@@ -206,7 +202,7 @@ static int dbg_read(char * buf, int size)
for (;;)
{
flush_symbols();
DEBUG_FlushSymbols();
line = readline ("Wine-dbg>");
if (!line)
{
......@@ -226,7 +222,7 @@ static int dbg_read(char * buf, int size)
add_history( line );
strncpy( last_line, line, 255 );
last_line[255] = '\0';
}
}
free( line );
line = last_line;
......@@ -249,14 +245,15 @@ static int dbg_read(char * buf, int size)
static char *local_symbols[30];
static int next_symbol;
static char * make_symbol(char * symbol)
static char * DEBUG_MakeSymbol(char * symbol)
{
return local_symbols[next_symbol++] = DBG_strdup(symbol);
assert(0 <= next_symbol && next_symbol < (sizeof(local_symbols) / sizeof(local_symbols[0])));
return local_symbols[next_symbol++] = DBG_strdup(symbol);
}
void flush_symbols(void)
void DEBUG_FlushSymbols(void)
{
while(--next_symbol>= 0) DBG_free(local_symbols[next_symbol]);
while(--next_symbol >= 0) DBG_free(local_symbols[next_symbol]);
next_symbol = 0;
}
......
......@@ -112,7 +112,6 @@ enum exec_mode
* and set breakpoint there - not at the
* instr just after the call.
*/
EXEC_KILL /* terminate debugging session */
};
#define DBG_BREAK 0
......@@ -224,24 +223,16 @@ typedef struct {
#define OFFSET_OF(__c,__f) ((int)(((char*)&(((__c*)0)->__f))-((char*)0)))
#ifdef __i386__
# define GET_IP(context) ((DWORD)(context)->Eip)
#endif
#ifdef __sparc__
# define GET_IP(context) ((DWORD)(context)->pc)
#endif
#if !defined(GET_IP)
# error You must define GET_IP for this CPU
#endif
/* from winelib.so */
extern void DEBUG_ExternalDebugger(void);
/* debugger/break.c */
extern void DEBUG_SetBreakpoints( BOOL set );
extern void DEBUG_AddBreakpoint( const DBG_VALUE *addr, BOOL (*func)(void) );
extern void DEBUG_AddBreakpointFromId( const char *name, int lineno );
extern void DEBUG_AddBreakpointFromLineno( int lineno );
extern void DEBUG_AddWatchpoint( const DBG_VALUE *addr, int is_write );
extern void DEBUG_AddWatchpointFromId( const char *name, int lineno );
extern void DEBUG_DelBreakpoint( int num );
extern void DEBUG_EnableBreakpoint( int num, BOOL enable );
extern void DEBUG_InfoBreakpoints(void);
......@@ -250,17 +241,17 @@ extern BOOL DEBUG_ShouldContinue( DWORD code, enum exec_mode mode, int * count )
extern void DEBUG_SuspendExecution( void );
extern enum exec_mode DEBUG_RestartExecution( enum exec_mode mode, int count );
extern BOOL DEBUG_IsFctReturn(void);
extern int DEBUG_AddBPCondition(int bpnum, struct expr * exp);
extern int DEBUG_AddBPCondition(int bpnum, struct expr * exp);
/* debugger/db_disasm.c */
extern void DEBUG_Disasm( DBG_ADDR *addr, int display );
/* debugger/dbg.y */
extern BOOL DEBUG_Main( BOOL is_debug, BOOL force, DWORD code );
extern BOOL DEBUG_Parser(void);
extern void DEBUG_Exit( DWORD );
/* debugger/debug.l */
extern void flush_symbols(void);
extern void DEBUG_FlushSymbols(void);
/* debugger/display.c */
extern int DEBUG_DoDisplay(void);
......@@ -294,9 +285,6 @@ extern struct expr * DEBUG_CloneExpr(const struct expr * exp);
extern int DEBUG_FreeExpr(struct expr * exp);
extern int DEBUG_DisplayExpr(const struct expr * exp);
/* debugger/external.c */
extern void DEBUG_ExternalDebugger(void);
/* debugger/hash.c */
extern struct name_hash * DEBUG_AddSymbol( const char *name,
const DBG_VALUE *addr,
......@@ -378,6 +366,7 @@ extern DBG_MODULE* DEBUG_AddModule(const char* name, int type,
void* mod_addr, HMODULE hmod);
extern DBG_MODULE* DEBUG_FindModuleByName(const char* name, int type);
extern DBG_MODULE* DEBUG_FindModuleByHandle(HANDLE handle, int type);
extern DBG_MODULE* DEBUG_GetProcessMainModule(DBG_PROCESS* process);
extern DBG_MODULE* DEBUG_RegisterPEModule(HMODULE, u_long load_addr, const char* name);
extern DBG_MODULE* DEBUG_RegisterELFModule(u_long load_addr, const char* name);
extern void DEBUG_InfoShare(void);
......@@ -456,8 +445,11 @@ extern int DEBUG_Printf(int chn, const char* format, ...) __attribute__((format
extern int DEBUG_Printf(int chn, const char* format, ...);
#endif
extern DBG_INTVAR* DEBUG_GetIntVar(const char*);
extern BOOL DEBUG_Attach(DWORD pid, BOOL cofe);
extern void DEBUG_Run(const char* args);
extern int curr_frame;
/* Choose your allocator! */
/* Choose your allocator! */
#if 1
/* this one is libc's fast one */
extern void* DEBUG_XMalloc(size_t size);
......
......@@ -9,6 +9,7 @@
INTERNAL_VAR(BreakAllThreadsStartup, FALSE, NULL, DEBUG_TypeIntConst)
INTERNAL_VAR(BreakOnCritSectTimeOut, FALSE, NULL, DEBUG_TypeIntConst)
INTERNAL_VAR(BreakOnAttach, FALSE, NULL, DEBUG_TypeIntConst)
INTERNAL_VAR(BreakOnFirstChance, TRUE, NULL, DEBUG_TypeIntConst)
/* output handling */
INTERNAL_VAR(ConChannelMask, DBG_CHN_MESG, NULL, DEBUG_TypeIntConst)
......
......@@ -117,9 +117,11 @@ void DEBUG_GetCurrentAddress( DBG_ADDR *addr )
if (!DEBUG_FixSegment( addr ) && DEBUG_IsSelectorSystem(addr->seg))
addr->seg = 0;
addr->off = DEBUG_context.Eip;
#elif defined(__sparc__)
addr->seg = 0;
addr->off = DEBUG_context.pc;
#else
addr->seg = 0;
addr->off = GET_IP( &DEBUG_context );
# error You must define GET_IP for this CPU
#endif
}
......
......@@ -92,6 +92,21 @@ DBG_MODULE* DEBUG_FindModuleByHandle(HANDLE handle, int type)
}
/***********************************************************************
* DEBUG_GetProcessMainModule
*/
DBG_MODULE* DEBUG_GetProcessMainModule(DBG_PROCESS* process)
{
DBG_MODULE* wmod;
if (!process) return NULL;
/* main module is the first to be loaded on a given process, so it's the last on
* the list */
for (wmod = process->modules; wmod && wmod->next; wmod = wmod->next);
return wmod;
}
/***********************************************************************
* DEBUG_RegisterELFModule
*
* ELF modules are also entered into the list - this is so that we
......@@ -139,6 +154,7 @@ DBG_MODULE* DEBUG_RegisterNEModule(HMODULE hModule, void* load_addr, const char
return wmod;
}
#if 0
/***********************************************************************
* DEBUG_GetEP16
*
......@@ -230,6 +246,7 @@ static void DEBUG_LoadModule16(HMODULE hModule, NE_MODULE* module, char* moduleA
}
GlobalUnlock16(module->nrname_handle);
}
#endif
/***********************************************************************
* DEBUG_LoadModule32
......@@ -355,10 +372,14 @@ void DEBUG_LoadModule32(const char* name, HANDLE hFile, DWORD base)
*/
int DEBUG_LoadEntryPoints(const char* pfx)
{
int first = 0;
/* FIXME: with address space separation in space, this is plain wrong
* it requires the 16 bit WOW debugging interface...
*/
#if 0
MODULEENTRY entry;
NE_MODULE module;
void* moduleAddr;
int first = 0;
int rowcount = 0;
int len;
......@@ -390,6 +411,7 @@ int DEBUG_LoadEntryPoints(const char* pfx)
DEBUG_LoadModule16(entry.hModule, &module, moduleAddr, entry.szModule);
} while (ModuleNext16(&entry));
#endif
if (first) DEBUG_Printf(DBG_CHN_MESG, "\n");
return first;
......
......@@ -121,6 +121,9 @@ void DEBUG_InfoRegisters(void)
*/
BOOL DEBUG_ValidateRegisters(void)
{
DBG_ADDR addr;
char ch;
#ifdef __i386__
if (DEBUG_context.EFlags & V86_FLAG) return TRUE;
......@@ -157,9 +160,13 @@ BOOL DEBUG_ValidateRegisters(void)
(WORD)DEBUG_context.SegSs );
return FALSE;
}
return TRUE;
#undef CHECK_SEG
#else
return TRUE;
/* to be written */
#endif
/* check if PC is correct */
DEBUG_GetCurrentAddress(&addr);
return DEBUG_READ_MEM_VERBOSE((void*)DEBUG_ToLinear(&addr), &ch, 1);
}
......@@ -30,7 +30,6 @@ struct bt_info
static int nframe;
static struct bt_info * frames = NULL;
int curr_frame;
typedef struct
{
......
......@@ -897,7 +897,11 @@ DEBUG_Print( const DBG_VALUE *value, int count, char format, int level )
}
}
break;
case DT_FUNC:
DEBUG_Printf(DBG_CHN_MESG, "Function at ???\n");
break;
default:
DEBUG_Printf(DBG_CHN_MESG, "Unknown type (%d)\n", value->type->type);
assert(FALSE);
break;
}
......
......@@ -10,9 +10,7 @@ point of view and processes/threads from a Windows point of view.
Each Windows' thread is implemented as a Unix process (under Linux
using the clone syscall), meaning that all threads of a same Windows'
process share the same (unix) address space (currently, one of wine
limitation is that several windows processes run in the same (unix)
address space. it's being worked on).
process share the same (unix) address space.
In the following:
+ W-process means a process in Windows' terminology
......@@ -69,22 +67,13 @@ winedbg "hl.exe -windowed"
II.2 Attaching
--------------
WineDbg can also launched without any command line argument:
- if a wineserver is running, WineDbg lists the running W-processes
(and their wpid:s), and let you pick up the wpid of the W-process you
want to debug.
- WineDbg is started without any attached process. You can get a list
of running W-processes (and their wpid:s) using 'walk process'
command, and then, with the attach command, pick up the wpid of the
W-process you want to debug.
This is (for now) a neat feature for the following reasons:
* debug an already started application
+ launching WineDbg this way let WineDbg and the debugged process run
in a *separate address space* (launching with 'winedbg myprog.exe'
doesn't), and most of the deadlocks seen when running the debugger
disappear (because there is no crit sect shared by both
processes). That's the best (but far from being acceptable) current
way to debug an application
This last advantage shall disappear when address space separation is
in place. At that time, only the ability to debug an already started
process will remain.
II.3 On exception
-----------------
......@@ -188,6 +177,9 @@ modifications to those options are automatically saved when WineDbg
terminates).
Here's the list of all options:
III.2.1 Controling when the debugger is entered
BreakAllThreadsStartup set to TRUE if at all threads start-up the
debugger stops
set to FALSE if only at the first thread
......@@ -204,11 +196,30 @@ BreakOnAttach, set to TRUE if when WineDbg attaches to an
context of an exception event (the next event
which is the exception event is of course
relevant), that option is likely to be FALSE.
BreakOnFirstChance an exception can generate two debug events.
The first one is passed to the debugger (known
as a first chance) just after the
exception. The debugger can then decides
either to resume execution (see winedbg's cont
command) or pass the exception up to the
exception handler chain in the program (if it
exists) (winedbg implements this thru the pass
command). If none of the exception handlers
takes care of the exception, the exception
event is sent again to the debugger (known as
last chance exception). You cannot pass on a
last exception. When the BreakOnFirstChance
exception is TRUE, then winedbg is entered for
both first and last chance execptions (to
FALSE, it's only entered for last chance exceptions).
III.2.1 Output handling
ConChannelMask mask of active debugger output channels on
console
StdChannelMask mask of active debugger output channels on
stderr
UseXTerm set to TRUE if the debugger uses its own xterm
window for console input/output
set to FALSE is the debugger uses the current
......@@ -241,6 +252,17 @@ example >& shell redirect command), you'll get in that file both
outputs. It may be interesting to look in the relay trace for specific
values which the process segv:ed on.
III.2.3 Context information
ThreadId ID of the W-thread currently examined by the
debugger
ProcessId ID of the W-thread currently examined by the
debugger
<registers> All CPU registers are also available
The ThreadId and ProcessId variables can be handy to set conditional
breakpoints on a given thread or process.
IV WineDbg commands
===================
......@@ -249,6 +271,9 @@ IV.1 Misc
abort aborts the debugger
quit exits the debugger
attach N attach to a W-process (N is its ID). IDs can be
obtained thru walk process command
help prints some help on the commands
help info prints some help on info commands
......@@ -417,8 +442,7 @@ V.2 Using other Windows debuggers
You can use any Windows' debugging API compliant debugger with
Wine. Some reports have been made of success with VisualStudio
debugger (in remote mode, only the hub runs in Wine).
GoVest fully runs in Wine, but is plagued with the same address space
issues as WineDbg as stated in II.2
GoVest fully runs in Wine.
V.3 Main differences between winedbg and regular Unix debuggers
---------------------------------------------------------------
......@@ -444,6 +468,5 @@ VI Limitations
16 bit processes are not supported (but calls to 16 bit code in 32 bit
applications is).
Lack of address space separation exhibits some deadlocks.
Last updated: 5/21/2000 by ericP
Last updated: 6/14/2000 by ericP
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment