ps: Retry without icons when icon loading fails

Some devices fail the whole full-scope enumeration when an app icon
cannot be loaded, e.g. Android raising Resources$NotFoundException for
sym_def_app_icon. Retry with the minimal scope and warn, so the listing
still works.

Refs frida/frida#3762
This commit is contained in:
tubibibo authored and Ole André Vadla Ravnås committed 2026-11-05 20:26:06 +01:00
1 parent 735a2770ce
commit 280fb9e2c8
2 files changed
+70 -2

No files matched your search

+22 -2
View File
@@ -79,7 +79,11 @@ def main() -> None:
try:
assert self._device is not None
processes = self._device.enumerate_processes(scope=scope)
processes = _enumerate_with_icon_fallback(
self._device.enumerate_processes,
scope,
lambda e: self._log("warning", f"Unable to load app icons ({e}), listing without them"),
)
except Exception as e:
self._update_status(f"Failed to enumerate processes: {e}")
self._exit(1)
@@ -128,7 +132,11 @@ def main() -> None:
try:
assert self._device is not None
applications = self._device.enumerate_applications(scope=scope)
applications = _enumerate_with_icon_fallback(
self._device.enumerate_applications,
scope,
lambda e: self._log("warning", f"Unable to load app icons ({e}), listing without them"),
)
except Exception as e:
self._update_status(f"Failed to enumerate applications: {e}")
self._exit(1)
@@ -287,6 +295,18 @@ def main() -> None:
app.run()
def _enumerate_with_icon_fallback(enumerate_fn, scope, warn):
if scope != "full":
return enumerate_fn(scope=scope)
try:
return enumerate_fn(scope="full")
except Exception as e:
result = enumerate_fn(scope="minimal")
warn(e)
return result
if __name__ == "__main__":
try:
main()
+48
View File
@@ -0,0 +1,48 @@
import unittest
from frida_tools.ps import _enumerate_with_icon_fallback
class TestPSEnumerationFallback(unittest.TestCase):
def test_full_scope_failure_is_retried_without_icons(self):
device = FakeDevice(fail_scopes={"full"})
warnings = []
processes = _enumerate_with_icon_fallback(device.enumerate_processes, "full", warnings.append)
self.assertEqual(processes, ["init"])
self.assertEqual(device.calls, ["full", "minimal"])
self.assertEqual(len(warnings), 1)
self.assertIn("could not load icons", str(warnings[0]))
def test_full_scope_success_is_returned_as_is(self):
device = FakeDevice(fail_scopes=set())
warnings = []
processes = _enumerate_with_icon_fallback(device.enumerate_processes, "full", warnings.append)
self.assertEqual(processes, ["init"])
self.assertEqual(device.calls, ["full"])
self.assertEqual(warnings, [])
def test_minimal_scope_failure_is_propagated(self):
device = FakeDevice(fail_scopes={"minimal"})
with self.assertRaises(RuntimeError):
_enumerate_with_icon_fallback(device.enumerate_processes, "minimal", lambda e: None)
self.assertEqual(device.calls, ["minimal"])
def test_failure_that_is_not_about_icons_is_not_reported_as_one(self):
device = FakeDevice(fail_scopes={"full", "minimal"})
warnings = []
with self.assertRaises(RuntimeError):
_enumerate_with_icon_fallback(device.enumerate_processes, "full", warnings.append)
self.assertEqual(device.calls, ["full", "minimal"])
self.assertEqual(warnings, [])
class FakeDevice:
def __init__(self, fail_scopes):
self.fail_scopes = fail_scopes
self.calls = []
def enumerate_processes(self, scope="minimal"):
self.calls.append(scope)
if scope in self.fail_scopes:
raise RuntimeError(f"could not load icons for {scope}")
return ["init"]