From af2783cb032ea2c9b66726c826b2aeeec87a0a7b Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Mon, 28 Sep 2026 15:59:04 -0400 Subject: [PATCH] research: probe Valve Bionic Steam client IPC --- tools/research/bionic-steam-probe/README.md | 153 ++++++++++++++++++ .../bionic-steam-probe/fetch_build.sh | 98 +++++++++++ .../research/bionic-steam-probe/run_probe.sh | 58 +++++++ .../bionic-steam-probe/steamclient_probe.c | 73 +++++++++ .../steamclient_service_probe.c | 146 +++++++++++++++++ .../bionic-steam-probe/steamservice_probe.c | 83 ++++++++++ .../research/bionic-steam-probe/trace_hooks.c | 101 ++++++++++++ 7 files changed, 712 insertions(+) create mode 100644 tools/research/bionic-steam-probe/README.md create mode 100755 tools/research/bionic-steam-probe/fetch_build.sh create mode 100755 tools/research/bionic-steam-probe/run_probe.sh create mode 100644 tools/research/bionic-steam-probe/steamclient_probe.c create mode 100644 tools/research/bionic-steam-probe/steamclient_service_probe.c create mode 100644 tools/research/bionic-steam-probe/steamservice_probe.c create mode 100644 tools/research/bionic-steam-probe/trace_hooks.c diff --git a/tools/research/bionic-steam-probe/README.md b/tools/research/bionic-steam-probe/README.md new file mode 100644 index 000000000..061079c54 --- /dev/null +++ b/tools/research/bionic-steam-probe/README.md @@ -0,0 +1,153 @@ +# Bionic Steam client probe + +This is a research harness for Valve's Android/Bionic ARM64 Steam client +libraries. It does **not** vendor or redistribute Valve binaries. The build +script fetches the current package from Valve's public Steam client-update +endpoint into a temporary working directory. + +The question under test is narrow: + +> Can Valve's `androidarm64/libsteamclient.so` run directly under Android's +> Bionic linker, and if so, what does it need in order to become a usable +> Steam client? + +## Result + +Yes, the library itself works directly on stock Android. It is not a complete +Steam desktop client, however. It is a Steamworks IPC client for a separate +full Steam process. + +On 2026-09-28, the public-beta ARM64 client manifest contained +`bins_androidarm64_linuxarm64.zip.35688b00e791987ab70b1b6c6ddd006d4c864470` +with SHA-256: + +`811859a83c7c5ff220ac9731235e76fd0d5da603a5cdb56fe5f08e145a417cbd` + +The archive contains Android ARM64 builds of `libsteamclient.so`, +`libsteamnetworkingsockets.so`, `libtier0_s.so`, `libvstdlib_s.so`, and +`steamservice.so`. +## ABI evidence + +`libsteamclient.so` is a genuine Android/NDK build, not a glibc ARM64 binary +with an Android-flavored directory name: + +- ELF64 AArch64 shared object. +- Depends on `libandroid.so`, `liblog.so`, `libm.so`, `libdl.so`, and Bionic + `libc.so`. +- Uses the NDK C++ namespace (`std::__ndk1`). +- Carries `.note.android.ident` and Android LLVM/Clang build strings. +- Contains no `GLIBC_*` symbol-version requirements. + +Android's stock linker on an AYN Thor loaded both Valve libraries with no +compatibility shim. `CreateInterface("SteamClient023")` also succeeded. + +## What `steamservice.so` proved + +`steamservice.so` exports `SteamService_StartThread`, +`SteamService_GetIPCServer`, `SteamService_Stop`, and +`SteamService_Shutdown`. A non-null service name starts its worker thread +successfully on Android and shuts down normally. + +That service is **not** the Steamworks host expected by `libsteamclient.so`. +Starting it does not create the TCP IPC listener used by `CreateSteamPipe`. +The service probe is retained because it establishes that the Bionic service +code itself is runnable and documents the calling convention. +## The actual dependency: Steam IPC on TCP 57343 + +With no Steam host available, both the legacy exported C entry point and the +`SteamClient023` virtual `CreateSteamPipe` method return `0`. + +An `LD_PRELOAD` trace of `socket()` and `connect()` showed why: + +```text +socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) -> fd +connect(fd, 127.0.0.1:57343) -> ECONNREFUSED +connect(fd, 127.0.0.1:57343) -> ECONNREFUSED +``` + +A dummy listener captured the client's first 17 bytes: + +```text +0d 00 00 00 09 01 00 00 00 00 00 00 00 00 00 00 00 +``` + +Port 57343 is Steam's long-standing local IPC listener. Valve's Lepton +Android compatibility environment uses the same Steam gateway for its +Android `libsteamclient.so`. + +## End-to-end proof + +Desktop Steam on a Mac was listening on host `127.0.0.1:57343`. We mapped +Android's loopback port to it temporarily with: + +```sh +adb reverse tcp:57343 tcp:57343 +``` +With no local `steamservice.so` involved, the unmodified Bionic client on the +Thor then returned: + +```text +SteamClient023= rc=0 +Steam_CreateSteamPipe=1 +Steam_ConnectToGlobalUser=1 +Steam_BConnected=1 +Steam_BLoggedOn=1 +Steam_BReleaseSteamPipe=1 +``` + +This proves the Bionic client is operational on normal Android and can speak +the real cross-process Steam IPC protocol to an ordinary desktop Steam +process. + +## Implication for DroidDeck / PRoot + +This discovery does **not** by itself remove PRoot. The Android archive does +not contain the full Steam executable or `steamwebhelper`; the Bionic client +expects that full Steam process to exist elsewhere and own port 57343. + +The useful architectural split is now proven rather than hypothetical: + +```text +Android/Bionic workload + -> Valve Bionic libsteamclient.so + -> TCP Steam IPC :57343 + -> full Steam host process +``` + +For a PRoot-free DroidDeck, the remaining problem is therefore the **host +Steam process**, not Steamworks ABI compatibility. Plausible future backends +are a real Linux environment (rooted chroot/namespaces), a VM/AVF guest with +a host bridge, or a future Valve Bionic build of the full client. Reimplementing +Steam's private IPC server should be treated as a last resort. +## Reproducing + +Build and fetch Valve's current Android package into `/tmp`: + +```sh +tools/research/bionic-steam-probe/fetch_build.sh +``` + +Run without a Steam host. This should resolve the Bionic interfaces but +return pipe/user handle `0`: + +```sh +tools/research/bionic-steam-probe/run_probe.sh +``` + +If desktop Steam is running on the ADB host and listening on loopback, test +the real bridge: + +```sh +tools/research/bionic-steam-probe/run_probe.sh --reverse-host-steam +``` + +The runner removes the ADB reverse mapping on exit. No Valve binary is copied +into this repository. + +## Security note + +Steam binds 57343 to loopback on desktop systems. Keep any DroidDeck bridge +equally constrained; do not expose this private Steam IPC endpoint to the LAN. +On Android all regular applications share the device network namespace, so a +future local host design should also consider how to prevent unrelated apps +from reaching a loopback-only Steam IPC listener. \ No newline at end of file diff --git a/tools/research/bionic-steam-probe/fetch_build.sh b/tools/research/bionic-steam-probe/fetch_build.sh new file mode 100755 index 000000000..46d007714 --- /dev/null +++ b/tools/research/bionic-steam-probe/fetch_build.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +MANIFEST_URL="${MANIFEST_URL:-https://client-update.steamstatic.com/steam_client_publicbeta_linuxarm64}" +WORKDIR="${WORKDIR:-${TMPDIR:-/tmp}/droiddeck-bionic-steam-probe}" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +mkdir -p "$WORKDIR/android" "$WORKDIR/bin" + +find_ndk() { + if [[ -n "${ANDROID_NDK_HOME:-}" ]]; then + printf '%s\n' "$ANDROID_NDK_HOME" + return + fi + if [[ -n "${ANDROID_NDK_ROOT:-}" ]]; then + printf '%s\n' "$ANDROID_NDK_ROOT" + return + fi + local sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}}" + local ndk + ndk="$(find "$sdk/ndk" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)" + [[ -n "$ndk" ]] || return 1 + printf '%s\n' "$ndk" +} +NDK="$(find_ndk)" || { + echo "Android NDK not found; set ANDROID_NDK_HOME" >&2 + exit 1 +} +HOST_TAG="linux-x86_64" +[[ "$(uname -s)" == Darwin ]] && HOST_TAG="darwin-x86_64" +CC="$NDK/toolchains/llvm/prebuilt/$HOST_TAG/bin/aarch64-linux-android26-clang" + +curl -fsSL "$MANIFEST_URL" -o "$WORKDIR/manifest.vdf" +PACKAGE="$(python3 - "$WORKDIR/manifest.vdf" <<'PY' +import re +import sys +text = open(sys.argv[1], encoding="utf-8").read() +m = re.search( + r'"bins_androidarm64_linuxarm64"\s*\{.*?"file"\s*"([^"]+)"', + text, + re.S, +) +if not m: + raise SystemExit("bins_androidarm64_linuxarm64 not found") +print(m.group(1)) +PY +)" +PACKAGE_SHA="$(python3 - "$WORKDIR/manifest.vdf" <<'PY' +import re +import sys +text = open(sys.argv[1], encoding="utf-8").read() +block = re.search(r'"bins_androidarm64_linuxarm64"\s*\{(.*?)\n\s*\}', text, re.S) +if not block: + raise SystemExit("bins_androidarm64_linuxarm64 block not found") +m = re.search(r'"sha2"\s*"([0-9a-fA-F]{64})"', block.group(1)) +if not m: + raise SystemExit("sha2 missing from Android ARM64 package") +print(m.group(1).lower()) +PY +)" +echo "Valve package: $PACKAGE" +curl -fsSL "https://client-update.steamstatic.com/$PACKAGE" -o "$WORKDIR/android.zip" +ACTUAL_SHA="$(python3 - "$WORKDIR/android.zip" <<'PY' +import hashlib +import sys +print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest()) +PY +)" +[[ "$ACTUAL_SHA" == "$PACKAGE_SHA" ]] || { + echo "SHA-256 mismatch: manifest=$PACKAGE_SHA download=$ACTUAL_SHA" >&2 + exit 1 +} +python3 - "$WORKDIR/android.zip" "$WORKDIR/android" <<'PY' +import os +import sys +import zipfile + +archive, out = sys.argv[1:] +wanted = { + "libsteamclient.so", + "steamservice.so", + "libtier0_s.so", + "libvstdlib_s.so", +} +with zipfile.ZipFile(archive) as zf: + for name in zf.namelist(): + base = os.path.basename(name) + if base in wanted: + target = os.path.join(out, base) + with open(target, "wb") as f: + f.write(zf.read(name)) + print(f"extracted {name} -> {target}") +PY + +"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamclient_probe.c" -ldl -o "$WORKDIR/bin/steamclient-probe" +"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamservice_probe.c" -ldl -o "$WORKDIR/bin/steamservice-probe" +"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamclient_service_probe.c" -ldl -o "$WORKDIR/bin/steamclient-service-probe" +"$CC" -shared -fPIC -O0 -g -Wall -Wextra "$SCRIPT_DIR/trace_hooks.c" -o "$WORKDIR/bin/trace-hooks.so" +echo "Built probes in $WORKDIR/bin" diff --git a/tools/research/bionic-steam-probe/run_probe.sh b/tools/research/bionic-steam-probe/run_probe.sh new file mode 100755 index 000000000..839762f88 --- /dev/null +++ b/tools/research/bionic-steam-probe/run_probe.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +WORKDIR="${WORKDIR:-${TMPDIR:-/tmp}/droiddeck-bionic-steam-probe}" +ADB="${ADB:-adb}" +REMOTE="/data/local/tmp/droiddeck-bionic" +REVERSE=0 + +if [[ "${1:-}" == "--reverse-host-steam" ]]; then + REVERSE=1 +elif [[ -n "${1:-}" ]]; then + echo "usage: $0 [--reverse-host-steam]" >&2 + exit 2 +fi + +[[ -f "$WORKDIR/bin/steamclient-probe" ]] || { + echo "Run fetch_build.sh first" >&2 + exit 1 +} +[[ -f "$WORKDIR/android/libsteamclient.so" ]] || { + echo "Valve libsteamclient.so is missing from $WORKDIR" >&2 + exit 1 +} + +"$ADB" shell "mkdir -p $REMOTE" +"$ADB" push "$WORKDIR/bin/steamclient-probe" "$REMOTE/steamclient-probe" >/dev/null +"$ADB" push "$WORKDIR/android/libsteamclient.so" "$REMOTE/libsteamclient.so" >/dev/null +"$ADB" shell "chmod 755 $REMOTE/steamclient-probe" +cleanup() { + if (( REVERSE )); then + "$ADB" reverse --remove tcp:57343 >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +if (( REVERSE )); then + "$ADB" reverse --remove tcp:57343 >/dev/null 2>&1 || true + "$ADB" reverse tcp:57343 tcp:57343 >/dev/null + echo "Mapped Android 127.0.0.1:57343 to host 127.0.0.1:57343." + echo "A desktop Steam process must already be listening on the host." +else + echo "No bridge configured; CreateSteamPipe should be 0 unless Steam already" + echo "exists in the Android network namespace." +fi + +set +e +"$ADB" shell "$REMOTE/steamclient-probe $REMOTE/libsteamclient.so" +rc=$? +set -e + +if (( REVERSE )); then + if (( rc == 0 )); then + echo "PASS: Bionic libsteamclient connected to host Steam." + else + echo "FAIL: Bionic libsteamclient did not obtain a Steam pipe." >&2 + fi +fi +exit "$rc" diff --git a/tools/research/bionic-steam-probe/steamclient_probe.c b/tools/research/bionic-steam-probe/steamclient_probe.c new file mode 100644 index 000000000..4abb921ee --- /dev/null +++ b/tools/research/bionic-steam-probe/steamclient_probe.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include + +typedef void *(*create_interface_fn)(const char *, int *); +typedef int32_t (*create_pipe_fn)(void); +typedef int32_t (*connect_user_fn)(int32_t); +typedef bool (*status_fn)(int32_t, int32_t); +typedef bool (*release_pipe_fn)(int32_t); + +static void *lookup(void *handle, const char *name) { + dlerror(); + void *ptr = dlsym(handle, name); + const char *error = dlerror(); + printf("dlsym %-28s = %p", name, ptr); + if (error != NULL) printf(" error=%s", error); + putchar('\n'); + return ptr; +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "usage: %s LIBSTEAMCLIENT.SO\n", argv[0]); + return 2; + } + + printf("pid=%d uid=%d\n", getpid(), getuid()); + void *client = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (client == NULL) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + return 3; + } + + create_interface_fn create_interface = + (create_interface_fn)lookup(client, "CreateInterface"); + create_pipe_fn create_pipe = + (create_pipe_fn)lookup(client, "Steam_CreateSteamPipe"); + connect_user_fn connect_user = + (connect_user_fn)lookup(client, "Steam_ConnectToGlobalUser"); + status_fn connected = + (status_fn)lookup(client, "Steam_BConnected"); + status_fn logged_on = + (status_fn)lookup(client, "Steam_BLoggedOn"); + release_pipe_fn release_pipe = + (release_pipe_fn)lookup(client, "Steam_BReleaseSteamPipe"); + + int rc = -1; + void *iface = create_interface != NULL + ? create_interface("SteamClient023", &rc) : NULL; + printf("SteamClient023=%p rc=%d\n", iface, rc); + + int32_t pipe = create_pipe != NULL ? create_pipe() : 0; + printf("Steam_CreateSteamPipe=%d\n", pipe); + int32_t user = (pipe != 0 && connect_user != NULL) + ? connect_user(pipe) : 0; + printf("Steam_ConnectToGlobalUser=%d\n", user); + + if (pipe != 0 && user != 0 && connected != NULL) { + printf("Steam_BConnected=%d\n", connected(user, pipe)); + } + if (pipe != 0 && user != 0 && logged_on != NULL) { + printf("Steam_BLoggedOn=%d\n", logged_on(user, pipe)); + } + + if (pipe != 0 && release_pipe != NULL) { + printf("Steam_BReleaseSteamPipe=%d\n", release_pipe(pipe)); + } + dlclose(client); + + return pipe != 0 && user != 0 ? 0 : 4; +} diff --git a/tools/research/bionic-steam-probe/steamclient_service_probe.c b/tools/research/bionic-steam-probe/steamclient_service_probe.c new file mode 100644 index 000000000..ef2638fee --- /dev/null +++ b/tools/research/bionic-steam-probe/steamclient_service_probe.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include + +typedef void *(*create_interface_fn)(const char *, int *); +typedef void *(*service_start_fn)(const char *); +typedef void *(*service_get_ipc_fn)(void); +typedef void (*service_void_fn)(void); +typedef int32_t (*create_pipe_fn)(void); +typedef int32_t (*connect_user_fn)(int32_t); +typedef bool (*connected_fn)(int32_t, int32_t); +typedef bool (*release_pipe_fn)(int32_t); + +static void *must_sym(void *handle, const char *name) { + dlerror(); + void *ptr = dlsym(handle, name); + const char *error = dlerror(); + printf("dlsym %-28s = %p", name, ptr); + if (error != NULL) printf(" error=%s", error); + putchar('\n'); + fflush(stdout); + return ptr; +} + +static void dump_unix_sockets(const char *label) { + FILE *file = fopen("/proc/self/net/unix", "r"); + if (file == NULL) return; + printf("--- UNIX sockets: %s ---\n", label); + char line[1024]; + while (fgets(line, sizeof(line), file) != NULL) { + if (strstr(line, "steam") != NULL || + strstr(line, "Steam") != NULL || + strstr(line, "@") != NULL) { + fputs(line, stdout); + } + } + fclose(file); + fflush(stdout); +} + +static void *open_library(const char *path) { + printf("dlopen(%s)\n", path); + fflush(stdout); + void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } else { + printf("dlopen ok handle=%p\n", handle); + } + return handle; +} + +int main(int argc, char **argv) { + if (argc < 3) { + fprintf(stderr, "usage: %s STEAMSERVICE.SO LIBSTEAMCLIENT.SO [service_arg]\n", + argv[0]); + return 2; + } + + const char *service_arg = argc >= 4 ? argv[3] : "."; + printf("pid=%d uid=%d service_arg=%s\n", + getpid(), getuid(), service_arg); + + void *service = open_library(argv[1]); + if (service == NULL) return 3; + service_start_fn service_start = + (service_start_fn)must_sym(service, "SteamService_StartThread"); + service_get_ipc_fn get_ipc = + (service_get_ipc_fn)must_sym(service, "SteamService_GetIPCServer"); + service_void_fn stop = + (service_void_fn)must_sym(service, "SteamService_Stop"); + service_void_fn shutdown = + (service_void_fn)must_sym(service, "SteamService_Shutdown"); + + printf("IPC before start=%p\n", get_ipc != NULL ? get_ipc() : NULL); + void *started = service_start != NULL ? service_start(service_arg) : NULL; + printf("StartThread=%p IPC after start=%p\n", + started, get_ipc != NULL ? get_ipc() : NULL); + fflush(stdout); + usleep(200000); + dump_unix_sockets("after service start"); + + void *client = open_library(argv[2]); + if (client == NULL) return 4; + + create_interface_fn create_interface = + (create_interface_fn)must_sym(client, "CreateInterface"); + create_pipe_fn create_pipe = + (create_pipe_fn)must_sym(client, "Steam_CreateSteamPipe"); + connect_user_fn connect_user = + (connect_user_fn)must_sym(client, "Steam_ConnectToGlobalUser"); + connected_fn connected = + (connected_fn)must_sym(client, "Steam_BConnected"); + connected_fn logged_on = + (connected_fn)must_sym(client, "Steam_BLoggedOn"); + release_pipe_fn release_pipe = + (release_pipe_fn)must_sym(client, "Steam_BReleaseSteamPipe"); + + int rc = -999; + void *steam_client = create_interface != NULL + ? create_interface("SteamClient023", &rc) : NULL; + printf("CreateInterface SteamClient023 -> %p rc=%d\n", + steam_client, rc); + + int32_t adapter_pipe = 0; + if (steam_client != NULL) { + void **vtable = *(void ***)steam_client; + int32_t (*adapter_create_pipe)(void *) = + (int32_t (*)(void *))vtable[0]; + printf("SteamClient023 vtable=%p CreateSteamPipe=%p\n", + vtable, (void *)adapter_create_pipe); + adapter_pipe = adapter_create_pipe(steam_client); + printf("SteamClient023::CreateSteamPipe -> %d\n", adapter_pipe); + } + + int32_t pipe = create_pipe != NULL ? create_pipe() : 0; + printf("Steam_CreateSteamPipe -> %d\n", pipe); + dump_unix_sockets("after client CreateSteamPipe"); + + int32_t user = (connect_user != NULL && pipe != 0) + ? connect_user(pipe) : 0; + printf("Steam_ConnectToGlobalUser(%d) -> %d\n", pipe, user); + + if (connected != NULL && user != 0 && pipe != 0) { + printf("Steam_BConnected -> %d\n", connected(user, pipe)); + } + if (logged_on != NULL && user != 0 && pipe != 0) { + printf("Steam_BLoggedOn -> %d\n", logged_on(user, pipe)); + } + fflush(stdout); + + sleep(2); + if (release_pipe != NULL && pipe != 0) { + printf("Steam_BReleaseSteamPipe -> %d\n", release_pipe(pipe)); + } + if (stop != NULL) stop(); + if (shutdown != NULL) shutdown(); + + dlclose(client); + dlclose(service); + puts("done"); + return 0; +} diff --git a/tools/research/bionic-steam-probe/steamservice_probe.c b/tools/research/bionic-steam-probe/steamservice_probe.c new file mode 100644 index 000000000..75f2dd908 --- /dev/null +++ b/tools/research/bionic-steam-probe/steamservice_probe.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include + +typedef void *(*get_ipc_fn)(void); +typedef void *(*start_thread_fn)(const char *); +typedef void (*void_fn)(void); + +static void *lookup(void *handle, const char *name) { + dlerror(); + void *ptr = dlsym(handle, name); + const char *error = dlerror(); + printf("dlsym %-28s = %p", name, ptr); + if (error != NULL) { + printf(" error=%s", error); + } + putchar('\n'); + fflush(stdout); + return ptr; +} + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, + "usage: %s STEAMSERVICE.SO [symbols|getipc|start [arg]]\n", + argv[0]); + return 2; + } + printf("pid=%d uid=%d euid=%d\n", getpid(), getuid(), geteuid()); + printf("dlopen(%s)\n", argv[1]); + fflush(stdout); + + void *handle = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + return 3; + } + printf("dlopen ok handle=%p\n", handle); + + get_ipc_fn get_ipc = + (get_ipc_fn)lookup(handle, "SteamService_GetIPCServer"); + start_thread_fn start = + (start_thread_fn)lookup(handle, "SteamService_StartThread"); + void_fn stop = (void_fn)lookup(handle, "SteamService_Stop"); + void_fn shutdown = (void_fn)lookup(handle, "SteamService_Shutdown"); + lookup(handle, "SteamService_RunMainLoop"); + lookup(handle, "CreateInterface"); + + const char *mode = argc >= 3 ? argv[2] : "symbols"; + if (!strcmp(mode, "getipc") || !strcmp(mode, "start")) { + printf("GetIPCServer before start -> %p\n", + get_ipc != NULL ? get_ipc() : NULL); + fflush(stdout); + } + if (!strcmp(mode, "start")) { + const char *arg = argc >= 4 ? argv[3] : NULL; + printf("StartThread arg=%s\n", arg != NULL ? arg : ""); + fflush(stdout); + + void *result = start != NULL ? start(arg) : NULL; + printf("StartThread -> %p; GetIPCServer -> %p\n", + result, get_ipc != NULL ? get_ipc() : NULL); + fflush(stdout); + + sleep(2); + if (stop != NULL) { + puts("Stop()"); + fflush(stdout); + stop(); + } + if (shutdown != NULL) { + puts("Shutdown()"); + fflush(stdout); + shutdown(); + } + } + + dlclose(handle); + puts("done"); + return 0; +} diff --git a/tools/research/bionic-steam-probe/trace_hooks.c b/tools/research/bionic-steam-probe/trace_hooks.c new file mode 100644 index 000000000..96a275e25 --- /dev/null +++ b/tools/research/bionic-steam-probe/trace_hooks.c @@ -0,0 +1,101 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void trace_line(const char *kind, int fd, + const struct sockaddr *addr, socklen_t len) { + char buffer[768]; + const char *detail = ""; + char endpoint[512] = {0}; + + if (addr != NULL && addr->sa_family == AF_UNIX && + len > offsetof(struct sockaddr_un, sun_path)) { + const struct sockaddr_un *un = (const struct sockaddr_un *)addr; + size_t path_len = len - offsetof(struct sockaddr_un, sun_path); + if (path_len > sizeof(un->sun_path)) path_len = sizeof(un->sun_path); + if (path_len > 0 && un->sun_path[0] == '\0') { + snprintf(endpoint, sizeof(endpoint), "@%.*s", + (int)(path_len - 1), un->sun_path + 1); + } else { + snprintf(endpoint, sizeof(endpoint), "%.*s", + (int)path_len, un->sun_path); + } + detail = endpoint; + } else if (addr != NULL && addr->sa_family == AF_INET && + len >= sizeof(struct sockaddr_in)) { + const struct sockaddr_in *in = (const struct sockaddr_in *)addr; + unsigned long ip = ntohl(in->sin_addr.s_addr); + snprintf(endpoint, sizeof(endpoint), "%lu.%lu.%lu.%lu:%u", + (ip >> 24) & 0xff, (ip >> 16) & 0xff, + (ip >> 8) & 0xff, ip & 0xff, ntohs(in->sin_port)); + detail = endpoint; + } + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] %s fd=%d family=%d endpoint=%s\n", + kind, fd, addr != NULL ? addr->sa_family : -1, detail); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); +} + +int socket(int domain, int type, int protocol) { + int fd = (int)syscall(SYS_socket, domain, type, protocol); + char buffer[160]; + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] socket domain=%d type=%d protocol=%d -> %d errno=%d\n", + domain, type, protocol, fd, fd < 0 ? errno : 0); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); + return fd; +} + +int socketpair(int domain, int type, int protocol, int sv[2]) { + int rc = (int)syscall(SYS_socketpair, domain, type, protocol, sv); + char buffer[180]; + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] socketpair domain=%d type=%d -> %d [%d,%d] errno=%d\n", + domain, type, rc, rc == 0 ? sv[0] : -1, + rc == 0 ? sv[1] : -1, rc < 0 ? errno : 0); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); + return rc; +} +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + trace_line("connect", fd, addr, len); + int rc = (int)syscall(SYS_connect, fd, addr, len); + if (rc < 0) { + char buffer[120]; + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] connect rc=%d errno=%d\n", rc, errno); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); + } + return rc; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + trace_line("bind", fd, addr, len); + int rc = (int)syscall(SYS_bind, fd, addr, len); + if (rc < 0) { + char buffer[120]; + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] bind rc=%d errno=%d\n", rc, errno); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); + } + return rc; +} + +int listen(int fd, int backlog) { + int rc = (int)syscall(SYS_listen, fd, backlog); + char buffer[120]; + int n = snprintf(buffer, sizeof(buffer), + "[probe-trace] listen fd=%d backlog=%d -> %d errno=%d\n", + fd, backlog, rc, rc < 0 ? errno : 0); + if (n > 0) write(STDERR_FILENO, buffer, (size_t)n); + return rc; +}