@@ -556,15 +556,85 @@ FILE* popen(const char* cmd, const char* /*mode*/) {
556556
557557int pclose (FILE *) { return -1 ; }
558558
559+ // ── A minimal DisplayService, because the panel-blank bug was INVISIBLE without one ─────────────
560+ //
561+ // Everything else here degrades honestly when dlopen returns null: the app takes its "service not
562+ // available" path and the scenario still means something. The backlight is the exception, because
563+ // the service is not an optional extra there — it is half of the operation.
564+ //
565+ // `set_backlight`'s own comment has recorded since 2026-08-19 that writing 0 to the sysfs node does
566+ // NOT darken the glass (node at 0, service at 2, panel still lit). Both blank paths — the idle
567+ // timeout and the Power button — wrote only the node. With dlopen stubbed to null the harness saw
568+ // the node go to "0" and would have called that a pass, which is exactly the shape of the bug: the
569+ // observable half worked and the half that mattered was missing.
570+ //
571+ // So: enough of a DisplayServiceClient to be CALLED and COUNTED. Two vtable slots, matching
572+ // main.cpp's VIDX_SetLCDBacklightBrightness = 11 and VIDX_GetLCDBacklightBrightness = 12. The
573+ // remembered level starts at 2, which is the level the real service was measured reporting.
574+ static unsigned g_fake_bl_level = 2 ;
575+
576+ static void fake_bl_set (void *, const unsigned * level) {
577+ Lock l; ensure ();
578+ g_fake_bl_level = level ? *level : 0 ;
579+ g_trace->push_back (Call{intern (" display:SetLCDBacklightBrightness" ),
580+ (long long )g_fake_bl_level, g_now_ms});
581+ }
582+ static void fake_bl_get (void *, unsigned * out) {
583+ Lock l; ensure ();
584+ if (out) *out = g_fake_bl_level;
585+ g_trace->push_back (Call{intern (" display:GetLCDBacklightBrightness" ),
586+ (long long )g_fake_bl_level, g_now_ms});
587+ }
588+
589+ // EVERY slot gets a body, not just the two this fake models.
590+ //
591+ // The first version filled only 11 and 12 and left the rest null — and the very first run
592+ // segfaulted with PC=0x00000000, because ONE DisplayServiceClient is shared by the backlight AND
593+ // the touch-panel switch (main.cpp says so where it builds the client): `touch_set_sleep` calls
594+ // slot 13, SetTouchPanelValidate, on the same object. A partially-filled vtable is a worse fake
595+ // than no fake at all — it turns "this service is unavailable" into a crash in code that was
596+ // correct. So the table is filled with a recording no-op and the modelled slots overwrite it.
597+ static void * g_fake_display_vtbl[32 ];
598+ static void * g_fake_display_obj[2 ];
599+
600+ // Accepts and ignores its arguments. The real methods here are all `void(void*, const T*)`, and a
601+ // callee that never reads its arguments is safe to reach with any of those shapes.
602+ static void fake_display_noop (void *, const void *) {}
603+
604+ static void * fake_display_create (void ) {
605+ for (unsigned i = 0 ; i < sizeof g_fake_display_vtbl / sizeof *g_fake_display_vtbl; ++i)
606+ g_fake_display_vtbl[i] = (void *)&fake_display_noop;
607+ g_fake_display_vtbl[11 ] = (void *)&fake_bl_set; // VIDX_SetLCDBacklightBrightness
608+ g_fake_display_vtbl[12 ] = (void *)&fake_bl_get; // VIDX_GetLCDBacklightBrightness
609+ // 13 = SetTouchPanelValidate — left as the no-op, but it must not be NULL.
610+ g_fake_display_obj[0 ] = (void *)g_fake_display_vtbl; // the vptr main.cpp reads
611+ return (void *)g_fake_display_obj;
612+ }
613+
614+ // A distinct non-null cookie so dlsym can tell which library it is being asked about.
615+ static int g_display_handle_cookie = 0 ;
616+
617+ // / Current fake backlight level, for scenarios to assert on.
618+ int cinder_harness_display_backlight (void ) { return (int )g_fake_bl_level; }
619+
559620void * dlopen (const char * path, int ) {
560621 Lock l; ensure ();
561- g_trace->push_back (Call{intern ((std::string (" dlopen:" ) + (path ? path : " ?" )).c_str ()), 0 , g_now_ms});
622+ const std::string p = path ? path : " ?" ;
623+ g_trace->push_back (Call{intern ((std::string (" dlopen:" ) + p).c_str ()), 0 , g_now_ms});
624+ // Only this one is faked; every other Sony .so still returns null so the optional-service
625+ // paths keep being exercised in their degraded form, which is what they are here to prove.
626+ if (p == " libDisplayService.so" ) return (void *)&g_display_handle_cookie;
562627 return nullptr ; // no Sony .so on a build machine — the optional-service paths degrade
563628}
564629
565- void * dlsym (void *, const char * sym) {
630+ void * dlsym (void * h , const char * sym) {
566631 Lock l; ensure ();
567- g_trace->push_back (Call{intern ((std::string (" dlsym:" ) + (sym ? sym : " ?" )).c_str ()), 0 , g_now_ms});
632+ const std::string s = sym ? sym : " ?" ;
633+ g_trace->push_back (Call{intern ((std::string (" dlsym:" ) + s).c_str ()), 0 , g_now_ms});
634+ if (h == (void *)&g_display_handle_cookie &&
635+ s == " _ZN3pst8services27DisplayServiceClientFactory14CreateInstanceEv" ) {
636+ return (void *)&fake_display_create;
637+ }
568638 return nullptr ;
569639}
570640
0 commit comments