2001-11-07 01:52:52 +03:00
|
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
2012-05-21 15:12:37 +04:00
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2001-11-07 01:52:52 +03:00
|
|
|
|
|
|
|
|
|
#include "nsToolkit.h"
|
|
|
|
|
|
2006-12-08 11:29:46 +03:00
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
#include <mach/mach_port.h>
|
|
|
|
|
#include <mach/mach_interface.h>
|
|
|
|
|
#include <mach/mach_init.h>
|
|
|
|
|
|
2010-04-05 18:04:49 +04:00
|
|
|
|
extern "C" {
|
|
|
|
|
#include <mach-o/getsect.h>
|
|
|
|
|
}
|
|
|
|
|
#include <mach-o/dyld.h>
|
|
|
|
|
#include <mach-o/nlist.h>
|
|
|
|
|
#include <mach/vm_map.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <dlfcn.h>
|
|
|
|
|
|
2007-05-18 06:06:59 +04:00
|
|
|
|
#import <Cocoa/Cocoa.h>
|
2007-01-18 09:34:07 +03:00
|
|
|
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
|
|
|
#import <IOKit/IOMessage.h>
|
2006-12-08 11:29:46 +03:00
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
#include "nsCocoaUtils.h"
|
2008-02-21 02:47:05 +03:00
|
|
|
|
#include "nsObjCExceptions.h"
|
2007-07-18 00:29:39 +04:00
|
|
|
|
|
2011-10-14 22:11:22 +04:00
|
|
|
|
#include "nsGkAtoms.h"
|
2007-05-18 06:06:59 +04:00
|
|
|
|
#include "nsIRollupListener.h"
|
|
|
|
|
#include "nsIWidget.h"
|
2006-12-08 11:29:46 +03:00
|
|
|
|
|
|
|
|
|
#include "nsIObserverService.h"
|
|
|
|
|
#include "nsIServiceManager.h"
|
2011-05-27 12:15:20 +04:00
|
|
|
|
|
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
|
|
|
|
|
|
using namespace mozilla;
|
2006-12-18 22:50:14 +03:00
|
|
|
|
|
2007-05-18 06:06:59 +04:00
|
|
|
|
// defined in nsChildView.mm
|
|
|
|
|
extern nsIRollupListener * gRollupListener;
|
|
|
|
|
extern nsIWidget * gRollupWidget;
|
|
|
|
|
|
2006-12-18 22:50:14 +03:00
|
|
|
|
static io_connect_t gRootPort = MACH_PORT_NULL;
|
2006-12-08 11:29:46 +03:00
|
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
|
nsToolkit* nsToolkit::gToolkit = nullptr;
|
2006-12-08 11:29:46 +03:00
|
|
|
|
|
2002-12-13 11:43:18 +03:00
|
|
|
|
nsToolkit::nsToolkit()
|
2012-07-30 18:20:58 +04:00
|
|
|
|
: mSleepWakeNotificationRLS(nullptr)
|
|
|
|
|
, mEventTapPort(nullptr)
|
|
|
|
|
, mEventTapRLS(nullptr)
|
2001-11-07 01:52:52 +03:00
|
|
|
|
{
|
2011-10-28 18:47:54 +04:00
|
|
|
|
MOZ_COUNT_CTOR(nsToolkit);
|
2011-10-25 19:05:32 +04:00
|
|
|
|
RegisterForSleepWakeNotifcations();
|
|
|
|
|
RegisterForAllProcessMouseEvents();
|
2001-11-07 01:52:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsToolkit::~nsToolkit()
|
2006-12-08 11:29:46 +03:00
|
|
|
|
{
|
2011-10-06 03:54:07 +04:00
|
|
|
|
MOZ_COUNT_DTOR(nsToolkit);
|
2006-12-08 11:29:46 +03:00
|
|
|
|
RemoveSleepWakeNotifcations();
|
2007-07-18 00:29:39 +04:00
|
|
|
|
UnregisterAllProcessMouseEventHandlers();
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
nsToolkit::PostSleepWakeNotification(const char* aNotification)
|
|
|
|
|
{
|
|
|
|
|
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
|
|
|
|
|
if (observerService)
|
2012-07-30 18:20:58 +04:00
|
|
|
|
observerService->NotifyObservers(nullptr, aNotification, nullptr);
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
|
|
|
|
|
static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
|
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
2006-12-08 11:29:46 +03:00
|
|
|
|
switch (messageType)
|
|
|
|
|
{
|
|
|
|
|
case kIOMessageSystemWillSleep:
|
|
|
|
|
// System is going to sleep now.
|
|
|
|
|
nsToolkit::PostSleepWakeNotification("sleep_notification");
|
|
|
|
|
::IOAllowPowerChange(gRootPort, (long)messageArgument);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kIOMessageCanSystemSleep:
|
|
|
|
|
// In this case, the computer has been idle for several minutes
|
|
|
|
|
// and will sleep soon so you must either allow or cancel
|
|
|
|
|
// this notification. Important: if you don’t respond, there will
|
|
|
|
|
// be a 30-second timeout before the computer sleeps.
|
|
|
|
|
// In Mozilla's case, we always allow sleep.
|
|
|
|
|
::IOAllowPowerChange(gRootPort,(long)messageArgument);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kIOMessageSystemHasPoweredOn:
|
|
|
|
|
// Handle wakeup.
|
|
|
|
|
nsToolkit::PostSleepWakeNotification("wake_notification");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
2001-11-07 01:52:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2003-10-31 05:30:22 +03:00
|
|
|
|
nsresult
|
2006-12-08 11:29:46 +03:00
|
|
|
|
nsToolkit::RegisterForSleepWakeNotifcations()
|
2001-11-07 01:52:52 +03:00
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
|
|
|
|
|
2006-12-08 11:29:46 +03:00
|
|
|
|
IONotificationPortRef notifyPortRef;
|
|
|
|
|
|
|
|
|
|
NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
|
|
|
|
|
|
|
|
|
|
gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
|
|
|
|
|
if (gRootPort == MACH_PORT_NULL) {
|
2009-08-14 18:09:00 +04:00
|
|
|
|
NS_ERROR("IORegisterForSystemPower failed");
|
2006-12-08 11:29:46 +03:00
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
|
|
|
|
|
::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
|
|
|
|
|
mSleepWakeNotificationRLS,
|
|
|
|
|
kCFRunLoopDefaultMode);
|
|
|
|
|
|
2002-12-13 11:43:18 +03:00
|
|
|
|
return NS_OK;
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
2002-12-13 11:43:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
2006-12-08 11:29:46 +03:00
|
|
|
|
void
|
|
|
|
|
nsToolkit::RemoveSleepWakeNotifcations()
|
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
2006-12-08 11:29:46 +03:00
|
|
|
|
if (mSleepWakeNotificationRLS) {
|
|
|
|
|
::IODeregisterForSystemPower(&mPowerNotifier);
|
|
|
|
|
::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
|
|
|
|
|
mSleepWakeNotificationRLS,
|
|
|
|
|
kCFRunLoopDefaultMode);
|
|
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
|
mSleepWakeNotificationRLS = nullptr;
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
// Converts aPoint from the CoreGraphics "global display coordinate" system
|
|
|
|
|
// (which includes all displays/screens and has a top-left origin) to its
|
|
|
|
|
// (presumed) Cocoa counterpart (assumed to be the same as the "screen
|
|
|
|
|
// coordinates" system), which has a bottom-left origin.
|
|
|
|
|
static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
|
|
|
|
|
{
|
|
|
|
|
NSPoint cocoaPoint;
|
|
|
|
|
cocoaPoint.x = aPoint.x;
|
2007-12-06 02:17:08 +03:00
|
|
|
|
cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
return cocoaPoint;
|
2007-05-18 06:06:59 +04:00
|
|
|
|
}
|
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
// Since our event tap is "listen only", events arrive here a little after
|
|
|
|
|
// they've already been processed.
|
|
|
|
|
static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
|
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
if ((type == kCGEventTapDisabledByUserInput) ||
|
|
|
|
|
(type == kCGEventTapDisabledByTimeout))
|
|
|
|
|
return event;
|
|
|
|
|
if (!gRollupWidget || !gRollupListener || [NSApp isActive])
|
|
|
|
|
return event;
|
|
|
|
|
// Don't bother with rightMouseDown events here -- because of the delay,
|
|
|
|
|
// we'll end up closing browser context menus that we just opened. Since
|
|
|
|
|
// these events usually raise a context menu, we'll handle them by hooking
|
|
|
|
|
// the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
|
|
|
|
|
// notification (in nsAppShell.mm's AppShellDelegate).
|
|
|
|
|
if (type == kCGEventRightMouseDown)
|
|
|
|
|
return event;
|
|
|
|
|
NSWindow *ctxMenuWindow = (NSWindow*) gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
|
|
|
|
|
if (!ctxMenuWindow)
|
|
|
|
|
return event;
|
|
|
|
|
NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
|
|
|
|
|
// Don't roll up the rollup widget if our mouseDown happens over it (doing
|
|
|
|
|
// so would break the corresponding context menu).
|
|
|
|
|
if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
|
|
|
|
|
return event;
|
2011-11-08 23:59:07 +04:00
|
|
|
|
gRollupListener->Rollup(0);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
return event;
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cocoa Firefox's use of custom context menus requires that we explicitly
|
|
|
|
|
// handle mouse events from other processes that the OS handles
|
|
|
|
|
// "automatically" for native context menus -- mouseMoved events so that
|
|
|
|
|
// right-click context menus work properly when our browser doesn't have the
|
|
|
|
|
// focus (bmo bug 368077), and mouseDown events so that our browser can
|
|
|
|
|
// dismiss a context menu when a mouseDown happens in another process (bmo
|
|
|
|
|
// bug 339945).
|
2007-05-18 06:06:59 +04:00
|
|
|
|
void
|
|
|
|
|
nsToolkit::RegisterForAllProcessMouseEvents()
|
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
2007-08-16 01:03:18 +04:00
|
|
|
|
// Don't do this for apps that (like Camino) use native context menus.
|
2011-06-22 01:00:47 +04:00
|
|
|
|
#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
|
|
|
|
|
return;
|
|
|
|
|
#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
|
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
if (!mEventTapRLS) {
|
|
|
|
|
// Using an event tap for mouseDown events (instead of installing a
|
|
|
|
|
// handler for them on the EventMonitor target) works around an Apple
|
|
|
|
|
// bug that causes OS menus (like the Clock menu) not to work properly
|
|
|
|
|
// on OS X 10.4.X and below (bmo bug 381448).
|
|
|
|
|
// We install our event tap "listen only" to get around yet another Apple
|
|
|
|
|
// bug -- when we install it as an event filter on any kind of mouseDown
|
|
|
|
|
// event, that kind of event stops working in the main menu, and usually
|
|
|
|
|
// mouse event processing stops working in all apps in the current login
|
|
|
|
|
// session (so the entire OS appears to be hung)! The downside of
|
|
|
|
|
// installing listen-only is that events arrive at our handler slightly
|
|
|
|
|
// after they've already been processed.
|
|
|
|
|
mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
|
|
|
|
|
kCGHeadInsertEventTap,
|
|
|
|
|
kCGEventTapOptionListenOnly,
|
|
|
|
|
CGEventMaskBit(kCGEventLeftMouseDown)
|
|
|
|
|
| CGEventMaskBit(kCGEventRightMouseDown)
|
|
|
|
|
| CGEventMaskBit(kCGEventOtherMouseDown),
|
|
|
|
|
EventTapCallback,
|
2012-07-30 18:20:58 +04:00
|
|
|
|
nullptr);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
if (!mEventTapPort)
|
|
|
|
|
return;
|
2012-07-30 18:20:58 +04:00
|
|
|
|
mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
if (!mEventTapRLS) {
|
|
|
|
|
CFRelease(mEventTapPort);
|
2012-07-30 18:20:58 +04:00
|
|
|
|
mEventTapPort = nullptr;
|
2007-07-18 00:29:39 +04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
|
|
|
|
|
}
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
2007-07-18 00:29:39 +04:00
|
|
|
|
}
|
2007-05-18 06:06:59 +04:00
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
void
|
|
|
|
|
nsToolkit::UnregisterAllProcessMouseEventHandlers()
|
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
2007-07-18 00:29:39 +04:00
|
|
|
|
if (mEventTapRLS) {
|
|
|
|
|
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
|
|
|
|
|
kCFRunLoopDefaultMode);
|
|
|
|
|
CFRelease(mEventTapRLS);
|
2012-07-30 18:20:58 +04:00
|
|
|
|
mEventTapRLS = nullptr;
|
2007-07-18 00:29:39 +04:00
|
|
|
|
}
|
|
|
|
|
if (mEventTapPort) {
|
2009-01-08 02:03:56 +03:00
|
|
|
|
// mEventTapPort must be invalidated as well as released. Otherwise the
|
|
|
|
|
// event tap doesn't get destroyed until the browser process ends (it
|
|
|
|
|
// keeps showing up in the list returned by CGGetEventTapList()).
|
|
|
|
|
CFMachPortInvalidate(mEventTapPort);
|
2007-07-18 00:29:39 +04:00
|
|
|
|
CFRelease(mEventTapPort);
|
2012-07-30 18:20:58 +04:00
|
|
|
|
mEventTapPort = nullptr;
|
2007-07-18 00:29:39 +04:00
|
|
|
|
}
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
2007-05-18 06:06:59 +04:00
|
|
|
|
}
|
|
|
|
|
|
2011-10-25 19:05:32 +04:00
|
|
|
|
// Return the nsToolkit instance. If a toolkit does not yet exist, then one
|
|
|
|
|
// will be created.
|
|
|
|
|
// static
|
|
|
|
|
nsToolkit* nsToolkit::GetToolkit()
|
2006-12-08 11:29:46 +03:00
|
|
|
|
{
|
2011-10-25 19:05:32 +04:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
2011-10-25 19:05:32 +04:00
|
|
|
|
if (!gToolkit) {
|
|
|
|
|
gToolkit = new nsToolkit();
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
2011-10-25 19:05:32 +04:00
|
|
|
|
return gToolkit;
|
|
|
|
|
|
2012-07-30 18:20:58 +04:00
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
|
2006-12-08 11:29:46 +03:00
|
|
|
|
}
|
|
|
|
|
|
2008-02-13 18:57:12 +03:00
|
|
|
|
// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
|
|
|
|
|
// Leopard and is available to 64-bit binaries on Leopard and above. Based on
|
|
|
|
|
// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
|
|
|
|
|
// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
|
|
|
|
|
// have to switch to using accessor methods like method_exchangeImplementations()
|
|
|
|
|
// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
|
|
|
|
|
// and above). But these accessor methods aren't available in Objective-C 1
|
|
|
|
|
// (or on Tiger). So we need to access Method's members directly for (Tiger-
|
|
|
|
|
// capable) binaries (32-bit or 64-bit) that use Objective-C 1 (as long as we
|
|
|
|
|
// keep supporting Tiger).
|
|
|
|
|
//
|
|
|
|
|
// Be aware that, if aClass doesn't have an orgMethod selector but one of its
|
|
|
|
|
// superclasses does, the method substitution will (in effect) take place in
|
|
|
|
|
// that superclass (rather than in aClass itself). The substitution has
|
|
|
|
|
// effect on the class where it takes place and all of that class's
|
|
|
|
|
// subclasses. In order for method swizzling to work properly, posedMethod
|
|
|
|
|
// needs to be unique in the class where the substitution takes place and all
|
|
|
|
|
// of its subclasses.
|
2008-03-27 19:30:13 +03:00
|
|
|
|
nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
|
2011-09-29 10:19:26 +04:00
|
|
|
|
bool classMethods)
|
2008-02-13 18:57:12 +03:00
|
|
|
|
{
|
2008-02-21 02:47:05 +03:00
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
|
|
|
|
|
2008-03-27 19:30:13 +03:00
|
|
|
|
Method original = nil;
|
|
|
|
|
Method posed = nil;
|
|
|
|
|
|
|
|
|
|
if (classMethods) {
|
|
|
|
|
original = class_getClassMethod(aClass, orgMethod);
|
|
|
|
|
posed = class_getClassMethod(aClass, posedMethod);
|
|
|
|
|
} else {
|
|
|
|
|
original = class_getInstanceMethod(aClass, orgMethod);
|
|
|
|
|
posed = class_getInstanceMethod(aClass, posedMethod);
|
|
|
|
|
}
|
2008-02-13 18:57:12 +03:00
|
|
|
|
|
|
|
|
|
if (!original || !posed)
|
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
|
2009-08-13 02:32:41 +04:00
|
|
|
|
#ifdef __LP64__
|
|
|
|
|
method_exchangeImplementations(original, posed);
|
|
|
|
|
#else
|
2008-02-13 18:57:12 +03:00
|
|
|
|
IMP aMethodImp = original->method_imp;
|
|
|
|
|
original->method_imp = posed->method_imp;
|
|
|
|
|
posed->method_imp = aMethodImp;
|
2009-08-13 02:32:41 +04:00
|
|
|
|
#endif
|
2008-02-13 18:57:12 +03:00
|
|
|
|
|
|
|
|
|
return NS_OK;
|
2008-02-21 02:47:05 +03:00
|
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
2008-02-13 18:57:12 +03:00
|
|
|
|
}
|
2010-04-05 18:04:49 +04:00
|
|
|
|
|
|
|
|
|
#ifndef __LP64__
|
|
|
|
|
|
|
|
|
|
void ScanImportedFunctions(const struct mach_header* mh, intptr_t vmaddr_slide);
|
|
|
|
|
|
|
|
|
|
int gInWebInitForCarbonLevel = 0;
|
|
|
|
|
|
|
|
|
|
void Hooked_WebInitForCarbon();
|
|
|
|
|
OSStatus Hooked_InstallEventLoopIdleTimer(
|
|
|
|
|
EventLoopRef inEventLoop,
|
|
|
|
|
EventTimerInterval inDelay,
|
|
|
|
|
EventTimerInterval inInterval,
|
|
|
|
|
EventLoopIdleTimerUPP inTimerProc,
|
|
|
|
|
void *inTimerData,
|
|
|
|
|
EventLoopTimerRef *outTimer
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
void (*WebKit_WebInitForCarbon)() = NULL;
|
|
|
|
|
OSStatus (*HIToolbox_InstallEventLoopIdleTimer)(
|
|
|
|
|
EventLoopRef inEventLoop,
|
|
|
|
|
EventTimerInterval inDelay,
|
|
|
|
|
EventTimerInterval inInterval,
|
|
|
|
|
EventLoopIdleTimerUPP inTimerProc,
|
|
|
|
|
void *inTimerData,
|
|
|
|
|
EventLoopTimerRef *outTimer
|
|
|
|
|
) = NULL;
|
|
|
|
|
|
|
|
|
|
typedef struct _nsHookedFunctionSpec {
|
|
|
|
|
const char *name; // Includes leading underscore
|
|
|
|
|
void *newAddress;
|
|
|
|
|
void **oldAddressPtr;
|
|
|
|
|
} nsHookedFunctionSpec;
|
|
|
|
|
|
|
|
|
|
nsHookedFunctionSpec gHookedFunctions[] = {
|
|
|
|
|
{"_WebInitForCarbon", (void *) Hooked_WebInitForCarbon,
|
|
|
|
|
(void **) &WebKit_WebInitForCarbon},
|
|
|
|
|
{"_InstallEventLoopIdleTimer", (void *) Hooked_InstallEventLoopIdleTimer,
|
|
|
|
|
(void **) &HIToolbox_InstallEventLoopIdleTimer},
|
|
|
|
|
{NULL, NULL, NULL}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Plugins may exist that use the WebKit framework. Those that are
|
|
|
|
|
// Carbon-based need to call WebKit's WebInitForCarbon() method. There
|
|
|
|
|
// currently appears to be only one Carbon WebKit plugin --
|
|
|
|
|
// DivXBrowserPlugin (included with the DivX Web Player,
|
|
|
|
|
// http://www.divx.com/en/downloads/divx/mac). See bug 509130.
|
|
|
|
|
//
|
|
|
|
|
// The source-code for WebInitForCarbon() is in the WebKit source tree's
|
|
|
|
|
// WebKit/mac/Carbon/CarbonUtils.mm file. Among other things it installs
|
|
|
|
|
// an idle timer on the main event loop, whose target is the PoolCleaner()
|
|
|
|
|
// function (also in CarbonUtils.mm). WebInitForCarbon() allocates an
|
|
|
|
|
// NSAutoreleasePool object which it stores in the global sPool variable.
|
|
|
|
|
// PoolCleaner() periodically releases/drains sPool and creates another
|
|
|
|
|
// NSAutoreleasePool object to take its place. The intention is to ensure
|
|
|
|
|
// an autorelease pool is in place for whatever Objective-C code may be
|
|
|
|
|
// called by WebKit code, and that it periodically gets "cleaned". But we're
|
|
|
|
|
// already doing this ourselves. And PoolCleaner()'s periodic cleaning has a
|
|
|
|
|
// very bad effect on us -- it causes objects to be deleted prematurely, so
|
|
|
|
|
// that attempts to access them cause crashes. This is probably because, when
|
|
|
|
|
// WebInitForCarbon() is called from a plugin, one or more autorelease pools
|
|
|
|
|
// are already in place.
|
|
|
|
|
//
|
|
|
|
|
// To get around this we hook/subclass WebInitForCarbon() and
|
|
|
|
|
// InstallEventLoopIdleTimer() and make the latter return without doing
|
|
|
|
|
// anything when called from the former. This stops WebInitForCarbon()'s
|
|
|
|
|
// (useless and harmful) idle timer from ever being installed.
|
|
|
|
|
//
|
|
|
|
|
// PoolCleaner() only "works" if the autorelease pool count (returned by
|
|
|
|
|
// WKGetNSAutoreleasePoolCount(), stored in numPools) is the same as when
|
|
|
|
|
// sPool was last set. But WKGetNSAutoreleasePoolCount() only works on OS X
|
|
|
|
|
// 10.5 and below. So PoolCleaner() always fails 10.6 and above, and we
|
|
|
|
|
// needn't do anything there.
|
|
|
|
|
//
|
|
|
|
|
// WKGetNSAutoreleasePoolCount() is a thin wrapper around the following code:
|
|
|
|
|
//
|
|
|
|
|
// unsigned count = NSPushAutoreleasePool(0);
|
|
|
|
|
// NSPopAutoreleasePool(count);
|
|
|
|
|
// return count;
|
|
|
|
|
//
|
|
|
|
|
// NSPushAutoreleasePool() and NSPopAutoreleasePool() are undocumented
|
|
|
|
|
// functions from the Foundation framework. On OS X 10.5.X and below their
|
|
|
|
|
// declarations are (as best I can tell) as follows. ('capacity' is
|
|
|
|
|
// presumably the initial capacity, in number of items, of the autorelease
|
|
|
|
|
// pool to be created.)
|
|
|
|
|
//
|
|
|
|
|
// unsigned NSPushAutoreleasePool(unsigned capacity);
|
|
|
|
|
// void NSPopAutoreleasePool(unsigned offset);
|
|
|
|
|
//
|
|
|
|
|
// But as of OS X 10.6 these functions appear to have changed as follows:
|
|
|
|
|
//
|
|
|
|
|
// AutoreleasePool *NSPushAutoreleasePool(unsigned capacity);
|
|
|
|
|
// void NSPopAutoreleasePool(AutoreleasePool *aPool);
|
|
|
|
|
|
|
|
|
|
void Hooked_WebInitForCarbon()
|
|
|
|
|
{
|
|
|
|
|
++gInWebInitForCarbonLevel;
|
|
|
|
|
WebKit_WebInitForCarbon();
|
|
|
|
|
--gInWebInitForCarbonLevel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OSStatus Hooked_InstallEventLoopIdleTimer(
|
|
|
|
|
EventLoopRef inEventLoop,
|
|
|
|
|
EventTimerInterval inDelay,
|
|
|
|
|
EventTimerInterval inInterval,
|
|
|
|
|
EventLoopIdleTimerUPP inTimerProc,
|
|
|
|
|
void *inTimerData,
|
|
|
|
|
EventLoopTimerRef *outTimer
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
OSStatus rv = noErr;
|
|
|
|
|
if (gInWebInitForCarbonLevel <= 0) {
|
|
|
|
|
rv = HIToolbox_InstallEventLoopIdleTimer(inEventLoop, inDelay, inInterval,
|
|
|
|
|
inTimerProc, inTimerData, outTimer);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to hook (or "subclass") the dynamically bound functions specified in
|
|
|
|
|
// gHookedFunctions. We don't hook these functions at their "original"
|
|
|
|
|
// addresses, so we can only "subclass" calls to them from modules other than
|
|
|
|
|
// the one in which they're defined. Of course, this only works for globally
|
|
|
|
|
// accessible functions.
|
|
|
|
|
void HookImportedFunctions()
|
|
|
|
|
{
|
|
|
|
|
// We currently only need to do anything on Tiger or Leopard.
|
2012-05-17 11:53:20 +04:00
|
|
|
|
if (nsCocoaFeatures::OnSnowLeopardOrLater())
|
2010-04-05 18:04:49 +04:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// _dyld_register_func_for_add_image() makes the dynamic linker runtime call
|
|
|
|
|
// ScanImportedFunctions() "once for each of the images that are currently
|
|
|
|
|
// loaded into the program" (including the main image, i.e. firefox-bin).
|
|
|
|
|
// When a new image is added (e.g. a plugin), ScanImportedFunctions() is
|
|
|
|
|
// called again with data for that image.
|
|
|
|
|
//
|
|
|
|
|
// Calling HookImportedFunctions() from loadHandler's constructor (i.e. as
|
|
|
|
|
// the current module is being loaded) minimizes the likelihood that the
|
|
|
|
|
// imported functions in the already-loaded images will get called while
|
|
|
|
|
// we're resetting their pointers.
|
|
|
|
|
//
|
|
|
|
|
// _dyld_register_func_for_add_image()'s behavior when a new image is added
|
|
|
|
|
// allows us to reset its imported functions' pointers before they ever get
|
|
|
|
|
// called.
|
|
|
|
|
_dyld_register_func_for_add_image(ScanImportedFunctions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct segment_command *GetSegmentFromMachHeader(const struct mach_header* mh,
|
|
|
|
|
const char *segname,
|
|
|
|
|
uint32_t *numFollowingCommands)
|
|
|
|
|
{
|
|
|
|
|
if (numFollowingCommands)
|
|
|
|
|
*numFollowingCommands = 0;
|
|
|
|
|
uint32_t numCommands = mh->ncmds;
|
|
|
|
|
struct segment_command *aCommand = (struct segment_command *)
|
|
|
|
|
((uint32_t)mh + sizeof(struct mach_header));
|
|
|
|
|
for (uint32_t i = 1; i <= numCommands; ++i) {
|
|
|
|
|
if (aCommand->cmd != LC_SEGMENT)
|
|
|
|
|
return NULL;
|
|
|
|
|
if (strcmp(segname, aCommand->segname) == 0) {
|
|
|
|
|
if (numFollowingCommands)
|
|
|
|
|
*numFollowingCommands = numCommands-i;
|
|
|
|
|
return aCommand;
|
|
|
|
|
}
|
|
|
|
|
aCommand = (struct segment_command *)
|
|
|
|
|
((uint32_t)aCommand + aCommand->cmdsize);
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Scan through parts of the "indirect symbol table" for imported functions
|
|
|
|
|
// (functions dynamically bound from another module) whose names match those
|
|
|
|
|
// we're trying to hook. If we find one, change the corresponding pointer/
|
|
|
|
|
// instruction in a "jump table" or "lazy pointer array" to point at the
|
|
|
|
|
// function's replacement. It appears we only need to look at "lazy bound"
|
|
|
|
|
// symbols -- non-"lazy" symbols seem to always be for (imported) data. (A
|
|
|
|
|
// lazy bound symbol is one that's only resolved on first "use".)
|
|
|
|
|
//
|
|
|
|
|
// Most of what we do here is documented by Apple
|
|
|
|
|
// (http://developer.apple.com/Mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html,
|
|
|
|
|
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Reference/MachOReference/Reference/reference.html).
|
|
|
|
|
// When Apple doesn't explicitly document something (e.g. the format of the
|
|
|
|
|
// __LINKEDIT segment or the indirect symbol table), you can often get "hints"
|
|
|
|
|
// from the output of 'otool -l' or 'otool -I". And sometimes mach-o header
|
|
|
|
|
// files contain additional information -- for example the format of the
|
|
|
|
|
// indirect symbol table is described in the comment above the definitions of
|
|
|
|
|
// INDIRECT_SYMBOL_LOCAL and INDIRECT_SYMBOL_ABS in mach-o/loader.h.
|
|
|
|
|
//
|
|
|
|
|
// The "__jump_table" section of the "__IMPORT" segment is an array of
|
|
|
|
|
// assembler JMP or CALL instructions. It's only present in i386 binaries
|
|
|
|
|
// (ppc and x86_64 binaries use arrays of pointers). Each instruction is
|
|
|
|
|
// 5 bytes long. The format is a byte-length opcode (0xE9 for JMP, 0xE8 for
|
|
|
|
|
// CALL) followed by a four-byte relative address (relative to the start of
|
|
|
|
|
// the next instruction in the table). All the CALL instructions point to the
|
|
|
|
|
// same code -- a 'dyld_stub_binding_helper()' that somehow locates the lazy-
|
|
|
|
|
// bound function and replaces the CALL instruction with a JMP instruction
|
|
|
|
|
// to the appropriate function. If we replace the CALL instruction ourselves,
|
|
|
|
|
// dyld_stub_binding_helper() never gets called (and never needs to be).
|
|
|
|
|
void ScanImportedFunctions(const struct mach_header* mh, intptr_t vmaddr_slide)
|
|
|
|
|
{
|
|
|
|
|
// While we're looking through all our images/modules, also scan for the
|
|
|
|
|
// original addresses of the functions we plan to hook. Though
|
|
|
|
|
// NSLookupSymbolInImage() is deprecated (along with the entire NSModule
|
|
|
|
|
// API), it's by far the best (and most efficient) way to do what we need
|
|
|
|
|
// to do here (scan for the original addresses of symbols that aren't all
|
|
|
|
|
// loaded at the same time). It's still available to 64-bit apps on OS X
|
|
|
|
|
// 10.6.X.
|
|
|
|
|
for (uint32_t i = 0; gHookedFunctions[i].name; ++i) {
|
|
|
|
|
// Since a symbol might be defined more than once, we record only its
|
|
|
|
|
// "first" address.
|
|
|
|
|
if (*gHookedFunctions[i].oldAddressPtr)
|
|
|
|
|
continue;
|
|
|
|
|
NSSymbol symbol =
|
|
|
|
|
NSLookupSymbolInImage(mh, gHookedFunctions[i].name,
|
|
|
|
|
NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR);
|
|
|
|
|
if (symbol)
|
|
|
|
|
*gHookedFunctions[i].oldAddressPtr = NSAddressOfSymbol(symbol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t numFollowingCommands = 0;
|
|
|
|
|
struct segment_command *linkeditSegment =
|
|
|
|
|
GetSegmentFromMachHeader(mh, "__LINKEDIT", &numFollowingCommands);
|
|
|
|
|
if (!linkeditSegment)
|
|
|
|
|
return;
|
|
|
|
|
uint32_t fileoffIncrement = linkeditSegment->vmaddr - linkeditSegment->fileoff;
|
|
|
|
|
|
|
|
|
|
struct symtab_command *symtab =
|
|
|
|
|
(struct symtab_command *)((uint32_t)linkeditSegment + linkeditSegment->cmdsize);
|
|
|
|
|
for (uint32_t i = 1;; ++i) {
|
|
|
|
|
if (symtab->cmd == LC_SYMTAB)
|
|
|
|
|
break;
|
|
|
|
|
if (i == numFollowingCommands)
|
|
|
|
|
return;
|
|
|
|
|
symtab = (struct symtab_command *) ((uint32_t)symtab + symtab->cmdsize);
|
|
|
|
|
}
|
|
|
|
|
uint32_t symbolTableOffset = symtab->symoff + fileoffIncrement + vmaddr_slide;
|
|
|
|
|
uint32_t stringTableOffset = symtab->stroff + fileoffIncrement + vmaddr_slide;
|
|
|
|
|
|
|
|
|
|
struct dysymtab_command *dysymtab =
|
|
|
|
|
(struct dysymtab_command *)((uint32_t)symtab + symtab->cmdsize);
|
|
|
|
|
if (dysymtab->cmd != LC_DYSYMTAB)
|
|
|
|
|
return;
|
|
|
|
|
uint32_t indirectSymbolTableOffset =
|
|
|
|
|
dysymtab->indirectsymoff + fileoffIncrement + vmaddr_slide;
|
|
|
|
|
|
|
|
|
|
// Some i386 binaries on OS X 10.6.X use a __la_symbol_ptr section (in the
|
|
|
|
|
// __DATA segment) instead of a __jump_table section (in the __IMPORT
|
|
|
|
|
// segment).
|
|
|
|
|
const struct section *lazySymbols = NULL;
|
|
|
|
|
#ifdef __i386__
|
|
|
|
|
struct segment_command *importSegment =
|
|
|
|
|
GetSegmentFromMachHeader(mh, "__IMPORT", nil);
|
|
|
|
|
const struct section *jumpTable =
|
|
|
|
|
getsectbynamefromheader(mh, "__IMPORT", "__jump_table");
|
|
|
|
|
if (!jumpTable)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
lazySymbols = getsectbynamefromheader(mh, "__DATA", "__la_symbol_ptr");
|
|
|
|
|
if (!lazySymbols)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
uint32_t numLazySymbols = 0;
|
|
|
|
|
uint32_t lazyBytes = 0;
|
|
|
|
|
unsigned char *lazy = NULL;
|
|
|
|
|
#ifdef __i386__
|
|
|
|
|
uint32_t numJumpTableStubs = 0;
|
|
|
|
|
uint32_t stubsBytes = 0;
|
|
|
|
|
unsigned char *stubs = NULL;
|
|
|
|
|
vm_prot_t importSegProt = VM_PROT_NONE;
|
|
|
|
|
if (jumpTable) {
|
|
|
|
|
// Bail if we don't have an __IMPORT segment (which shouldn't be possible,
|
|
|
|
|
// but just in case).
|
|
|
|
|
if (!importSegment)
|
|
|
|
|
return;
|
|
|
|
|
importSegProt = importSegment->initprot;
|
|
|
|
|
// Bail if the size of each entry in the "jump table" isn't 5 bytes.
|
|
|
|
|
if (jumpTable->reserved2 != 5)
|
|
|
|
|
return;
|
|
|
|
|
numJumpTableStubs = jumpTable->size/5;
|
|
|
|
|
indirectSymbolTableOffset += jumpTable->reserved1*sizeof(uint32_t);
|
|
|
|
|
stubs = (unsigned char *)
|
|
|
|
|
(getsectdatafromheader(mh, "__IMPORT", "__jump_table", &stubsBytes) + vmaddr_slide);
|
|
|
|
|
// Bail if (for some reason) these figures don't agree.
|
|
|
|
|
if (stubsBytes != jumpTable->size)
|
|
|
|
|
return;
|
|
|
|
|
} else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
numLazySymbols = lazySymbols->size/4;
|
|
|
|
|
indirectSymbolTableOffset += lazySymbols->reserved1*sizeof(uint32_t);
|
|
|
|
|
lazy = (unsigned char *)
|
|
|
|
|
(getsectdatafromheader(mh, "__DATA", "__la_symbol_ptr", &lazyBytes) + vmaddr_slide);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t items = 0;
|
|
|
|
|
#ifdef __i386__
|
|
|
|
|
if (jumpTable) {
|
|
|
|
|
items = numJumpTableStubs;
|
|
|
|
|
// If the __IMPORT segment is read-only, we'll need to make it writeable
|
|
|
|
|
// before trying to change entries in its jump table. Below we restore
|
|
|
|
|
// its original level of protection.
|
|
|
|
|
if (!(importSegProt & VM_PROT_WRITE)) {
|
|
|
|
|
void *protAddr = (void *) (importSegment->vmaddr + vmaddr_slide);
|
|
|
|
|
size_t protSize = importSegment->vmsize;
|
|
|
|
|
vm_protect(mach_task_self(), (vm_address_t) protAddr, protSize, NO,
|
|
|
|
|
importSegProt | VM_PROT_WRITE);
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
items = numLazySymbols;
|
|
|
|
|
}
|
|
|
|
|
uint32_t *indirectSymbolTableItem = (uint32_t *) indirectSymbolTableOffset;
|
|
|
|
|
for (uint32_t i = 0; i < items; ++i, ++indirectSymbolTableItem) {
|
|
|
|
|
// Skip indirect symbol table items that are 0x80000000 (for a local
|
|
|
|
|
// symbol) and/or 0x40000000 (for an absolute symbol). See
|
|
|
|
|
// mach-o/loader.h.
|
|
|
|
|
if (0xF0000000 & *indirectSymbolTableItem)
|
|
|
|
|
continue;
|
|
|
|
|
struct nlist *symbolTableItem = (struct nlist *)
|
|
|
|
|
(symbolTableOffset + *indirectSymbolTableItem*sizeof(struct nlist));
|
|
|
|
|
char *stringTableItem = (char *) (stringTableOffset + symbolTableItem->n_un.n_strx);
|
|
|
|
|
|
|
|
|
|
for (uint32_t j = 0; gHookedFunctions[j].name; ++j) {
|
|
|
|
|
if (strcmp(stringTableItem, gHookedFunctions[j].name) != 0)
|
|
|
|
|
continue;
|
|
|
|
|
#ifdef __i386__
|
|
|
|
|
if (jumpTable) {
|
|
|
|
|
unsigned char *opcodeAddr = stubs + (i * 5);
|
|
|
|
|
int32_t *displacementAddr = (int32_t *) (opcodeAddr + 1);
|
|
|
|
|
int32_t eip = (int32_t) stubs + (i + 1) * 5;
|
|
|
|
|
int32_t displacement = (int32_t) (gHookedFunctions[j].newAddress) - eip;
|
|
|
|
|
displacementAddr[0] = displacement;
|
|
|
|
|
opcodeAddr[0] = 0xE9;
|
|
|
|
|
} else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
int32_t *lazySymbolAddr = (int32_t *) (lazy + (i * 4));
|
|
|
|
|
lazySymbolAddr[0] = (int32_t) (gHookedFunctions[j].newAddress);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef __i386__
|
|
|
|
|
// If we needed to make an __IMPORT segment writeable above, restore its
|
|
|
|
|
// original protection level here.
|
|
|
|
|
if (jumpTable && !(importSegProt & VM_PROT_WRITE)) {
|
|
|
|
|
void *protAddr = (void *) (importSegment->vmaddr + vmaddr_slide);
|
|
|
|
|
size_t protSize = importSegment->vmsize;
|
|
|
|
|
vm_protect(mach_task_self(), (vm_address_t) protAddr, protSize,
|
|
|
|
|
NO, importSegProt);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class loadHandler
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
loadHandler();
|
|
|
|
|
~loadHandler() {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadHandler::loadHandler()
|
|
|
|
|
{
|
|
|
|
|
// Calling HookImportedFunctions() from here (i.e. as the current module is
|
|
|
|
|
// being loaded) minimizes the likelihood that the imported functions in
|
|
|
|
|
// the already-loaded images will get called while we're resetting their
|
|
|
|
|
// pointers.
|
|
|
|
|
HookImportedFunctions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadHandler handler = loadHandler();
|
|
|
|
|
|
|
|
|
|
#endif // __LP64__
|