mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-15 03:37:09 -04:00
perf: Steam's web helper runs its GPU, audio and zygote work inside one process (#566)
This commit is contained in:
1 parent
6211faf6bc
commit
3fe03c0a88
5 files changed
+151
-5
No files matched your search
@@ -262,6 +262,7 @@ __attribute__((visibility("hidden"))) void bl_binfmt_before(const char *path, ch
|
||||
__attribute__((visibility("hidden"))) int bl_binfmt_retry(const char *path, char *const argv[], char *const envp[], int error);
|
||||
__attribute__((visibility("hidden"))) void bl_binfmt_before_search(const char *file, char *const argv[], char *const envp[]);
|
||||
__attribute__((visibility("hidden"))) int bl_binfmt_after_search(const char *file, char *const argv[], char *const envp[], int error);
|
||||
__attribute__((visibility("hidden"))) char *const *bl_webhelper_argv(const char *path, char *const argv[]);
|
||||
__attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
|
||||
const posix_spawnattr_t *attr, char *const argv[], char *const envp[],
|
||||
spawn_fn real);
|
||||
@@ -308,7 +309,8 @@ static int maybe_answer_lsof(const char *path, char *const argv[]) {
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
int execve(const char *path, char *const argv[], char *const envp[]) {
|
||||
int execve(const char *path, char *const argv_in[], char *const envp[]) {
|
||||
char *const *argv = bl_webhelper_argv(path, argv_in);
|
||||
static int (*real)(const char *, char *const[], char *const[]);
|
||||
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
|
||||
maybe_answer_lsof(path, argv);
|
||||
@@ -332,7 +334,8 @@ static int answer_lsof_via_spawn(const char *path, char *const argv[], pid_t *pi
|
||||
return 0;
|
||||
}
|
||||
|
||||
int execvp(const char *file, char *const argv[]) {
|
||||
int execvp(const char *file, char *const argv_in[]) {
|
||||
char *const *argv = bl_webhelper_argv(file, argv_in);
|
||||
static int (*real)(const char *, char *const[]);
|
||||
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execvp");
|
||||
maybe_answer_lsof(file, argv);
|
||||
@@ -341,7 +344,8 @@ int execvp(const char *file, char *const argv[]) {
|
||||
return bl_binfmt_after_search(file, argv, environ, errno);
|
||||
}
|
||||
|
||||
int execv(const char *path, char *const argv[]) {
|
||||
int execv(const char *path, char *const argv_in[]) {
|
||||
char *const *argv = bl_webhelper_argv(path, argv_in);
|
||||
static int (*real)(const char *, char *const[]);
|
||||
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv");
|
||||
maybe_answer_lsof(path, argv);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef WEBHELPER_DEFAULT
|
||||
#define WEBHELPER_DEFAULT "--no-zygote --in-process-gpu -AudioServiceOutOfProcess"
|
||||
#endif
|
||||
|
||||
static int is_webhelper_browser(const char *path, char *const argv[]) {
|
||||
if (!path || !argv) return 0;
|
||||
const char *base = strrchr(path, '/');
|
||||
base = base ? base + 1 : path;
|
||||
if (strcmp(base, "steamwebhelper")) return 0;
|
||||
for (int i = 1; argv[i]; i++)
|
||||
if (!strncmp(argv[i], "--type=", 7)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *join_features(const char *have, const char *add) {
|
||||
size_t a = have ? strlen(have) : 0, b = strlen(add);
|
||||
char *out = malloc(a + b + 2);
|
||||
if (!out) return NULL;
|
||||
out[0] = 0;
|
||||
if (a) strcat(strcat(out, have), ",");
|
||||
return strcat(out, add);
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) char *const *bl_webhelper_argv(const char *path, char *const argv[]) {
|
||||
if (!is_webhelper_browser(path, argv)) return argv;
|
||||
const char *spec = getenv("BL_WEBHELPER_ARGS");
|
||||
if (!spec || !*spec) spec = WEBHELPER_DEFAULT;
|
||||
if (!strcmp(spec, "stock")) return argv;
|
||||
|
||||
int argc = 0;
|
||||
while (argv[argc]) argc++;
|
||||
char *tokens = strdup(spec);
|
||||
if (!tokens) return argv;
|
||||
int extra = 0;
|
||||
for (char *c = tokens; *c; c++)
|
||||
if (*c == ' ') extra++;
|
||||
char **out = calloc(argc + extra + 4, sizeof *out);
|
||||
if (!out) return argv;
|
||||
|
||||
const char *enable = NULL, *disable = NULL;
|
||||
int n = 0;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (i && !strncmp(argv[i], "--enable-features=", 18)) enable = argv[i] + 18;
|
||||
else if (i && !strncmp(argv[i], "--disable-features=", 19)) disable = argv[i] + 19;
|
||||
else out[n++] = argv[i];
|
||||
}
|
||||
char *add_enable = NULL, *add_disable = NULL, *save = NULL;
|
||||
for (char *t = strtok_r(tokens, " ", &save); t; t = strtok_r(NULL, " ", &save)) {
|
||||
if (t[0] == '+' && t[1]) {
|
||||
char *joined = join_features(add_enable, t + 1);
|
||||
free(add_enable);
|
||||
add_enable = joined;
|
||||
} else if (t[0] == '-' && t[1] && t[1] != '-') {
|
||||
char *joined = join_features(add_disable, t + 1);
|
||||
free(add_disable);
|
||||
add_disable = joined;
|
||||
} else if (!strncmp(t, "--", 2)) {
|
||||
out[n++] = t;
|
||||
}
|
||||
}
|
||||
char *merged;
|
||||
if (add_enable && (merged = join_features(enable, add_enable))) enable = merged;
|
||||
if (add_disable && (merged = join_features(disable, add_disable))) disable = merged;
|
||||
char *flag;
|
||||
if (enable && (flag = malloc(strlen(enable) + 19))) out[n++] = strcat(strcpy(flag, "--enable-features="), enable);
|
||||
if (disable && (flag = malloc(strlen(disable) + 20))) out[n++] = strcat(strcpy(flag, "--disable-features="), disable);
|
||||
out[n] = NULL;
|
||||
return out;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class BinfmtPreloadTest(unittest.TestCase):
|
||||
cls.library = cls.tmp / "libbinfmt.so"
|
||||
subprocess.run(["gcc", "-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-DBINFMT_TEST",
|
||||
'-DBINFMT_HANDLER="%s"' % cls.handler, "-o", str(cls.library),
|
||||
"-pthread"] + [str(PRELOAD / name) for name in ("binfmt.c", "net.c", "udevmon.c", "pathcache.c")] + ["-ldl"],
|
||||
"-pthread"] + [str(PRELOAD / name) for name in ("binfmt.c", "net.c", "udevmon.c", "pathcache.c", "webhelper.c")] + ["-ldl"],
|
||||
check=True)
|
||||
source = cls.tmp / "program.c"
|
||||
source.write_text(PROGRAM)
|
||||
|
||||
@@ -16,7 +16,7 @@ class CdpGuardTest(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
cls.build = tempfile.TemporaryDirectory()
|
||||
cls.library = Path(cls.build.name) / "net.so"
|
||||
subprocess.run(["cc", "-shared", "-fPIC", "-O2", str(SOURCE), str(SOURCE.parent / "binfmt.c"), "-x", "c", "-", "-ldl",
|
||||
subprocess.run(["cc", "-shared", "-fPIC", "-O2", str(SOURCE), str(SOURCE.parent / "binfmt.c"), str(SOURCE.parent / "webhelper.c"), "-x", "c", "-", "-ldl",
|
||||
"-o", str(cls.library)],
|
||||
input=b"int bl_udevmon_stand_in(int fd) { return 0; }", check=True, capture_output=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
PRELOAD = Path(__file__).resolve().parents[1] / "linuxfs/preload"
|
||||
|
||||
PROGRAM = r"""
|
||||
#include <stdio.h>
|
||||
char *const *bl_webhelper_argv(const char *path, char *const argv[]);
|
||||
int main(int argc, char **argv) {
|
||||
char *const *out = bl_webhelper_argv(argv[1], argv + 1);
|
||||
for (int i = 0; out[i]; i++) printf("%s\n", out[i]);
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
|
||||
WEBHELPER = "/root/.local/share/Steam/steamrtarm64/steamwebhelper"
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("cc"), "needs cc")
|
||||
class WebhelperPreloadTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.tmp = Path(tempfile.mkdtemp())
|
||||
source = cls.tmp / "program.c"
|
||||
source.write_text(PROGRAM)
|
||||
cls.program = cls.tmp / "program"
|
||||
subprocess.run(["cc", "-O2", "-Wall", "-Werror", "-o", str(cls.program), str(source),
|
||||
str(PRELOAD / "webhelper.c")], check=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
shutil.rmtree(cls.tmp, ignore_errors=True)
|
||||
|
||||
def argv(self, *args, spec=None):
|
||||
env = {k: v for k, v in os.environ.items() if k != "BL_WEBHELPER_ARGS"}
|
||||
if spec is not None:
|
||||
env["BL_WEBHELPER_ARGS"] = spec
|
||||
result = subprocess.run([str(self.program), *args], env=env, capture_output=True, text=True, check=True)
|
||||
return result.stdout.splitlines()
|
||||
|
||||
def test_the_browser_process_keeps_its_helpers_in_process(self):
|
||||
out = self.argv(WEBHELPER, "-lang=en_US", "--enable-features=A", "--no-sandbox", "--disable-features=B,C")
|
||||
self.assertEqual(out, [WEBHELPER, "-lang=en_US", "--no-sandbox", "--no-zygote", "--in-process-gpu",
|
||||
"--enable-features=A", "--disable-features=B,C,AudioServiceOutOfProcess"])
|
||||
|
||||
def test_feature_lists_are_added_when_steam_passes_none(self):
|
||||
out = self.argv(WEBHELPER, spec="+X -Y")
|
||||
self.assertEqual(out, [WEBHELPER, "--enable-features=X", "--disable-features=Y"])
|
||||
|
||||
def test_child_processes_and_other_programs_are_left_alone(self):
|
||||
child = [WEBHELPER, "--type=renderer", "--enable-features=A"]
|
||||
self.assertEqual(self.argv(*child), child)
|
||||
other = ["/usr/bin/steam", "--enable-features=A"]
|
||||
self.assertEqual(self.argv(*other), other)
|
||||
|
||||
def test_stock_turns_it_off(self):
|
||||
args = [WEBHELPER, "--enable-features=A"]
|
||||
self.assertEqual(self.argv(*args, spec="stock"), args)
|
||||
|
||||
def test_an_empty_setting_means_the_default(self):
|
||||
self.assertIn("--no-zygote", self.argv(WEBHELPER, spec=""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user