/* cygload.cc Written by Max Kaehn <slothman@electric-cloud.com> This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. Note that dynamically linking to cygwin1.dll automatically places your code under the GPL unless you purchase a Cygwin Contract with Red Hat, Inc. See http://www.redhat.com/software/cygwin/ for more information. cygload demonstrates how to dynamically load cygwin1.dll. The default build uses MinGW to compile it; the Makefile also shows how to build it using the Microsoft compiler. By default, the program will silently test basic functionality: * Making space on the stack for cygtls * Loading and initializing cygwin1.dll * Path translation * Error handling * Signal handling Options for this program: -v Verbose output. Normal operation is entirely silent, save for errors. -testinterrupts Pauses the program for 30 seconds so you can demonstrate that it handles ^C properly. -cygwin Name of DLL to load. Defaults to "cygwin1.dll". */ #include "cygload.h" #include <iostream> #include <sstream> #include <vector> #include <errno.h> // for ENOENT #include <sys/types.h> #include <sys/stat.h> using std::cout; using std::cerr; using std::endl; using std::string; cygwin::padding *cygwin::padding::_main = NULL; DWORD cygwin::padding::_mainTID = 0; // A few cygwin constants. static const int SIGHUP = 1; static const int SIGINT = 2; static const int SIGTERM = 15; // Cygwin won't deliver this one to us; // expect unadorned "kill" to just kill // your process. static const int SIGSTOP = 17; // Cygwin insists on delivering SIGSTOP to // the main thread. If your main thread // is not interruptible, you'll miss the // signal and ignore the request to suspend. static const int SIGTSTP = 18; // ^Z on a tty. static const int SIGCONT = 19; // Resume a stopped process. static const int SIGUSR1 = 30; static const int SIGUSR2 = 31; // Using *out instead of cout. In verbose mode, out == &cout. static std::ostream *out = NULL; cygwin::padding::padding () { _main = this; _mainTID = GetCurrentThreadId (); _end = _padding + sizeof (_padding); char *stackbase; #ifdef __GNUC__ __asm__ ( "movl %%fs:4, %0" :"=r"(stackbase) ); #else __asm { mov eax, fs:[4]; mov stackbase, eax; } #endif _stackbase = stackbase; // We've gotten as close as we can to the top of the stack. Even // subverting the entry point, though, still doesn't get us there-- I'm // getting 64 bytes in use before the entry point. So we back up the data // there and restore it when the destructor is called: if ((_stackbase - _end) != 0) { size_t delta = (_stackbase - _end); _backup.resize (delta); memcpy (&(_backup[0]), _end, delta); } } cygwin::padding::~padding () { _main = NULL; if (_backup.size ()) { memcpy (_end, &(_backup[0]), _backup.size ()); } } void cygwin::padding::check () { if (_main == NULL) throw std::runtime_error ("No padding declared!"); if (_mainTID != GetCurrentThreadId ()) throw std::runtime_error ("You need to initialize cygwin::connector " "in the same thread in which you declared the " "padding."); if (_main->_backup.size ()) *out << "Warning! Stack base is " << static_cast<void *>(_main->_stackbase) << ". padding ends at " << static_cast<void *>(_main->_end) << ". Delta is " << (_main->_stackbase - _main->_end) << ". Stack variables could be overwritten!" << endl; } cygwin::connector::connector (const char *dll) { // This will throw if padding is not in place. padding::check (); *out << "Loading " << dll << "..." << endl; // This should call init.cc:dll_entry() with DLL_PROCESS_ATTACH, // which calls dll_crt0_0(). if ((_library = LoadLibrary (dll)) == NULL) throw windows_error ("LoadLibrary", dll); *out << "Initializing cygwin..." << endl; // This calls dcrt0.cc:cygwin_dll_init(), which calls dll_crt0_1(), // which will, among other things: // * spawn the cygwin signal handling thread from sigproc_init() // * initialize the thread-local storage for this thread and overwrite // the first 4K of the stack void (*cyginit) (); get_symbol ("cygwin_dll_init", cyginit); (*cyginit) (); *out << "Loading symbols..." << endl; // Pick up the function pointers for the basic infrastructure. get_symbol ("__errno", _errno); get_symbol ("strerror", _strerror); get_symbol ("cygwin_conv_to_full_posix_path", _conv_to_full_posix_path); get_symbol ("cygwin_conv_to_full_win32_path", _conv_to_full_win32_path); // Note that you need to be running an interruptible cygwin function if // you want to receive signals. You can use the standard signal() // mechanism if you're willing to have your main thread spend all its time // in interruptible cygwin functions like sleep(). Christopher Faylor // cautions that this solution "could be slightly racy": if a second // signal comes in before the first one is done processing, the thread // won't be back in sigwait() to catch it. *out << "Spawning signal handling thread..." << endl; _waiting_for_signals = true; _signal_thread_done = false; InitializeCriticalSection (&_thread_mutex); DWORD tid; _signal_thread = CreateThread (NULL, // Default security. 32768, // Adjust the stack size as // appropriate for what your signal // handler needs in order to run, and // then add 4K for cygtls. &signal_thread, // Function to call this, // Context 0, // Flags &tid); // Thread ID if (_signal_thread == NULL) throw windows_error ("CreateThread", "signal_thread"); } cygwin::connector::~connector () { try { // First, shut down signal handling. int (*raze) (int); int (*pthread_join) (void *, void **); get_symbol ("raise", raze); get_symbol ("pthread_join", pthread_join); // Tell the listener to shut down... _waiting_for_signals = false; int err = 0; EnterCriticalSection (&_thread_mutex); if (!_signal_thread_done) err = raze (SIGUSR2); LeaveCriticalSection (&_thread_mutex); if (err) cerr << error (this, "raise", "SIGUSR2").what () << endl; // ...and get the thread to join. if (!CloseHandle (_signal_thread)) throw windows_error ("CloseHandle", "signal_thread"); // This should call init.cc:dll_entry() with DLL_PROCESS_DETACH. if (!FreeLibrary (_library)) throw windows_error ("FreeLibrary", "cygwin1.dll"); } catch (std::exception &x) { cerr << x.what () << endl; } } DWORD WINAPI cygwin::connector::signal_thread (void *param) { connector *that = reinterpret_cast < connector * > (param); try { that->await_signal (); } catch (std::exception &x) { cerr << "signal_thread caught " << x.what () << endl; return 0; } return 0; } void cygwin::connector::await_signal () { // Wait for signals. unsigned long sigset[32]; int sig; int (*empty) (void *); int (*add) (void *, int); int (*wait) (void *, int *); get_symbol ("sigemptyset", empty); get_symbol ("sigaddset", add); get_symbol ("sigwait", wait); empty (sigset); add (sigset, SIGHUP); add (sigset, SIGINT); // add (sigset, SIGSTOP); // add (sigset, SIGTSTP); // I can't get this to suspend properly, so // I'll leave it up to chance that the main // thread is interruptible. add (sigset, SIGUSR1); add (sigset, SIGUSR2); while (_waiting_for_signals) { int err = wait (sigset, &sig); if (err) cerr << error (this, "sigwait").what () << endl; else *out << "Received signal " << sig << "." << endl; switch (sig) { case SIGUSR2: if (!_waiting_for_signals) { // SIGUSR2 is how ~connector wakes this thread goto done; } break; default: break; } handle_signals (sig); } done: EnterCriticalSection (&_thread_mutex); _signal_thread_done = true; LeaveCriticalSection (&_thread_mutex); *out << "await_signal done." << endl; } cygwin::connector::signal_handler * cygwin::connector::set_handler (int signal, signal_handler *handler) { signal_handler *retval = _signal_handlers[signal]; if (handler == NULL) _signal_handlers.erase (signal); else _signal_handlers[signal] = handler; return retval; } void cygwin::connector::handle_signals (int sig) { callback_list::iterator h = _signal_handlers.find (sig); if (h != _signal_handlers.end ()) { try { signal_handler *handler = h->second; (*handler) (sig); return; } catch (std::exception &x) { cerr << "cygwin::connector::handle_signals caught " << x.what () << "!" << endl; return; } } cerr << "No handler for signal " << sig << "!" << endl; } int cygwin::connector::err_no () const { int *e = (*_errno) (); if (e == NULL) { return -1; } return *e; } string cygwin::connector::str_error (int err_no) const { string retval; const char *s = (*_strerror) (err_no); if (s != NULL) { retval = s; } else { std::ostringstream o; o << "Unexpected errno " << err_no; retval = o.str (); } return retval; } string cygwin::connector::unix_path (const string &windows) const { char buf[MAX_PATH]; _conv_to_full_posix_path (windows.c_str (), buf); return string (buf); } string cygwin::connector::win_path (const string &unix) const { char buf[MAX_PATH]; _conv_to_full_win32_path (unix.c_str (), buf); return string (buf); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// string cygwin::error::format (cygwin::connector *c, int err_no, const char *message, const char *detail) { std::ostringstream ret; ret << message; if (detail) { ret << "(" << detail << ")"; } ret << ": " << c->str_error (err_no); return ret.str (); } string windows_error::format (DWORD error, const char *message, const char *detail) { std::ostringstream ret; char buf[512]; DWORD bytes; ret << message; if (detail) ret << "(" << detail << ")"; ret << ": "; bytes = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof (buf), 0); if (bytes == 0) ret << "Unexpected Windows error " << error; else { // Remove trailing whitespace char *p = buf + bytes - 1; while (isspace (*p)) *p-- = '\0'; ret << buf << " (" << error << ")"; } return ret.str (); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// extern "C" int mainCRTStartup (); // This just pushes 4K onto the stack, backs up the original stack, and // jumps into the regular startup code. This avoids having to worry about // backing up argc and argv. extern "C" int __stdcall cygloadCRTStartup () { cygwin::padding padding; return mainCRTStartup (); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// static void hangup (int sig) { cout << "Hangup (" << sig << ")." << endl; } static void interrupt (int sig) { cerr << "Interrupt (" << sig << ")!" << endl; exit (0); } static int caught = false; static void catch_signal (int) { *out << "Signals are working." << endl; caught = true; } int main (int argc, char *argv[]) { // If you do not want to use cygloadCRTStartup() as an entry point, // uncomment this line, but be sure to have *everything* you want // from the stack below it backed up before you call the // constructor for cygwin::connector. //cygwin::padding padding; std::ostringstream output; bool verbose = false, testinterrupts = false; const char *dll = "cygwin1.dll"; out = &output; for (int i = 1; i < argc; ++i) { string arg = string (argv[i]); if (arg == "-v") { verbose = true; out = &cout; } else if (arg == "-testinterrupts") testinterrupts = true; else if (arg == "-cygwin") { if (i+1 >= argc) { cerr << "Need to supply an argument with -cygwin." << endl; return 255; } dll = argv[++i]; } } try { *out << "Connecting to cygwin..." << endl; cygwin::connector cygwin (dll); *out << "Successfully connected." << endl; string result = cygwin.str_error (ENOENT); if (result != "No such file or directory") { cerr << "strerror(ENOENT) returned \"" << result << "\" instead of \"No such file or directory\"!" << endl; return 1; } else if (verbose) { *out << "strerror(ENOENT) = " << result << endl; } // Path conversion: from cygwin to Windows... result = cygwin.win_path ("/usr"); struct _stat statbuf; if (::_stat (result.c_str (), &statbuf) < 0) { cerr << "stat(\"" << result << "\") failed!" << endl; return 2; } else if (verbose) { *out << "/usr == " << result << endl; } // ...and back: char buf[MAX_PATH], scratch[256]; GetSystemDirectory (buf, sizeof(buf)); int (*cygstat) (const char *, void *); cygwin.get_symbol ("stat", cygstat); if (cygstat (buf, scratch) < 0) { cerr << "cygwin stat(\"" << buf << "\") failed!" << endl; return 3; } else if (verbose) { *out << buf << " == " << cygwin.unix_path(buf) << endl; } // Test error handling. This should output // "open(/potrzebie/furshlugginer): No such file or directory" { int (*cygopen) (const char *, int); cygwin.get_symbol ("open", cygopen); if (cygopen ("/potrzebie/furshlugginer", 0 /* O_RDONLY */ ) < 0) { int err = cygwin.err_no (); if (err != ENOENT) { cerr << "cygwin open(\"/potrzebie/furshlugginer\", " "O_RDONLY): expected to fail with ENOENT, got " << err << "!" << endl; return 4; } if (verbose) *out << cygwin::error (&cygwin, "open", "/potrzebie/furshlugginer").what () << endl; } else { cerr << "/potrzebie/furshlugginer should not exist!" << endl; return 5; } } // And signal handling: std::pointer_to_unary_function < int , void > h1 (&hangup); std::pointer_to_unary_function < int , void > h2 (&interrupt); std::pointer_to_unary_function < int , void > h3 (&catch_signal); cygwin.set_handler (SIGHUP, &h1); cygwin.set_handler (SIGINT, &h2); cygwin.set_handler (SIGUSR1, &h3); // Make sure the signal handler thread has had time to start... Sleep (100); // Send a test signal to set "caught" to true... int (*raze) (int); cygwin.get_symbol ("raise", raze); raze (SIGUSR1); // And give the thread time to wait for the shutdown signal. Sleep (100); if (testinterrupts) { // This is a worst case scenario for testing interrupts: the // main thread is in a long-duration Windows API call. This // makes the main thread uninterruptible; cygwin will retry // 20 times, with a low_priority_sleep(0) between each try. cout << "Sleeping for 30 seconds, waiting for a signal..." << endl; Sleep (30000); cout << "Done waiting." << endl; } } catch (std::exception &x) { cerr << x.what () << endl; return 2; } if (caught) return 0; else { cerr << "Never received SIGUSR1." << endl; return 1; } }