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
This commit is contained in:
stransky 2023-03-27 10:16:46 +00:00
Родитель 08c92637d2
Коммит facf3ee2b4
6 изменённых файлов: 154 добавлений и 105 удалений

Просмотреть файл

@ -160,6 +160,7 @@
#endif
#ifdef MOZ_GTK
@BINPATH@/vaapitest
@BINPATH@/glxtest
#endif
; [Components]

Просмотреть файл

@ -24,16 +24,15 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <vector>
#include <stdint.h>
#include <string.h>
#if defined(MOZ_ASAN) || defined(FUZZING)
# include <signal.h>
#endif
#include "mozilla/Unused.h"
#include "nsAppRunner.h" // for IsWaylandEnabled on IsX11EGLEnabled
#include "stdint.h"
#include "nsExceptionHandler.h"
#ifdef __SUNPRO_CC
# include <stdio.h>
#endif
@ -44,11 +43,6 @@
# include <X11/extensions/Xrandr.h>
#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);
}

Просмотреть файл

@ -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()

Просмотреть файл

@ -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",

Просмотреть файл

@ -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 <glib.h>
# if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
# define CLEANUP_MEMORY 1
# define PANGO_ENABLE_BACKEND
# include <pango/pangofc-fontmap.h>
# endif
# include "mozilla/WidgetUtilsGtk.h"
# include <gtk/gtk.h>
# ifdef MOZ_WAYLAND
@ -335,7 +334,6 @@ nsString gProcessStartupShortcut;
# ifdef MOZ_X11
# include <gdk/gdkx.h>
# endif /* MOZ_X11 */
# include <fontconfig/fontconfig.h>
#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<GError> 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"

Просмотреть файл

@ -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.