/* wince-stub.c -- debugging stub for a Windows CE device Copyright 1999, 2000 Free Software Foundation, Inc. Contributed by Cygnus Solutions, A Red Hat Company. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without eve nthe implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* by Christopher Faylor (cgf@cygnus.com) */ #include #include #include #include "wince-stub.h" #define MALLOC(n) (void *) LocalAlloc (LMEM_MOVEABLE | LMEM_ZEROINIT, (UINT)(n)) #define REALLOC(s, n) (void *) LocalReAlloc ((HLOCAL)(s), (UINT)(n), LMEM_MOVEABLE) #define FREE(s) LocalFree ((HLOCAL)(s)) static int skip_next_id = 0; /* Don't read next API code from socket */ /* v-style interface for handling varying argument list error messages. Displays the error message in a dialog box and exits when user clicks on OK. */ static void vstub_error (LPCWSTR fmt, va_list args) { WCHAR buf[4096]; wvsprintfW (buf, fmt, args); MessageBoxW (NULL, buf, L"GDB", MB_ICONERROR); WSACleanup (); ExitThread (1); } /* The standard way to display an error message and exit. */ static void stub_error (LPCWSTR fmt, ...) { va_list args; va_start (args, fmt); vstub_error (fmt, args); } /* Allocate a limited pool of memory, reallocating over unused buffers. This assumes that there will never be more than four "buffers" required which, so far, is a safe assumption. */ static LPVOID mempool (unsigned int len) { static int outn = -1; static LPWSTR outs[4] = {NULL, NULL, NULL, NULL}; if (++outn >= (sizeof (outs) / sizeof (outs[0]))) outn = 0; /* Allocate space for the converted string, reusing any previously allocated space, if applicable. */ if (outs[outn]) FREE (outs[outn]); outs[outn] = (LPWSTR) MALLOC (len); return outs[outn]; } /* Standard "oh well" can't communicate error. Someday this might attempt synchronization. */ static void attempt_resync (LPCWSTR huh, int s) { stub_error (L"lost synchronization with host attempting %s. Error %d", huh, WSAGetLastError ()); } /* Read arbitrary stuff from a socket. */ static int sockread (LPCWSTR huh, int s, void *str, size_t n) { for (;;) { if (recv (s, str, n, 0) == (int) n) return n; attempt_resync (huh, s); } } /* Write arbitrary stuff to a socket. */ static int sockwrite (LPCWSTR huh, int s, const void *str, size_t n) { for (;;) { if (send (s, str, n, 0) == (int) n) return n; attempt_resync (huh, s); } } /* Get a an ID (possibly) and a DWORD from the host gdb. Don't bother with the id if the main loop has already read it. */ static DWORD getdword (LPCWSTR huh, int s, gdb_wince_id what_this) { DWORD n; gdb_wince_id what; if (skip_next_id) skip_next_id = 0; else do if (sockread (huh, s, &what, sizeof (what)) != sizeof (what)) stub_error (L"error getting record type from host - %s.", huh); while (what_this != what); if (sockread (huh, s, &n, sizeof (n)) != sizeof (n)) stub_error (L"error getting %s from host.", huh); return n; } /* Get a an ID (possibly) and a WORD from the host gdb. Don't bother with the id if the main loop has already read it. */ static WORD getword (LPCWSTR huh, int s, gdb_wince_id what_this) { WORD n; gdb_wince_id what; if (skip_next_id) skip_next_id = 0; else do if (sockread (huh, s, &what, sizeof (what)) != sizeof (what)) stub_error (L"error getting record type from host - %s.", huh); while (what_this != what); if (sockread (huh, s, &n, sizeof (n)) != sizeof (n)) stub_error (L"error getting %s from host.", huh); return n; } /* Handy defines for getting various types of values. */ #define gethandle(huh, s, what) (HANDLE) getdword ((huh), (s), (what)) #define getpvoid(huh, s, what) (LPVOID) getdword ((huh), (s), (what)) #define getlen(huh, s, what) (gdb_wince_len) getword ((huh), (s), (what)) /* Get an arbitrary block of memory from the gdb host. This comes in two chunks an id/dword representing the length and the stream of memory itself. Returns a pointer, allocated via mempool, to a memory buffer. */ static LPWSTR getmemory (LPCWSTR huh, int s, gdb_wince_id what, gdb_wince_len *inlen) { LPVOID p; gdb_wince_len dummy; if (!inlen) inlen = &dummy; *inlen = getlen (huh, s, what); p = mempool ((unsigned int) *inlen); /* FIXME: check for error */ if ((gdb_wince_len) sockread (huh, s, p, *inlen) != *inlen) stub_error (L"error getting string from host."); return p; } /* Output an id/dword to the host */ static void putdword (LPCWSTR huh, int s, gdb_wince_id what, DWORD n) { if (sockwrite (huh, s, &what, sizeof (what)) != sizeof (what)) stub_error (L"error writing record id for %s to host.", huh); if (sockwrite (huh, s, &n, sizeof (n)) != sizeof (n)) stub_error (L"error writing %s to host.", huh); } /* Output an id/word to the host */ static void putword (LPCWSTR huh, int s, gdb_wince_id what, WORD n) { if (sockwrite (huh, s, &what, sizeof (what)) != sizeof (what)) stub_error (L"error writing record id for %s to host.", huh); if (sockwrite (huh, s, &n, sizeof (n)) != sizeof (n)) stub_error (L"error writing %s to host.", huh); } /* Convenience define for outputting a "gdb_wince_len" type. */ #define putlen(huh, s, what, n) putword ((huh), (s), (what), (gdb_wince_len) (n)) /* Put an arbitrary block of memory to the gdb host. This comes in two chunks an id/dword representing the length and the stream of memory itself. */ static void putmemory (LPCWSTR huh, int s, gdb_wince_id what, const void *mem, gdb_wince_len len) { putlen (huh, s, what, len); if (((short) len > 0) && (gdb_wince_len) sockwrite (huh, s, mem, len) != len) stub_error (L"error writing memory to host."); } /* Output the result of an operation to the host. If res != 0, sends a block of memory starting at mem of len bytes. If res == 0, sends -GetLastError () and avoids sending the mem. */ static void putresult (LPCWSTR huh, gdb_wince_result res, int s, gdb_wince_id what, const void *mem, gdb_wince_len len) { if (!res) len = -(int) GetLastError (); putmemory (huh, s, what, mem, len); } static HANDLE curproc; /* Currently unused, but nice for debugging */ /* Emulate CreateProcess. Returns &pi if no error. */ static void create_process (int s) { LPWSTR exec_file = getmemory (L"CreateProcess exec_file", s, GDB_CREATEPROCESS, NULL); LPWSTR args = getmemory (L"CreateProcess args", s, GDB_CREATEPROCESS, NULL); DWORD flags = getdword (L"CreateProcess flags", s, GDB_CREATEPROCESS); PROCESS_INFORMATION pi; gdb_wince_result res; res = CreateProcessW (exec_file, args, /* command line */ NULL, /* Security */ NULL, /* thread */ FALSE, /* inherit handles */ flags, /* start flags */ NULL, NULL, /* current directory */ NULL, &pi); putresult (L"CreateProcess", res, s, GDB_CREATEPROCESS, &pi, sizeof (pi)); curproc = pi.hProcess; } /* Emulate TerminateProcess. Returns return value of TerminateProcess if no error. *** NOTE: For some unknown reason, TerminateProcess seems to always return an ACCESS_DENIED (on Windows CE???) error. So, force a TRUE value for now. */ static void terminate_process (int s) { gdb_wince_result res; HANDLE h = gethandle (L"TerminateProcess handle", s, GDB_TERMINATEPROCESS); res = TerminateProcess (h, 0) || 1; /* Doesn't seem to work on SH so default to TRUE */ putresult (L"Terminate process result", res, s, GDB_TERMINATEPROCESS, &res, sizeof (res)); } static int stepped = 0; /* Handle single step instruction. FIXME: unneded? */ static void flag_single_step (int s) { stepped = 1; skip_next_id = 0; } struct skipper { wchar_t *s; int nskip; } skippy[] = { {L"Undefined Instruction:", 1}, {L"Data Abort:", 2}, {NULL, 0} }; static int skip_message (DEBUG_EVENT *ev) { char s[80]; DWORD nread; struct skipper *skp; int nbytes = ev->u.DebugString.nDebugStringLength; if (nbytes > sizeof(s)) nbytes = sizeof(s); memset (s, 0, sizeof (s)); if (!ReadProcessMemory (curproc, ev->u.DebugString.lpDebugStringData, s, nbytes, &nread)) return 0; for (skp = skippy; skp->s != NULL; skp++) if (wcsncmp ((wchar_t *) s, skp->s, wcslen (skp->s)) == 0) return skp->nskip; return 0; } /* Emulate WaitForDebugEvent. Returns the debug event on success. */ static void wait_for_debug_event (int s) { DWORD ms = getdword (L"WaitForDebugEvent ms", s, GDB_WAITFORDEBUGEVENT); gdb_wince_result res; DEBUG_EVENT ev; static int skip_next = 0; for (;;) { res = WaitForDebugEvent (&ev, ms); if (ev.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { if (skip_next) { skip_next--; goto ignore; } if (skip_next = skip_message (&ev)) goto ignore; } putresult (L"WaitForDebugEvent event", res, s, GDB_WAITFORDEBUGEVENT, &ev, sizeof (ev)); break; ignore: ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE); } return; } /* Emulate GetThreadContext. Returns CONTEXT structure on success. */ static void get_thread_context (int s) { CONTEXT c; HANDLE h = gethandle (L"GetThreadContext handle", s, GDB_GETTHREADCONTEXT); gdb_wince_result res; memset (&c, 0, sizeof (c)); c.ContextFlags = getdword (L"GetThreadContext flags", s, GDB_GETTHREADCONTEXT); res = (gdb_wince_result) GetThreadContext (h, &c); putresult (L"GetThreadContext data", res, s, GDB_GETTHREADCONTEXT, &c, sizeof (c)); } /* Emulate GetThreadContext. Returns success of SetThreadContext. */ static void set_thread_context (int s) { gdb_wince_result res; HANDLE h = gethandle (L"SetThreadContext handle", s, GDB_SETTHREADCONTEXT); LPCONTEXT pc = (LPCONTEXT) getmemory (L"SetThreadContext context", s, GDB_SETTHREADCONTEXT, NULL); res = SetThreadContext (h, pc); putresult (L"SetThreadContext result", res, s, GDB_SETTHREADCONTEXT, &res, sizeof (res)); } /* Emulate ReadProcessMemory. Returns memory read on success. */ static void read_process_memory (int s) { HANDLE h = gethandle (L"ReadProcessMemory handle", s, GDB_READPROCESSMEMORY); LPVOID p = getpvoid (L"ReadProcessMemory base", s, GDB_READPROCESSMEMORY); gdb_wince_len len = getlen (L"ReadProcessMemory size", s, GDB_READPROCESSMEMORY); LPVOID buf = mempool ((unsigned int) len); DWORD outlen; gdb_wince_result res; outlen = 0; res = (gdb_wince_result) ReadProcessMemory (h, p, buf, len, &outlen); putresult (L"ReadProcessMemory data", res, s, GDB_READPROCESSMEMORY, buf, (gdb_wince_len) outlen); } /* Emulate WriteProcessMemory. Returns WriteProcessMemory success. */ static void write_process_memory (int s) { HANDLE h = gethandle (L"WriteProcessMemory handle", s, GDB_WRITEPROCESSMEMORY); LPVOID p = getpvoid (L"WriteProcessMemory base", s, GDB_WRITEPROCESSMEMORY); gdb_wince_len len; LPVOID buf = getmemory (L"WriteProcessMemory buf", s, GDB_WRITEPROCESSMEMORY, &len); DWORD outlen; gdb_wince_result res; outlen = 0; res = WriteProcessMemory (h, p, buf, (DWORD) len, &outlen); putresult (L"WriteProcessMemory data", res, s, GDB_WRITEPROCESSMEMORY, (gdb_wince_len *) & outlen, sizeof (gdb_wince_len)); } /* Return non-zero to gdb host if given thread is alive. */ static void thread_alive (int s) { HANDLE h = gethandle (L"ThreadAlive handle", s, GDB_THREADALIVE); gdb_wince_result res; res = WaitForSingleObject (h, 0) == WAIT_OBJECT_0 ? 1 : 0; putresult (L"WriteProcessMemory data", res, s, GDB_THREADALIVE, &res, sizeof (res)); } /* Emulate SuspendThread. Returns value returned from SuspendThread. */ static void suspend_thread (int s) { DWORD res; HANDLE h = gethandle (L"SuspendThread handle", s, GDB_SUSPENDTHREAD); res = SuspendThread (h); putdword (L"SuspendThread result", s, GDB_SUSPENDTHREAD, res); } /* Emulate ResumeThread. Returns value returned from ResumeThread. */ static void resume_thread (int s) { DWORD res; HANDLE h = gethandle (L"ResumeThread handle", s, GDB_RESUMETHREAD); res = ResumeThread (h); putdword (L"ResumeThread result", s, GDB_RESUMETHREAD, res); } /* Emulate ContinueDebugEvent. Returns ContinueDebugEvent success. */ static void continue_debug_event (int s) { gdb_wince_result res; DWORD pid = getdword (L"ContinueDebugEvent pid", s, GDB_CONTINUEDEBUGEVENT); DWORD tid = getdword (L"ContinueDebugEvent tid", s, GDB_CONTINUEDEBUGEVENT); DWORD status = getdword (L"ContinueDebugEvent status", s, GDB_CONTINUEDEBUGEVENT); res = (gdb_wince_result) ContinueDebugEvent (pid, tid, status); putresult (L"ContinueDebugEvent result", res, s, GDB_CONTINUEDEBUGEVENT, &res, sizeof (res)); } /* Emulate CloseHandle. Returns CloseHandle success. */ static void close_handle (int s) { gdb_wince_result res; HANDLE h = gethandle (L"CloseHandle handle", s, GDB_CLOSEHANDLE); res = (gdb_wince_result) CloseHandle (h); putresult (L"CloseHandle result", res, s, GDB_CLOSEHANDLE, &res, sizeof (res)); } /* Main loop for reading requests from gdb host on the socket. */ static void dispatch (int s) { gdb_wince_id id; /* Continue reading from socket until receive a GDB_STOPSUB. */ while (sockread (L"Dispatch", s, &id, sizeof (id)) > 0) { skip_next_id = 1; switch (id) { case GDB_CREATEPROCESS: create_process (s); break; case GDB_TERMINATEPROCESS: terminate_process (s); break; case GDB_WAITFORDEBUGEVENT: wait_for_debug_event (s); break; case GDB_GETTHREADCONTEXT: get_thread_context (s); break; case GDB_SETTHREADCONTEXT: set_thread_context (s); break; case GDB_READPROCESSMEMORY: read_process_memory (s); break; case GDB_WRITEPROCESSMEMORY: write_process_memory (s); break; case GDB_THREADALIVE: thread_alive (s); break; case GDB_SUSPENDTHREAD: suspend_thread (s); break; case GDB_RESUMETHREAD: resume_thread (s); break; case GDB_CONTINUEDEBUGEVENT: continue_debug_event (s); break; case GDB_CLOSEHANDLE: close_handle (s); break; case GDB_STOPSTUB: terminate_process (s); return; case GDB_SINGLESTEP: flag_single_step (s); break; default: { WCHAR buf[80]; wsprintfW (buf, L"Invalid command id received: %d", id); MessageBoxW (NULL, buf, L"GDB", MB_ICONERROR); skip_next_id = 0; } } } } /* The Windows Main entry point */ int WINAPI WinMain (HINSTANCE hi, HINSTANCE hp, LPWSTR cmd, int show) { struct hostent *h; int s; struct WSAData wd; struct sockaddr_in sin; int tmp; LPWSTR whost; char host[80]; whost = wcschr (cmd, L' '); /* Look for argument. */ /* If no host is specified, just use default */ if (whost) { /* Eat any spaces. */ while (*whost == L' ' || *whost == L'\t') whost++; wcstombs (host, whost, 80); /* Convert from UNICODE to ascii */ } /* Winsock initialization. */ if (WSAStartup (MAKEWORD (1, 1), &wd)) stub_error (L"Couldn't initialize WINSOCK."); /* If whost was specified, first try it. If it was not specified or the host lookup failed, try the Windows CE magic ppp_peer lookup. ppp_peer is supposed to be the Windows host sitting on the other end of the serial cable. */ if (whost && *whost && (h = gethostbyname (host)) != NULL) /* nothing to do */ ; else if ((h = gethostbyname ("ppp_peer")) == NULL) stub_error (L"Couldn't get IP address of host system. Error %d", WSAGetLastError ()); /* Get a socket. */ if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) stub_error (L"Couldn't connect to host system. Error %d", WSAGetLastError ()); /* Allow rapid reuse of the port. */ tmp = 1; setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)); /* Set up the information for connecting to the host gdb process. */ memset (&sin, 0, sizeof (sin)); sin.sin_family = h->h_addrtype; memcpy (&sin.sin_addr, h->h_addr, h->h_length); sin.sin_port = htons (7000); /* FIXME: This should be configurable */ /* Connect to host */ if (connect (s, (struct sockaddr *) &sin, sizeof (sin)) < 0) stub_error (L"Couldn't connect to host gdb."); /* Read from socket until told to exit. */ dispatch (s); WSACleanup (); return 0; }