From facf3ee2b448b464b8b8cec53d2b7b83dad6c05f Mon Sep 17 00:00:00 2001 From: stransky Date: Mon, 27 Mar 2023 10:16:46 +0000 Subject: [PATCH] Bug 1787182 [Linux] Implement glxtest test as binary r=emilio glxtest is run later when Firefox already spawns threads. Recently glxtest runs in forked process which doesn't work correctly in multi-thread environment, so we need to move glxtest to different binary file and launch it as stand alone code. Differential Revision: https://phabricator.services.mozilla.com/D173486 --- browser/installer/package-manifest.in | 1 + toolkit/xre/{ => glxtest}/glxtest.cpp | 185 +++++++++++++------------- toolkit/xre/glxtest/moz.build | 20 +++ toolkit/xre/moz.build | 6 +- toolkit/xre/nsAppRunner.cpp | 45 ++++++- widget/gtk/GfxInfo.cpp | 2 +- 6 files changed, 154 insertions(+), 105 deletions(-) rename toolkit/xre/{ => glxtest}/glxtest.cpp (91%) create mode 100644 toolkit/xre/glxtest/moz.build diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index f722e1d03704..faa912f5ad70 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -160,6 +160,7 @@ #endif #ifdef MOZ_GTK @BINPATH@/vaapitest +@BINPATH@/glxtest #endif ; [Components] diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest/glxtest.cpp similarity index 91% rename from toolkit/xre/glxtest.cpp rename to toolkit/xre/glxtest/glxtest.cpp index 8cb188236e38..a82bcdd652f0 100644 --- a/toolkit/xre/glxtest.cpp +++ b/toolkit/xre/glxtest/glxtest.cpp @@ -24,16 +24,15 @@ #include #include #include +#include +#include +#include +#include #if defined(MOZ_ASAN) || defined(FUZZING) # include #endif -#include "mozilla/Unused.h" -#include "nsAppRunner.h" // for IsWaylandEnabled on IsX11EGLEnabled -#include "stdint.h" -#include "nsExceptionHandler.h" - #ifdef __SUNPRO_CC # include #endif @@ -44,11 +43,6 @@ # include #endif -#ifdef MOZ_WAYLAND -# include "mozilla/widget/mozwayland.h" -# include "prlink.h" -#endif - #ifdef MOZ_X11 // stuff from glx.h typedef struct __GLXcontextRec* GLXContext; @@ -167,6 +161,10 @@ typedef struct _drmDevice { #define EXIT_FAILURE_BUFFER_TOO_SMALL 2 +// Print VA-API test results to stdout and logging to stderr +#define OUTPUT_PIPE 1 +#define LOG_PIPE 2 + namespace mozilla { namespace widget { // the read end of the pipe, which will be used by GfxInfo @@ -176,16 +174,11 @@ extern pid_t glxtest_pid; } // namespace widget } // namespace mozilla -// the write end of the pipe, which we're going to write to -static int write_end_of_the_pipe = -1; - // our buffer, size and used length static char* glxtest_buf = nullptr; static int glxtest_bufsize = 0; static int glxtest_length = 0; -static char* glxtest_render_device_path = nullptr; - // C++ standard collides with C standard in that it doesn't allow casting void* // to function pointer types. So the work-around is to convert first to size_t. // http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ @@ -225,14 +218,9 @@ static void record_warning(const char* str) { } static void record_flush() { - mozilla::Unused << write(write_end_of_the_pipe, glxtest_buf, glxtest_length); - if (auto* debugFile = getenv("MOZ_GFX_DEBUG_FILE")) { - auto fd = open(debugFile, O_CREAT | O_WRONLY | O_TRUNC, - S_IRUSR | S_IRGRP | S_IROTH); - if (fd != -1) { - mozilla::Unused << write(fd, glxtest_buf, glxtest_length); - close(fd); - } + (void)write(OUTPUT_PIPE, glxtest_buf, glxtest_length); + if (getenv("MOZ_GFX_DEBUG")) { + (void)write(LOG_PIPE, glxtest_buf, glxtest_length); } } @@ -244,7 +232,6 @@ static int x_error_handler(Display*, XErrorEvent* ev) { ev->error_code, ev->request_code, ev->minor_code); record_flush(); _exit(EXIT_FAILURE); - return 0; } #endif @@ -254,24 +241,6 @@ static int x_error_handler(Display*, XErrorEvent* ev) { // care about leaking memory extern "C" { -static void close_logging() { - // we want to redirect to /dev/null stdout, stderr, and while we're at it, - // any PR logging file descriptors. To that effect, we redirect all positive - // file descriptors up to what open() returns here. In particular, 1 is stdout - // and 2 is stderr. - int fd = open("/dev/null", O_WRONLY); - for (int i = 1; i < fd; i++) { - dup2(fd, i); - } - close(fd); - - if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) { - const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set"; - mozilla::Unused << write(write_end_of_the_pipe, msg, strlen(msg)); - exit(EXIT_FAILURE); - } -} - #define PCI_FILL_IDENT 0x0001 #define PCI_FILL_CLASS 0x0020 #define PCI_BASE_CLASS_DISPLAY 0x03 @@ -363,7 +332,6 @@ static void get_pci_status() { #ifdef MOZ_WAYLAND static void set_render_device_path(const char* render_device_path) { record_value("DRM_RENDERDEVICE\n%s\n", render_device_path); - glxtest_render_device_path = strdup(render_device_path); } static bool device_has_name(const drmDevice* device, const char* name) { @@ -720,14 +688,26 @@ static void get_xrandr_info(Display* dpy) { Window root = RootWindow(dpy, DefaultScreen(dpy)); XRRProviderResources* pr = XRRGetProviderResources(dpy, root); + if (!pr) { + return; + } XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root); + if (!res) { + XRRFreeProviderResources(pr); + return; + } if (pr->nproviders != 0) { record_value("DDX_DRIVER\n"); for (int i = 0; i < pr->nproviders; i++) { XRRProviderInfo* info = XRRGetProviderInfo(dpy, res, pr->providers[i]); - record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";"); + if (info) { + record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";"); + XRRFreeProviderInfo(info); + } } } + XRRFreeScreenResources(res); + XRRFreeProviderResources(pr); } static void glxtest() { @@ -743,6 +723,7 @@ static void glxtest() { if (!glXGetProcAddress) { record_error("no glXGetProcAddress"); + dlclose(libgl); return; } @@ -779,6 +760,7 @@ static void glxtest() { !glXCreateContext || !glXMakeCurrent || !glXDestroyContext || !glGetString) { record_error(LIBGL_FILENAME " missing methods"); + dlclose(libgl); return; } @@ -786,12 +768,14 @@ static void glxtest() { Display* dpy = XOpenDisplay(nullptr); if (!dpy) { record_error("Unable to open a connection to the X server"); + dlclose(libgl); return; } ///// Check that the GLX extension is present ///// if (!glXQueryExtension(dpy, nullptr, nullptr)) { record_error("GLX extension missing"); + dlclose(libgl); return; } @@ -808,6 +792,7 @@ static void glxtest() { vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs2); if (!vInfo) { record_error("No visuals found"); + dlclose(libgl); return; } } @@ -949,9 +934,21 @@ static bool x11_egltest() { #ifdef MOZ_WAYLAND static void wayland_egltest() { + static auto sWlDisplayConnect = (struct wl_display * (*)(const char*)) + dlsym(RTLD_DEFAULT, "wl_display_connect"); + static auto sWlDisplayRoundtrip = + (int (*)(struct wl_display*))dlsym(RTLD_DEFAULT, "wl_display_roundtrip"); + static auto sWlDisplayDisconnect = (void (*)(struct wl_display*))dlsym( + RTLD_DEFAULT, "wl_display_disconnect"); + + if (!sWlDisplayConnect || !sWlDisplayRoundtrip || !sWlDisplayDisconnect) { + record_error("Missing Wayland libraries"); + return; + } + // NOTE: returns false to fall back to X11 when the Wayland socket doesn't // exist but fails with record_error if something actually went wrong - struct wl_display* dpy = wl_display_connect(nullptr); + struct wl_display* dpy = sWlDisplayConnect(nullptr); if (!dpy) { record_error("Could not connect to wayland socket"); return; @@ -963,14 +960,14 @@ static void wayland_egltest() { // This is enough to crash some broken NVIDIA prime + Wayland setups, see // https://github.com/NVIDIA/egl-wayland/issues/41 and bug 1768260. - wl_display_roundtrip(dpy); + sWlDisplayRoundtrip(dpy); - wl_display_disconnect(dpy); + sWlDisplayDisconnect(dpy); record_value("TEST_TYPE\nEGL\n"); } #endif -int childgltest() { +int childgltest(bool aWayland) { enum { bufsize = 2048 }; char buf[bufsize]; @@ -983,18 +980,18 @@ int childgltest() { get_pci_status(); #ifdef MOZ_WAYLAND - if (IsWaylandEnabled()) { + if (aWayland) { wayland_egltest(); - } else + } #endif - { #ifdef MOZ_X11 + if (!aWayland) { // TODO: --display command line argument is not properly handled if (!x11_egltest()) { glxtest(); } -#endif } +#endif // Finally write buffered data to the pipe. record_flush(); @@ -1009,44 +1006,48 @@ int childgltest() { } // extern "C" -/** \returns true in the child glxtest process, false in the parent process */ -bool fire_glxtest_process() { - int pfd[2]; - if (pipe(pfd) == -1) { - perror("pipe"); - return false; - } - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - close(pfd[0]); - close(pfd[1]); - return false; - } - // The child exits early to avoid running the full shutdown sequence and avoid - // conflicting with threads we have already spawned (like the profiler). - if (pid == 0) { - close(pfd[0]); - write_end_of_the_pipe = pfd[1]; - close_logging(); - // This process is expected to be crashy, and we - // don't want the user to see its crashes. That's the whole reason for - // doing this in a separate process. - if (CrashReporter::GetEnabled()) { - CrashReporter::UnsetExceptionHandler(); - } -#if defined(MOZ_ASAN) || defined(FUZZING) - // If handle_segv=1 (default), then glxtest crash will print a sanitizer - // report which can confuse the harness in fuzzing automation. - signal(SIGSEGV, SIG_DFL); -#endif - int rv = childgltest(); - close(pfd[1]); - _exit(rv); - } - - close(pfd[1]); - mozilla::widget::glxtest_pipe = pfd[0]; - mozilla::widget::glxtest_pid = pid; - return false; +static void PrintUsage() { + printf( + "Firefox OpenGL probe utility\n" + "\n" + "usage: glxtest [options]\n" + "\n" + "Options:\n" + "\n" + " -h --help show this message\n" + " -w --wayland probe OpenGL/EGL on Wayland (default is " + "X11)\n" + "\n"); +} + +int main(int argc, char** argv) { + struct option longOptions[] = {{"help", no_argument, NULL, 'h'}, + {"wayland", no_argument, NULL, 'w'}, + {NULL, 0, NULL, 0}}; + const char* shortOptions = "hw"; + int c; + bool wayland = false; + while ((c = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { + switch (c) { + case 'w': + wayland = true; + break; + case 'h': + PrintUsage(); + return 0; + default: + break; + } + } + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) { + const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set"; + (void)write(OUTPUT_PIPE, msg, strlen(msg)); + exit(EXIT_FAILURE); + } +#if defined(MOZ_ASAN) || defined(FUZZING) + // If handle_segv=1 (default), then glxtest crash will print a sanitizer + // report which can confuse the harness in fuzzing automation. + signal(SIGSEGV, SIG_DFL); +#endif + return childgltest(wayland); } diff --git a/toolkit/xre/glxtest/moz.build b/toolkit/xre/glxtest/moz.build new file mode 100644 index 000000000000..5b5d77314322 --- /dev/null +++ b/toolkit/xre/glxtest/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Widget: Gtk") + +Program("glxtest") +SOURCES += [ + "glxtest.cpp", +] +CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] +CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"] +OS_LIBS += CONFIG["MOZ_X11_LIBS"] +OS_LIBS += CONFIG["MOZ_GTK3_LIBS"] + +NO_PGO = True +DisableStlWrapping() diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build index 17da480e8c41..728b70882b21 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -153,11 +153,6 @@ SOURCES += [ "ProfileReset.cpp", ] -if CONFIG["MOZ_X11"] or CONFIG["MOZ_WAYLAND"]: - UNIFIED_SOURCES += [ - "glxtest.cpp", - ] - if CONFIG["MOZ_INSTRUMENT_EVENT_LOOP"]: UNIFIED_SOURCES += [ "EventTracer.cpp", @@ -186,6 +181,7 @@ FINAL_LIBRARY = "xul" if CONFIG["MOZ_X11"] or CONFIG["MOZ_WAYLAND"]: DEFINES["USE_GLX_TEST"] = True + DIRS += ["glxtest"] for var in ( "MOZ_APP_NAME", diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index bbb07d041b04..ef11b8c00740 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -254,6 +254,10 @@ # include "nsIStringBundle.h" #endif +#ifdef USE_GLX_TEST +# include "mozilla/GUniquePtr.h" +#endif + extern uint32_t gRestartMode; extern void InstallSignalHandlers(const char* ProgramName); @@ -321,11 +325,6 @@ nsString gProcessStartupShortcut; #if defined(MOZ_WIDGET_GTK) # include -# if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) -# define CLEANUP_MEMORY 1 -# define PANGO_ENABLE_BACKEND -# include -# endif # include "mozilla/WidgetUtilsGtk.h" # include # ifdef MOZ_WAYLAND @@ -335,7 +334,6 @@ nsString gProcessStartupShortcut; # ifdef MOZ_X11 # include # endif /* MOZ_X11 */ -# include #endif #include "BinaryPath.h" @@ -3620,7 +3618,40 @@ static DWORD WINAPI InitDwriteBG(LPVOID lpdwThreadParam) { #endif #ifdef USE_GLX_TEST -bool fire_glxtest_process(); +namespace mozilla::widget { +// the read end of the pipe, which will be used by GfxInfo +extern int glxtest_pipe; +// the PID of the glxtest process, to pass to waitpid() +extern pid_t glxtest_pid; +} // namespace mozilla::widget + +void fire_glxtest_process() { + nsAutoCString glxTestBinary; + if (!gAppData->xreDirectory) { + return; + } + gAppData->xreDirectory->GetNativePath(glxTestBinary); + glxTestBinary.Append("/"); + glxTestBinary.Append("glxtest"); + + char* argv[] = {strdup(glxTestBinary.get()), + IsWaylandEnabled() ? strdup("-w") : nullptr, nullptr}; + auto freeArgv = mozilla::MakeScopeExit([&] { + for (auto& arg : argv) { + free(arg); + } + }); + + GUniquePtr err; + g_spawn_async_with_pipes( + nullptr, argv, nullptr, + GSpawnFlags(G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD), nullptr, + nullptr, &mozilla::widget::glxtest_pid, nullptr, + &mozilla::widget::glxtest_pipe, nullptr, getter_Transfers(err)); + if (err) { + Output(true, "Failed to probe graphics hardware! %s\n", err->message); + } +} #endif #include "GeckoProfiler.h" diff --git a/widget/gtk/GfxInfo.cpp b/widget/gtk/GfxInfo.cpp index a29723a90f40..9af523f92495 100644 --- a/widget/gtk/GfxInfo.cpp +++ b/widget/gtk/GfxInfo.cpp @@ -163,7 +163,7 @@ void GfxInfo::GetData() { wait_for_glxtest_process = true; } else { // Bug 718629 - // ECHILD happens when the glxtest process got reaped got reaped after a + // ECHILD happens when the glxtest process got reaped after a // PR_CreateProcess as per bug 227246. This shouldn't matter, as we // still seem to get the data from the pipe, and if we didn't, the // outcome would be to blocklist anyway.