Skip to content

Commit 2e5b685

Browse files
runlevel5illwieckz
authored andcommitted
Box64 hybrid NaCl emulation for unsupported platforms
Run x86_64 NaCl loader and amd64 .nexe game modules under Box64 emulation on platforms without native NaCl support. The native engine communicates with the emulated NaCl sandbox processes via architecture-independent Unix domain socket IPC, requiring no changes to game modules. - Add opt-in DAEMON_NACL_BOX64_EMULATION CMake option (OFF by default, Linux only) that remaps NACL_ARCH to amd64 - Replace architecture-specific NaCl dummy defines with a generic fallback for any unsupported architecture - Prepend box64 to NaCl loader args with PATH resolution - Skip bootstrap helper (incompatible with Box64 double-exec) - Disable platform qualification for emulated loader - Add cvars: vm.box64.path, workaround.box64.disableBootstrap, workaround.box64.disableQualification
1 parent 29446db commit 2e5b685

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

cmake/DaemonArchitecture.cmake

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ if (LINUX OR FREEBSD)
8181
# The nexe is system agnostic so there should be no difference with armel.
8282
set(NACL_ARCH "armhf")
8383
endif()
84+
85+
set(BOX64_USAGE ppc64el riscv64)
86+
if (ARCH IN_LIST BOX64_USAGE)
87+
option(DAEMON_NACL_BOX64_EMULATION "Use Box64 to emulate x86_64 NaCl loader on unsupported platforms" ON)
88+
if (DAEMON_NACL_BOX64_EMULATION)
89+
# Use Box64 to run x86_64 NaCl loader and amd64 nexe.
90+
# Box64 must be installed and available in PATH at runtime.
91+
set(NACL_ARCH "amd64")
92+
add_definitions(-DDAEMON_NACL_BOX64_EMULATION)
93+
endif()
94+
endif()
8495
elseif(APPLE)
8596
if ("${ARCH}" STREQUAL arm64)
8697
# You can get emulated NaCl going like this:

cmake/DaemonNacl.cmake

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,12 @@ else()
7171
add_definitions( -DNACL_BUILD_SUBARCH=32 )
7272
elseif( NACL_ARCH STREQUAL "armhf" )
7373
add_definitions( -DNACL_BUILD_ARCH=arm )
74-
elseif( NACL_ARCH STREQUAL "ppc64el" OR NACL_ARCH STREQUAL "ppc64" )
75-
# NaCl does not support PPC, but these defines must be set for native
76-
# builds. Use dummy x86 values as PNaCl does for arch-independent builds.
74+
else()
75+
# NaCl does not support this architecture natively, but these defines must
76+
# be set because nacl_config.h is included unconditionally. Use dummy x86
77+
# values as PNaCl does for architecture-independent builds.
7778
add_definitions( -DNACL_BUILD_ARCH=x86 )
7879
add_definitions( -DNACL_BUILD_SUBARCH=64 )
79-
else()
80-
message(WARNING "Unknown architecture ${NACL_ARCH}")
8180
endif()
8281
endif()
8382

src/engine/framework/VirtualMachine.cpp

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4242
#include <spawn.h>
4343
#include <fcntl.h>
4444
#include <sys/wait.h>
45+
// POSIX: environ is the process environment, not always declared in headers.
46+
extern char **environ;
4547
#ifdef __linux__
4648
#include <sys/prctl.h>
4749
#if defined(DAEMON_ARCH_armhf)
@@ -73,6 +75,54 @@ static Cvar::Cvar<bool> workaround_naclSystem_freebsd_disableQualification(
7375
"Disable platform qualification when running Linux NaCl loader on FreeBSD through Linuxulator",
7476
Cvar::NONE, true);
7577

