/** * @file slplugin.cpp * @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library. * * @cond * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * * @endcond */ #include "linden_common.h" #include "llpluginprocesschild.h" #include "llpluginmessage.h" #include "llerrorcontrol.h" #include "llapr.h" #include "llstring.h" #if LL_DARWIN #include #include "slplugin-objc.h" #endif #if LL_DARWIN || LL_LINUX #include #endif /* On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly or LSUIElement flag in the Info.plist. Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags: -sectcreate __TEXT __info_plist /path/to/slplugin_info.plist which means adding this to the gcc flags: -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist Now that SLPlugin is a bundled app on the Mac, this is no longer necessary (it can just use a regular Info.plist file), but I'm leaving this comment in for posterity. */ #if LL_DARWIN || LL_LINUX // Signal handlers to make crashes not show an OS dialog... static void crash_handler(int sig) { // Just exit cleanly. // TODO: add our own crash reporting _exit(1); } #endif #if LL_WINDOWS #include //////////////////////////////////////////////////////////////////////////////// // Our exception handler - will probably just exit and the host application // will miss the heartbeat and log the error in the usual fashion. LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop ) { //std::cerr << "This plugin (" << __FILE__ << ") - "; //std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl; // TODO: replace exception handler before we exit? return EXCEPTION_EXECUTE_HANDLER; } // Taken from : http://blog.kalmbachnet.de/?postid=75 // The MSVC 2005 CRT forces the call of the default-debugger (normally Dr.Watson) // even with the other exception handling code. This (terrifying) piece of code // patches things so that doesn't happen. LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) { return NULL; } BOOL PreventSetUnhandledExceptionFilter() { // WARNING: This won't work on 64-bit Windows systems so we turn it off it. // It should work for any flavor of 32-bit Windows we care about. // If it's off, sometimes you will see an OS message when a plugin crashes #ifndef _WIN64 HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" ); if ( NULL == hKernel32 ) return FALSE; void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" ); if( NULL == pOrgEntry ) return FALSE; unsigned char newJump[ 100 ]; DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far void *pNewFunc = &MyDummySetUnhandledExceptionFilter; DWORD dwNewEntryAddr = (DWORD) pNewFunc; DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; newJump[ 0 ] = 0xE9; // JMP absolute memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) ); SIZE_T bytesWritten; BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten ); return bRet; #else return FALSE; #endif } //////////////////////////////////////////////////////////////////////////////// // Hook our exception handler and replace the system one void initExceptionHandler() { LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; // save old exception handler in case we need to restore it at the end prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler ); PreventSetUnhandledExceptionFilter(); } bool checkExceptionHandler() { bool ok = true; LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler); PreventSetUnhandledExceptionFilter(); if (prev_filter != myWin32ExceptionHandler) { LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL; ok = false; } if (prev_filter == NULL) { ok = FALSE; if (NULL == myWin32ExceptionHandler) { LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL; } else { LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL; } } return ok; } #endif // If this application on Windows platform is a console application, a console is always // created which is bad. Making it a Windows "application" via CMake settings but not // adding any code to explicitly create windows does the right thing. #if LL_WINDOWS int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) #else int main(int argc, char **argv) #endif { ll_init_apr(); // Set up llerror logging { LLError::initForApplication("."); LLError::setDefaultLevel(LLError::LEVEL_INFO); // LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); // LLError::logToFile("slplugin.log"); } #if LL_WINDOWS if( strlen( lpCmdLine ) == 0 ) { LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL; }; U32 port = 0; if(!LLStringUtil::convertToU32(lpCmdLine, port)) { LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; }; // Insert our exception handler into the system so this plugin doesn't // display a crash message if something bad happens. The host app will // see the missing heartbeat and log appropriately. initExceptionHandler(); #elif LL_DARWIN || LL_LINUX if(argc < 2) { LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL; } U32 port = 0; if(!LLStringUtil::convertToU32(argv[1], port)) { LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; } // Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown. signal(SIGILL, &crash_handler); // illegal instruction # if LL_DARWIN signal(SIGEMT, &crash_handler); // emulate instruction executed # endif // LL_DARWIN signal(SIGFPE, &crash_handler); // floating-point exception signal(SIGBUS, &crash_handler); // bus error signal(SIGSEGV, &crash_handler); // segmentation violation signal(SIGSYS, &crash_handler); // non-existent system call invoked #endif #if LL_DARWIN setupCocoa(); createAutoReleasePool(); #endif LLPluginProcessChild *plugin = new LLPluginProcessChild(); plugin->init(port); #if LL_DARWIN deleteAutoReleasePool(); #endif LLTimer timer; timer.start(); #if LL_WINDOWS checkExceptionHandler(); #endif #if LL_DARWIN // If the plugin opens a new window (such as the Flash plugin's fullscreen player), we may need to bring this plugin process to the foreground. // Use this to track the current frontmost window and bring this process to the front if it changes. WindowRef front_window = NULL; WindowGroupRef layer_group = NULL; int window_hack_state = 0; CreateWindowGroup(kWindowGroupAttrFixedLevel, &layer_group); if(layer_group) { // Start out with a window layer that's way out in front (fixes the problem with the menubar not getting hidden on first switch to fullscreen youtube) SetWindowGroupName(layer_group, CFSTR("SLPlugin Layer")); SetWindowGroupLevel(layer_group, kCGOverlayWindowLevel); } #endif #if LL_DARWIN EventTargetRef event_target = GetEventDispatcherTarget(); #endif while(!plugin->isDone()) { #if LL_DARWIN createAutoReleasePool(); #endif timer.reset(); plugin->idle(); #if LL_DARWIN { // Some plugins (webkit at least) will want an event loop. This qualifies. EventRef event; if(ReceiveNextEvent(0, 0, kEventDurationNoWait, true, &event) == noErr) { SendEventToEventTarget (event, event_target); ReleaseEvent(event); } // Check for a change in this process's frontmost window. if(ActiveNonFloatingWindow() != front_window) { ProcessSerialNumber self = { 0, kCurrentProcess }; ProcessSerialNumber parent = { 0, kNoProcess }; ProcessSerialNumber front = { 0, kNoProcess }; Boolean this_is_front_process = false; Boolean parent_is_front_process = false; { // Get this process's parent ProcessInfoRec info; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = NULL; info.processAppSpec = NULL; if(GetProcessInformation( &self, &info ) == noErr) { parent = info.processLauncher; } // and figure out whether this process or its parent are currently frontmost if(GetFrontProcess(&front) == noErr) { (void) SameProcess(&self, &front, &this_is_front_process); (void) SameProcess(&parent, &front, &parent_is_front_process); } } if((ActiveNonFloatingWindow() != NULL) && (front_window == NULL)) { // Opening the first window if(window_hack_state == 0) { // Next time through the event loop, lower the window group layer window_hack_state = 1; } if(layer_group) { SetWindowGroup(ActiveNonFloatingWindow(), layer_group); } if(parent_is_front_process) { // Bring this process's windows to the front. (void) SetFrontProcess( &self ); } ActivateWindow(ActiveNonFloatingWindow(), true); } else if((ActiveNonFloatingWindow() == NULL) && (front_window != NULL)) { // Closing the last window if(this_is_front_process) { // Try to bring this process's parent to the front (void) SetFrontProcess(&parent); } } else if(window_hack_state == 1) { if(layer_group) { // Set the window group level back to something less extreme SetWindowGroupLevel(layer_group, kCGNormalWindowLevel); } window_hack_state = 2; } front_window = ActiveNonFloatingWindow(); } } #endif F64 elapsed = timer.getElapsedTimeF64(); F64 remaining = plugin->getSleepTime() - elapsed; if(remaining <= 0.0f) { // We've already used our full allotment. // LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL; // Still need to service the network... plugin->pump(); } else { // LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL; // timer.reset(); // This also services the network as needed. plugin->sleep(remaining); // LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" << LL_ENDL; } #if LL_WINDOWS // More agressive checking of interfering exception handlers. // Doesn't appear to be required so far - even for plugins // that do crash with a single call to the intercept // exception handler such as QuickTime. //checkExceptionHandler(); #endif #if LL_DARWIN deleteAutoReleasePool(); #endif } delete plugin; ll_cleanup_apr(); return 0; }