Moved Load/FreeLibrary16 definition to winbase16.h.
[wine.git] / debugger / winedbg.c
blob8e9a202d452cf21f3d6b986d39dc99819b0de969
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
3 /* Wine internal debugger
4 * Interface to Windows debugger API
5 * Eric Pouech (c) 2000
6 */
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include "debugger.h"
13 #include "thread.h"
14 #include "process.h"
15 #include "wincon.h"
16 #include "wingdi.h"
17 #include "winuser.h"
19 #include "winreg.h"
21 #ifdef DBG_need_heap
22 HANDLE dbg_heap = 0;
23 #endif
25 DBG_PROCESS* DEBUG_CurrProcess = NULL;
26 DBG_THREAD* DEBUG_CurrThread = NULL;
27 CONTEXT DEBUG_context;
29 static DBG_PROCESS* proc = NULL;
30 DBG_INTVAR DEBUG_IntVars[DBG_IV_LAST];
32 void DEBUG_Output(int chn, const char* buffer, int len)
34 if (DBG_IVAR(ConChannelMask) & chn)
35 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, len, NULL, NULL);
36 if (DBG_IVAR(StdChannelMask) & chn)
37 fwrite(buffer, len, 1, stderr);
40 int DEBUG_Printf(int chn, const char* format, ...)
42 char buf[1024];
43 va_list valist;
44 int len;
46 va_start(valist, format);
47 len = wvsnprintf(buf, sizeof(buf), format, valist);
48 va_end(valist);
50 if (len <= -1) {
51 len = sizeof(buf) - 1;
52 buf[len] = 0;
53 buf[len - 1] = buf[len - 2] = buf[len - 3] = '.';
55 DEBUG_Output(chn, buf, len);
56 return len;
59 static BOOL DEBUG_IntVarsRW(int read)
61 HKEY hkey;
62 DWORD type = REG_DWORD;
63 DWORD val;
64 DWORD count = sizeof(val);
65 int i;
66 DBG_INTVAR* div = DEBUG_IntVars;
68 if (read) {
69 /* initializes internal vars table */
70 #define INTERNAL_VAR(_var,_val,_ref,_typ) \
71 div->val = _val; div->name = #_var; div->pval = _ref; \
72 div->type = _typ; div++;
73 #include "intvar.h"
74 #undef INTERNAL_VAR
77 if (RegOpenKey(HKEY_CURRENT_USER, "Software\\Wine\\WineDbg", &hkey) &&
78 RegCreateKeyA(HKEY_CURRENT_USER, "Software\\Wine\\WineDbg", &hkey)) {
79 /* since the IVars are not yet setup, DEBUG_Printf doesn't work,
80 * so don't use it */
81 fprintf(stderr, "Cannot create WineDbg key in registry\n");
82 return FALSE;
85 for (i = 0; i < DBG_IV_LAST; i++) {
86 if (read) {
87 if (!DEBUG_IntVars[i].pval) {
88 if (!RegQueryValueEx(hkey, DEBUG_IntVars[i].name, 0,
89 &type, (LPSTR)&val, &count))
90 DEBUG_IntVars[i].val = val;
91 DEBUG_IntVars[i].pval = &DEBUG_IntVars[i].val;
92 } else {
93 *DEBUG_IntVars[i].pval = 0;
95 } else {
96 /* FIXME: type should be infered from basic type -if any- of intvar */
97 if (DEBUG_IntVars[i].pval == &DEBUG_IntVars[i].val)
98 RegSetValueEx(hkey, DEBUG_IntVars[i].name, 0,
99 type, (LPCVOID)DEBUG_IntVars[i].pval, count);
102 RegCloseKey(hkey);
103 return TRUE;
106 DBG_INTVAR* DEBUG_GetIntVar(const char* name)
108 int i;
110 for (i = 0; i < DBG_IV_LAST; i++) {
111 if (!strcmp(DEBUG_IntVars[i].name, name))
112 return &DEBUG_IntVars[i];
114 return NULL;
117 static WINE_EXCEPTION_FILTER(wine_dbg)
119 DEBUG_Printf(DBG_CHN_MESG, "\nwine_dbg: Exception (%lx) inside debugger, continuing...\n", GetExceptionCode());
120 DEBUG_ExternalDebugger();
121 return EXCEPTION_EXECUTE_HANDLER;
124 static DBG_PROCESS* DEBUG_GetProcess(DWORD pid)
126 DBG_PROCESS* p;
128 for (p = proc; p; p = p->next)
129 if (p->pid == pid) break;
130 return p;
133 static DBG_PROCESS* DEBUG_AddProcess(DWORD pid, HANDLE h)
135 DBG_PROCESS* p = DBG_alloc(sizeof(DBG_PROCESS));
136 if (!p)
137 return NULL;
138 p->handle = h;
139 p->pid = pid;
140 p->threads = NULL;
141 p->num_threads = 0;
142 p->continue_on_first_exception = FALSE;
143 p->modules = NULL;
144 p->next_index = 0;
145 p->dbg_hdr_addr = 0;
147 p->next = proc;
148 p->prev = NULL;
149 if (proc) proc->prev = p;
150 proc = p;
151 return p;
154 static void DEBUG_DelThread(DBG_THREAD* p);
156 static void DEBUG_DelProcess(DBG_PROCESS* p)
158 if (p->threads != NULL) {
159 DEBUG_Printf(DBG_CHN_ERR, "Shouldn't happen\n");
160 while (p->threads) DEBUG_DelThread(p->threads);
162 if (p->prev) p->prev->next = p->next;
163 if (p->next) p->next->prev = p->prev;
164 if (p == proc) proc = p->next;
165 DBG_free(p);
168 static void DEBUG_InitCurrProcess(void)
172 static BOOL DEBUG_ProcessGetString(char* buffer, int size, HANDLE hp, LPSTR addr)
174 DWORD sz;
175 *(WCHAR*)buffer = 0;
176 return (addr && ReadProcessMemory(hp, addr, buffer, size, &sz));
179 static BOOL DEBUG_ProcessGetStringIndirect(char* buffer, int size, HANDLE hp, LPVOID addr)
181 LPVOID ad;
182 DWORD sz;
184 if ( addr
185 && ReadProcessMemory(hp, addr, &ad, sizeof(ad), &sz)
186 && sz == sizeof(ad)
187 && ad
188 && ReadProcessMemory(hp, ad, buffer, size, &sz))
189 return TRUE;
190 *(WCHAR*)buffer = 0;
191 return FALSE;
194 static DBG_THREAD* DEBUG_GetThread(DBG_PROCESS* p, DWORD tid)
196 DBG_THREAD* t;
198 for (t = p->threads; t; t = t->next)
199 if (t->tid == tid) break;
200 return t;
203 static DBG_THREAD* DEBUG_AddThread(DBG_PROCESS* p, DWORD tid,
204 HANDLE h, LPVOID start, LPVOID teb)
206 DBG_THREAD* t = DBG_alloc(sizeof(DBG_THREAD));
207 if (!t)
208 return NULL;
210 t->handle = h;
211 t->tid = tid;
212 t->start = start;
213 t->teb = teb;
214 t->process = p;
215 t->wait_for_first_exception = 0;
216 t->dbg_exec_mode = EXEC_CONT;
217 t->dbg_exec_count = 0;
219 p->num_threads++;
220 t->next = p->threads;
221 t->prev = NULL;
222 if (p->threads) p->threads->prev = t;
223 p->threads = t;
225 return t;
228 static void DEBUG_InitCurrThread(void)
230 if (DEBUG_CurrThread->start) {
231 if (DEBUG_CurrThread->process->num_threads == 1 ||
232 DBG_IVAR(BreakAllThreadsStartup)) {
233 DBG_VALUE value;
235 DEBUG_SetBreakpoints(FALSE);
236 value.type = NULL;
237 value.cookie = DV_TARGET;
238 value.addr.seg = 0;
239 value.addr.off = (DWORD)DEBUG_CurrThread->start;
240 DEBUG_AddBreakpoint(&value, NULL);
241 DEBUG_SetBreakpoints(TRUE);
243 } else {
244 DEBUG_CurrThread->wait_for_first_exception = 1;
248 static void DEBUG_DelThread(DBG_THREAD* t)
250 if (t->prev) t->prev->next = t->next;
251 if (t->next) t->next->prev = t->prev;
252 if (t == t->process->threads) t->process->threads = t->next;
253 t->process->num_threads--;
254 DBG_free(t);
257 static BOOL DEBUG_HandleException( EXCEPTION_RECORD *rec, BOOL first_chance, BOOL force )
259 BOOL is_debug = FALSE;
260 BOOL ret;
262 /* FIXME: need for a configuration var ? */
263 /* pass to app first ??? */
264 /* if (first_chance && !force) return 0; */
266 switch (rec->ExceptionCode)
268 case EXCEPTION_BREAKPOINT:
269 case EXCEPTION_SINGLE_STEP:
270 is_debug = TRUE;
271 break;
274 if (!is_debug)
276 /* print some infos */
277 DEBUG_Printf( DBG_CHN_MESG, "%s: ",
278 first_chance ? "First chance exception" : "Unhandled exception" );
279 switch (rec->ExceptionCode)
281 case EXCEPTION_INT_DIVIDE_BY_ZERO:
282 DEBUG_Printf( DBG_CHN_MESG, "divide by zero" );
283 break;
284 case EXCEPTION_INT_OVERFLOW:
285 DEBUG_Printf( DBG_CHN_MESG, "overflow" );
286 break;
287 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
288 DEBUG_Printf( DBG_CHN_MESG, "array bounds " );
289 break;
290 case EXCEPTION_ILLEGAL_INSTRUCTION:
291 DEBUG_Printf( DBG_CHN_MESG, "illegal instruction" );
292 break;
293 case EXCEPTION_STACK_OVERFLOW:
294 DEBUG_Printf( DBG_CHN_MESG, "stack overflow" );
295 break;
296 case EXCEPTION_PRIV_INSTRUCTION:
297 DEBUG_Printf( DBG_CHN_MESG, "priviledged instruction" );
298 break;
299 case EXCEPTION_ACCESS_VIOLATION:
300 if (rec->NumberParameters == 2)
301 DEBUG_Printf( DBG_CHN_MESG, "page fault on %s access to 0x%08lx",
302 rec->ExceptionInformation[0] ? "write" : "read",
303 rec->ExceptionInformation[1] );
304 else
305 DEBUG_Printf( DBG_CHN_MESG, "page fault" );
306 break;
307 case EXCEPTION_DATATYPE_MISALIGNMENT:
308 DEBUG_Printf( DBG_CHN_MESG, "Alignment" );
309 break;
310 case CONTROL_C_EXIT:
311 DEBUG_Printf( DBG_CHN_MESG, "^C" );
312 break;
313 case EXCEPTION_CRITICAL_SECTION_WAIT:
314 DEBUG_Printf( DBG_CHN_MESG, "critical section %08lx wait failed",
315 rec->ExceptionInformation[0] );
316 if (!DBG_IVAR(BreakOnCritSectTimeOut))
317 return DBG_CONTINUE;
318 break;
319 default:
320 DEBUG_Printf( DBG_CHN_MESG, "%08lx", rec->ExceptionCode );
321 break;
323 DEBUG_Printf(DBG_CHN_MESG, "\n");
326 DEBUG_Printf(DBG_CHN_TRACE,
327 "Entering debugger PC=%lx EFL=%08lx mode=%d count=%d\n",
328 DEBUG_context.Eip, DEBUG_context.EFlags,
329 DEBUG_CurrThread->dbg_exec_mode, DEBUG_CurrThread->dbg_exec_count);
331 ret = DEBUG_Main( is_debug, force, rec->ExceptionCode );
333 DEBUG_Printf(DBG_CHN_TRACE,
334 "Exiting debugger PC=%lx EFL=%08lx mode=%d count=%d\n",
335 DEBUG_context.Eip, DEBUG_context.EFlags,
336 DEBUG_CurrThread->dbg_exec_mode, DEBUG_CurrThread->dbg_exec_count);
338 return ret;
341 static BOOL DEBUG_HandleDebugEvent(DEBUG_EVENT* de, LPDWORD cont)
343 char buffer[256];
344 BOOL ret;
346 __TRY {
347 ret = TRUE;
348 *cont = 0L;
350 if ((DEBUG_CurrProcess = DEBUG_GetProcess(de->dwProcessId)) != NULL)
351 DEBUG_CurrThread = DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId);
352 else
353 DEBUG_CurrThread = NULL;
355 switch (de->dwDebugEventCode) {
356 case EXCEPTION_DEBUG_EVENT:
357 if (!DEBUG_CurrThread) {
358 DEBUG_Printf(DBG_CHN_ERR, "%08lx:%08lx: not a registered process or thread (perhaps a 16 bit one ?)\n",
359 de->dwProcessId, de->dwThreadId);
360 break;
363 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exception code=%08lx\n",
364 de->dwProcessId, de->dwThreadId,
365 de->u.Exception.ExceptionRecord.ExceptionCode);
367 if (DEBUG_CurrProcess->continue_on_first_exception) {
368 DEBUG_CurrProcess->continue_on_first_exception = FALSE;
369 if (!DBG_IVAR(BreakOnAttach)) {
370 *cont = DBG_CONTINUE;
371 break;
375 DEBUG_context.ContextFlags = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS|CONTEXT_DEBUG_REGISTERS;
376 if (!GetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context)) {
377 DEBUG_Printf(DBG_CHN_WARN, "Can't get thread's context\n");
378 break;
381 *cont = DEBUG_HandleException(&de->u.Exception.ExceptionRecord,
382 de->u.Exception.dwFirstChance,
383 DEBUG_CurrThread->wait_for_first_exception);
384 if (DEBUG_CurrThread->dbg_exec_mode == EXEC_KILL) {
385 ret = FALSE;
386 } else {
387 DEBUG_CurrThread->wait_for_first_exception = 0;
388 SetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context);
390 break;
392 case CREATE_THREAD_DEBUG_EVENT:
393 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: create thread D @%p\n", de->dwProcessId, de->dwThreadId,
394 de->u.CreateThread.lpStartAddress);
396 if (DEBUG_CurrProcess == NULL) {
397 DEBUG_Printf(DBG_CHN_ERR, "Unknown process\n");
398 break;
400 if (DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId) != NULL) {
401 DEBUG_Printf(DBG_CHN_TRACE, "Thread already listed, skipping\n");
402 break;
405 DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess,
406 de->dwThreadId,
407 de->u.CreateThread.hThread,
408 de->u.CreateThread.lpStartAddress,
409 de->u.CreateThread.lpThreadLocalBase);
410 if (!DEBUG_CurrThread) {
411 DEBUG_Printf(DBG_CHN_ERR, "Couldn't create thread\n");
412 break;
414 DEBUG_InitCurrThread();
415 break;
417 case CREATE_PROCESS_DEBUG_EVENT:
418 DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer),
419 de->u.CreateProcessInfo.hProcess,
420 de->u.CreateProcessInfo.lpImageName);
422 /* FIXME unicode ? de->u.CreateProcessInfo.fUnicode */
423 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: create process %s @%p (%ld<%ld>)\n",
424 de->dwProcessId, de->dwThreadId,
425 buffer,
426 de->u.CreateProcessInfo.lpStartAddress,
427 de->u.CreateProcessInfo.dwDebugInfoFileOffset,
428 de->u.CreateProcessInfo.nDebugInfoSize);
430 if ((DEBUG_CurrProcess = DEBUG_GetProcess(de->dwProcessId)) != NULL) {
431 if (DEBUG_CurrProcess->handle) {
432 DEBUG_Printf(DBG_CHN_ERR, "Skipping already defined process\n");
433 break;
435 DEBUG_CurrProcess->handle = de->u.CreateProcessInfo.hProcess;
436 } else {
437 DEBUG_CurrProcess = DEBUG_AddProcess(de->dwProcessId,
438 de->u.CreateProcessInfo.hProcess);
439 if (DEBUG_CurrProcess == NULL) {
440 DEBUG_Printf(DBG_CHN_ERR, "Unknown process\n");
441 break;
445 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: create thread I @%p\n",
446 de->dwProcessId, de->dwThreadId,
447 de->u.CreateProcessInfo.lpStartAddress);
449 DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess,
450 de->dwThreadId,
451 de->u.CreateProcessInfo.hThread,
452 de->u.CreateProcessInfo.lpStartAddress,
453 de->u.CreateProcessInfo.lpThreadLocalBase);
454 if (!DEBUG_CurrThread) {
455 DEBUG_Printf(DBG_CHN_ERR, "Couldn't create thread\n");
456 break;
459 DEBUG_InitCurrProcess();
460 DEBUG_InitCurrThread();
461 /* so far, process name is not set */
462 DEBUG_LoadModule32("<Debugged process>", de->u.CreateProcessInfo.hFile,
463 (DWORD)de->u.CreateProcessInfo.lpBaseOfImage);
464 break;
466 case EXIT_THREAD_DEBUG_EVENT:
467 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exit thread (%ld)\n",
468 de->dwProcessId, de->dwThreadId, de->u.ExitThread.dwExitCode);
470 if (DEBUG_CurrThread == NULL) {
471 DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
472 break;
474 /* FIXME: remove break point set on thread startup */
475 DEBUG_DelThread(DEBUG_CurrThread);
476 break;
478 case EXIT_PROCESS_DEBUG_EVENT:
479 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exit process (%ld)\n",
480 de->dwProcessId, de->dwThreadId, de->u.ExitProcess.dwExitCode);
482 if (DEBUG_CurrProcess == NULL) {
483 DEBUG_Printf(DBG_CHN_ERR, "Unknown process\n");
484 break;
486 /* just in case */
487 DEBUG_SetBreakpoints(FALSE);
488 /* kill last thread */
489 DEBUG_DelThread(DEBUG_CurrProcess->threads);
490 DEBUG_DelProcess(DEBUG_CurrProcess);
491 ret = FALSE;
492 break;
494 case LOAD_DLL_DEBUG_EVENT:
495 if (DEBUG_CurrThread == NULL) {
496 DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
497 break;
499 DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer),
500 DEBUG_CurrThread->process->handle,
501 de->u.LoadDll.lpImageName);
503 /* FIXME unicode: de->u.LoadDll.fUnicode */
504 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: loads DLL %s @%p (%ld<%ld>)\n",
505 de->dwProcessId, de->dwThreadId,
506 buffer, de->u.LoadDll.lpBaseOfDll,
507 de->u.LoadDll.dwDebugInfoFileOffset,
508 de->u.LoadDll.nDebugInfoSize);
509 CharUpper(buffer);
510 DEBUG_LoadModule32(buffer, de->u.LoadDll.hFile, (DWORD)de->u.LoadDll.lpBaseOfDll);
511 break;
513 case UNLOAD_DLL_DEBUG_EVENT:
514 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: unload DLL @%p\n", de->dwProcessId, de->dwThreadId,
515 de->u.UnloadDll.lpBaseOfDll);
516 break;
518 case OUTPUT_DEBUG_STRING_EVENT:
519 if (DEBUG_CurrThread == NULL) {
520 DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
521 break;
524 DEBUG_ProcessGetString(buffer, sizeof(buffer),
525 DEBUG_CurrThread->process->handle,
526 de->u.DebugString.lpDebugStringData);
528 /* fixme unicode de->u.DebugString.fUnicode ? */
529 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: output debug string (%s)\n",
530 de->dwProcessId, de->dwThreadId, buffer);
531 break;
533 case RIP_EVENT:
534 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: rip error=%ld type=%ld\n",
535 de->dwProcessId, de->dwThreadId, de->u.RipInfo.dwError,
536 de->u.RipInfo.dwType);
537 break;
539 default:
540 DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: unknown event (%ld)\n",
541 de->dwProcessId, de->dwThreadId, de->dwDebugEventCode);
544 } __EXCEPT(wine_dbg) {
545 *cont = 0;
546 ret = TRUE;
548 __ENDTRY;
550 return ret;
553 static DWORD DEBUG_MainLoop(DWORD pid)
555 DEBUG_EVENT de;
556 DWORD cont;
557 BOOL ret = TRUE;
559 DEBUG_Printf(DBG_CHN_MESG, " on pid %ld\n", pid);
561 while (ret && WaitForDebugEvent(&de, INFINITE)) {
562 ret = DEBUG_HandleDebugEvent(&de, &cont);
563 ContinueDebugEvent(de.dwProcessId, de.dwThreadId, cont);
566 DEBUG_Printf(DBG_CHN_MESG, "WineDbg terminated on pid %ld\n", pid);
568 return 0;
571 int DEBUG_main(int argc, char** argv)
573 DWORD pid = 0, retv = 0;
575 #ifdef DBG_need_heap
576 /* Initialize the debugger heap. */
577 dbg_heap = HeapCreate(HEAP_NO_SERIALIZE, 0x1000, 0x8000000); /* 128MB */
578 #endif
580 /* Initialize the type handling stuff. */
581 DEBUG_InitTypes();
582 DEBUG_InitCVDataTypes();
584 /* Initialize internal vars (types must be initialized before) */
585 if (!DEBUG_IntVarsRW(TRUE)) return -1;
587 /* keep it as a guiexe for now, so that Wine won't touch the Unix stdin,
588 * stdout and stderr streams
590 if (DBG_IVAR(UseXTerm)) {
591 COORD pos;
593 /* This is a hack: it forces creation of an xterm, not done by default */
594 pos.X = 0; pos.Y = 1;
595 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
598 DEBUG_Printf(DBG_CHN_MESG, "Starting WineDbg... ");
599 if (argc == 3) {
600 HANDLE hEvent;
602 if ((pid = atoi(argv[1])) != 0 && (hEvent = atoi(argv[2])) != 0) {
603 if (!(DEBUG_CurrProcess = DEBUG_AddProcess(pid, 0))) goto leave;
604 DEBUG_CurrProcess->continue_on_first_exception = TRUE;
606 if (!DebugActiveProcess(pid)) {
607 DEBUG_Printf(DBG_CHN_ERR, "Can't attach process %ld: %ld\n",
608 pid, GetLastError());
609 SetEvent(hEvent);
610 goto leave;
612 SetEvent(hEvent);
613 } else {
614 pid = 0;
618 if (pid == 0) {
619 PROCESS_INFORMATION info;
620 STARTUPINFOA startup;
622 memset(&startup, 0, sizeof(startup));
623 startup.cb = sizeof(startup);
624 startup.dwFlags = STARTF_USESHOWWINDOW;
625 startup.wShowWindow = SW_SHOWNORMAL;
627 if (!CreateProcess(NULL, argv[1], NULL, NULL,
628 FALSE, DEBUG_PROCESS, NULL, NULL, &startup, &info)) {
629 DEBUG_Printf(DBG_CHN_MESG, "Couldn't start process '%s'\n", argv[1]);
630 goto leave;
632 pid = info.dwProcessId;
635 if (pid) retv = DEBUG_MainLoop(pid);
636 leave:
637 /* saves modified variables */
638 DEBUG_IntVarsRW(FALSE);
640 return retv;