78+
#if defined(DAEMON_NACL_BOX64_EMULATION)
79+
static Cvar::Cvar<bool> workaround_box64_disableQualification(
80+
"workaround.box64.disableQualification",
81+
"Disable platform qualification when running amd64 NaCl loader under Box64 emulation",
82+
Cvar::NONE, true);
83+
84+
static Cvar::Cvar<bool> workaround_box64_disableBootstrap(
85+
"workaround.box64.disableBootstrap",
86+
"Disable NaCl bootstrap helper when using Box64 emulation",
87+
Cvar::NONE, true);
88+
89+
static Cvar::Cvar<std::string> vm_box64_path(
90+
"vm.box64.path",
91+
"Path to the box64 binary for NaCl emulation (empty = search PATH)",
92+
Cvar::NONE, "");
93+
94+
// Resolve box64 binary path by searching PATH if not explicitly set.
95+
static std::string ResolveBox64Path() {
96+
std::string path = vm_box64_path.Get();
97+
if (!path.empty()) {
98+
return path;
99+
}
100+
101+
const char* envPath = getenv("PATH");
102+
if (!envPath) {
103+
Sys::Error("Box64 emulation is enabled but PATH is not set and vm.box64.path is empty.");
104+
}
105+
106+
std::string pathStr(envPath);
107+
size_t start = 0;
108+
while (start < pathStr.size()) {
109+
size_t end = pathStr.find(':', start);
110+
if (end == std::string::npos) {
111+
end = pathStr.size();
112+
}
113+
std::string candidate = pathStr.substr(start, end - start) + "/box64";
114+
if (access(candidate.c_str(), X_OK) == 0) {
115+
return candidate;
116+
}
117+
start = end + 1;
118+
}
119+
120+
Sys::Error("Box64 emulation is enabled but 'box64' was not found in PATH. "
121+
"Install Box64 or set vm.box64.path to the full path of the box64 binary.");
122+
return ""; // unreachable
123+
}
124+
#endif
125+
76126
static Cvar::Cvar<bool> vm_nacl_qualification(
77127
"vm.nacl.qualification",
78128
"Enable NaCl loader platform qualification",
@@ -128,7 +178,7 @@ static void CheckMinAddressSysctlTooLarge()
128178
}
129179

