Tencent/matrix · error · IllegalStateException

get service manager ClassLoader fail!

Error message

get service manager ClassLoader fail!

What it means

createServiceManagerProxy loads a system service class and its $Stub inner class, then needs the Stub's ClassLoader to build a dynamic proxy implementing IBinder/IInterface/the service interface. A null ClassLoader would make Proxy.newProxyInstance fail, so it throws IllegalStateException 'get service manager ClassLoader fail!' as an environment-invariant guard.

Solutions

  1. Verify serviceClassName is correct for the target API level (e.g. android.app.INotificationManager vs older names); a wrong class name yields a bogus Stub.
  2. Fall back to the caller's ClassLoader (SystemServiceBinderHooker.class.getClassLoader() or IBinder.class.getClassLoader()) when null.
  3. Guard the hook with a try-catch and skip hooking on devices where it fails; the library degrades to un-hooked monitoring.
  4. Check that the service actually exists before hooking (ServiceManager.getService != null).

Example fix

// before
ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
if (classLoader == null) {
    throw new IllegalStateException("get service manager ClassLoader fail!");
}
// after
ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
if (classLoader == null) {
    classLoader = SystemServiceBinderHooker.class.getClassLoader();
}
if (classLoader == null) {
    return originBinder; // skip hook
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (serviceClassName == null || !serviceClassName.contains("$Stub") == false) {
    try { Class.forName(serviceClassName + "$Stub"); } catch (Throwable t) { return originBinder; }
}

Type guard

static boolean serviceStubExists(String serviceClassName) {
    try { Class.forName(serviceClassName + "$Stub"); return true; }
    catch (Throwable t) { return false; }
}

Try / catch

try {
    Object proxy = createServiceManagerProxy(serviceClassName, originBinder, callback);
} catch (Exception e) {
    Log.w(TAG, "service hook failed for " + serviceClassName + ", skipping", e);
    proxy = originBinder;
}

Prevention

When it happens

Trigger: Hooking a system service where Class.forName(serviceClassName + "$Stub").getClassLoader() returns null — serviceClassName pointing to a wrong/hidden service class, bootstrap-classloader services on modified runtimes, or mock environments (Robolectric) where Stub classes behave differently.

Common situations: Passing an incorrect serviceClassName (typo or service absent on that API level); OEM ROMs moving services to different classloaders; hooking frameworks that relocate classes.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08). Data as JSON: /api/errors/847b51ecf38ef154. Report an issue: GitHub.

Appendix: source

Thrown at matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/SystemServiceBinderHooker.java:160

                    new Class<?>[]{IBinder.class},
                    this
            );
        }

        @SuppressWarnings({"PrivateApi"})
        static IBinder getCurrentBinder(String serviceName) throws Exception {
            Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
            Method getService = serviceManagerCls.getDeclaredMethod("getService", String.class);
            return  (IBinder) getService.invoke(null, serviceName);
        }

        @SuppressWarnings({"PrivateApi"})
        private static Object createServiceManagerProxy(String serviceClassName, IBinder originBinder, final HookCallback callback) throws Exception  {
            Class<?> serviceManagerCls = Class.forName(serviceClassName);
            Class<?> serviceManagerStubCls = Class.forName(serviceClassName + "$Stub");
            ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
            if (classLoader == null) {
                throw new IllegalStateException("get service manager ClassLoader fail!");
            }
            Method asInterfaceMethod = serviceManagerStubCls.getDeclaredMethod("asInterface", IBinder.class);
            final Object originManagerService = asInterfaceMethod.invoke(null, originBinder);
            return Proxy.newProxyInstance(classLoader,
                    new Class[]{IBinder.class, IInterface.class, serviceManagerCls},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (callback != null) {
                                callback.onServiceMethodInvoke(method, args);
                                Object result = callback.onServiceMethodIntercept(originManagerService, method, args);
                                if (result != null) {
                                    return result;
                                }
                            }
                            return method.invoke(originManagerService, args);
                        }
                    }

View on GitHub (pinned to 3b8293bd65)