// Standalone native ARM32 EXECUTABLE (not a .so, unlike ostream_repro.cpp's // own SHARED library target - see this directory's CMakeLists.txt) for the // 2026-09-17 "does real hardware ever see runaway basic_stringbuf::overflow() // growth" check (ARM64_TRANSLATION_LAYER.md). Built c++_static (same as // ostream_repro.cpp's own build.sh switched to - real, compiled // basic_string/basic_stringbuf logic baked directly into this // binary's own .text, not external imports), meant to run DIRECTLY on the // Galaxy A9 via `adb shell` - no Unicorn, no GuestEngine, no shims of any // kind involved. Pure ground truth: does the SAME growth pattern observed // under GuestEngine (push_back-driven capacity doubling via // basic_stringbuf::overflow(), real disasm at sub_2700E4/sub_27036C/ // sub_27003C) ever get stuck, or does it complete normally no matter how far // it's pushed? #include #include #include #include #include using Clock = std::chrono::steady_clock; int main(int argc, char** argv) { // Default: push well past the ~8MB (0x800000) point where GuestEngine's // own trace showed the crash - if real hardware sails through this same // magnitude with no trouble, that's strong evidence the growth mechanism // itself is fine and the bug is specific to GuestEngine (its own malloc // shim, or something else in its translation of these particular // instructions), not a genuine bug in the shipped game/libc++ pairing. long targetChars = (argc > 1) ? atol(argv[1]) : 20L * 1024 * 1024; // 20M printf("ostream_stress: appending %ld chars one at a time via push_back\n", targetChars); fflush(stdout); auto t0 = Clock::now(); std::string s; for (long i = 0; i < targetChars; i++) { s.push_back((char)('a' + (i % 26))); // Progress heartbeat every 1M chars, and explicitly flag whenever // capacity crosses the same doubling milestones GuestEngine's own // register trace captured (0xfffff, 0x1fffff, 0x3fffff, 0x7fffff) - // lets a hang be diagnosed by "last milestone reached" even if the // process needs to be killed rather than exiting cleanly. if (i != 0 && (i % (1L * 1024 * 1024)) == 0) { auto elapsedMs = std::chrono::duration_cast(Clock::now() - t0).count(); printf(" [%ldms] i=%ld cap=%zu size=%zu\n", (long)elapsedMs, i, s.capacity(), s.size()); fflush(stdout); } } auto totalMs = std::chrono::duration_cast(Clock::now() - t0).count(); printf("ostream_stress: push_back loop done in %ldms - final size=%zu cap=%zu\n", (long)totalMs, s.size(), s.capacity()); fflush(stdout); // Second phase: the ACTUAL crashing pattern is via basic_stringbuf's own // xsputn/overflow chain (operator<<), not raw std::string::push_back - // exercise that path too, via repeated small ostringstream writes, // mirroring sub_4702D8's own "several separate writes before one // extraction" shape but looped enough times to force the same magnitude // of reallocation. printf("ostream_stress: now stress-testing via ostringstream operator<<\n"); fflush(stdout); auto t1 = Clock::now(); std::ostringstream oss; long chunkCount = targetChars / 16; // ~16 chars per write, same order of magnitude for (long i = 0; i < chunkCount; i++) { oss << "0123456789ABCDEF"; if (i != 0 && (i % (65536)) == 0) { auto elapsedMs = std::chrono::duration_cast(Clock::now() - t1).count(); std::string cur = oss.str(); printf(" [oss %ldms] i=%ld size-so-far=%zu\n", (long)elapsedMs, i, cur.size()); fflush(stdout); } } std::string result = oss.str(); auto ossMs = std::chrono::duration_cast(Clock::now() - t1).count(); printf("ostream_stress: ostringstream loop done in %ldms - final size=%zu\n", (long)ossMs, result.size()); fflush(stdout); return 0; }