130180
// Platform-specific code to load a module
131-
static std::pair<Sys::OSHandle, IPC::Socket> InternalLoadModule(std::pair<IPC::Socket, IPC::Socket> pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File())
181+
static std::pair<Sys::OSHandle, IPC::Socket> InternalLoadModule(std::pair<IPC::Socket, IPC::Socket> pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File(), bool inheritEnvironment = false)
132182
{
133183
#ifdef _WIN32
134184
// Inherit the socket in the child process
@@ -213,6 +263,7 @@ static std::pair<Sys::OSHandle, IPC::Socket> InternalLoadModule(std::pair<IPC::S
213263
if (reserve_mem)
214264
VirtualAllocEx(processInfo.hProcess, nullptr, 1 << 30, MEM_RESERVE, PAGE_NOACCESS);
215265
#endif
266+
Q_UNUSED(inheritEnvironment);
216267

217268
ResumeThread(processInfo.hThread);
218269
CloseHandle(processInfo.hThread);
@@ -233,7 +284,13 @@ static std::pair<Sys::OSHandle, IPC::Socket> InternalLoadModule(std::pair<IPC::S
233284
}
234285

235286
pid_t pid;
236-
int err = posix_spawn(&pid, args[0], &fileActions, nullptr, const_cast<char* const*>(args), nullptr);
287+
// By default, the child process gets an empty environment for sandboxing.
288+
// When Box64 emulation is used, the child needs to inherit the parent's
289+
// environment so Box64 can find its configuration (e.g. ~/.box64rc, HOME)
290+
// and honor settings like BOX64_DYNAREC_PERFMAP.
291+
char* emptyEnv[] = {nullptr};
292+
char** envp = inheritEnvironment ? environ : emptyEnv;
293+
int err = posix_spawn(&pid, args[0], &fileActions, nullptr, const_cast<char* const*>(args), envp);
237294
posix_spawn_file_actions_destroy(&fileActions);
238295
if (err != 0) {
239296
Sys::Drop("VM: Failed to spawn process: %s", strerror(err));
@@ -255,6 +312,10 @@ static std::pair<Sys::OSHandle, IPC::Socket> CreateNaClVM(std::pair<IPC::Socket,
255312
char rootSocketRedir[32];
256313
std::string module, nacl_loader, irt, bootstrap, modulePath, verbosity;
257314
FS::File stderrRedirect;
315+
#if defined(DAEMON_NACL_BOX64_EMULATION)
316+
std::string box64Path;
317+
bool usingBox64 = false;
318+
#endif
258319
#if !defined(_WIN32) || defined(_WIN64)
259320
constexpr bool win32Force64Bit = false;
260321
#else
@@ -304,6 +365,34 @@ static std::pair<Sys::OSHandle, IPC::Socket> CreateNaClVM(std::pair<IPC::Socket,
304365
}
305366

306367
#if defined(__linux__) || defined(__FreeBSD__)
368+
#if defined(DAEMON_NACL_BOX64_EMULATION)
369+
/* Use Box64 to run the x86_64 NaCl loader on non-x86 architectures.
370+
The bootstrap helper uses a double-exec pattern that Box64 cannot handle,
371+
so we skip it and prepend "box64" to the nacl_loader command instead. */
372+
if (!workaround_box64_disableBootstrap.Get() && vm_nacl_bootstrap.Get()) {
373+
bootstrap = FS::Path::Build(naclPath, "nacl_helper_bootstrap");
374+
375+
if (!FS::RawPath::FileExists(bootstrap)) {
376+
Sys::Error("NaCl bootstrap helper not found: %s", bootstrap);
377+
}
378+
379+
args.push_back(bootstrap.c_str());
380+
args.push_back(nacl_loader.c_str());
381+
args.push_back("--r_debug=0xXXXXXXXXXXXXXXXX");
382+
args.push_back("--reserved_at_zero=0xXXXXXXXXXXXXXXXX");
383+
} else {
384+
if (workaround_box64_disableBootstrap.Get()) {
385+
Log::Notice("Skipping NaCl bootstrap helper for Box64 emulation.");
386+
} else {
387+
Log::Warn("Not using NaCl bootstrap helper.");
388+
}
389+
box64Path = ResolveBox64Path();
390+
Log::Notice("Using Box64 emulator: %s", box64Path);
391+
args.push_back(box64Path.c_str());
392+
args.push_back(nacl_loader.c_str());
393+
usingBox64 = true;
394+
}
395+
#else
307396
if (vm_nacl_bootstrap.Get()) {
308397
#if defined(DAEMON_ARCH_arm64)
309398
bootstrap = FS::Path::Build(naclPath, "nacl_helper_bootstrap-armhf");
@@ -323,6 +412,7 @@ static std::pair<Sys::OSHandle, IPC::Socket> CreateNaClVM(std::pair<IPC::Socket,
323412
Log::Warn("Not using NaCl bootstrap helper.");
324413
args.push_back(nacl_loader.c_str());
325414
}
415+
#endif
326416
#else
327417
Q_UNUSED(bootstrap);
328418
args.push_back(nacl_loader.c_str());
@@ -381,6 +471,17 @@ static std::pair<Sys::OSHandle, IPC::Socket> CreateNaClVM(std::pair<IPC::Socket,
381471
enableQualification = false;
382472
}
383473
#endif
474+
475+
#if defined(DAEMON_NACL_BOX64_EMULATION)
476+
/* When running the amd64 NaCl loader under Box64, the loader's
477+
platform qualification will fail because the CPU is not actually x86_64.
478+
Disabling qualification allows the emulated loader to proceed. */
479+
480+
if (workaround_box64_disableQualification.Get()) {
481+
Log::Warn("Disabling NaCL platform qualification for Box64 emulation.");
482+
enableQualification = false;
483+
}
484+
#endif
384485
}
385486
else {
386487
Log::Warn("Not using NaCl platform qualification.");
@@ -427,7 +528,11 @@ static std::pair<Sys::OSHandle, IPC::Socket> CreateNaClVM(std::pair<IPC::Socket,
427528
Log::Notice("Using loader args: %s", commandLine.c_str());
428529
}
429530

430-
return InternalLoadModule(std::move(pair), args.data(), true, std::move(stderrRedirect));
531+
return InternalLoadModule(std::move(pair), args.data(), true, std::move(stderrRedirect)
532+
#if defined(DAEMON_NACL_BOX64_EMULATION)
533+
, usingBox64
534+
#endif
535+
);
431536
}
432537

433538
static std::pair<Sys::OSHandle, IPC::Socket> CreateNativeVM(std::pair<IPC::Socket, IPC::Socket> pair, Str::StringRef name, bool debug) {

0 commit comments

Comments
 (0)