diff --git a/oneshot/shot.py b/oneshot/shot.py index 1852468..a8e121c 100644 --- a/oneshot/shot.py +++ b/oneshot/shot.py @@ -77,6 +77,7 @@ async def run_pycdc_async( seq_file_path: str, path_for_log: str, *, + unit_buf: bool = False, no_banner: bool = False, show_all: bool = False, show_err_opcode: bool = False, @@ -85,6 +86,8 @@ async def run_pycdc_async( logger = logging.getLogger("shot") try: options = [] + if unit_buf: + options.append("--unitbuf") if no_banner: options.append("--no-banner") process = await asyncio.create_subprocess_exec( @@ -103,6 +106,21 @@ async def run_pycdc_async( logger.warning(f"PYCDC: {line} ({path_for_log})") for line in stderr_lines: + if not unit_buf and line.startswith("Access violation caught"): + # retry with --unitbuf + await run_pycdc_async( + exe_path, + seq_file_path, + path_for_log, + unit_buf=True, + no_banner=no_banner, + show_all=show_all, + show_err_opcode=show_err_opcode, + show_warn_stack=show_warn_stack, + ) + # do not log anything because it will be logged in the retried call + return + if line.startswith( ( "Warning: Stack history is empty", @@ -121,6 +139,7 @@ async def run_pycdc_async( "Unsupported argument", "Unsupported Node type", "Unsupported node type", + "Access violation caught", ) ): # annoying wont-fix errors if show_all: diff --git a/pycdc/pyarmor-1shot.cpp b/pycdc/pyarmor-1shot.cpp index 2164fd3..09ff189 100644 --- a/pycdc/pyarmor-1shot.cpp +++ b/pycdc/pyarmor-1shot.cpp @@ -1,3 +1,19 @@ +#include +#include +#include +#include +#include + +#ifndef _MSC_VER +#include +#endif + +#ifdef _WIN32 +#include +#include +#include +#endif + /** I want to use functions in pycdas.cpp directly, but not moving them to * another file, to sync with upstream in the future easily. */ @@ -9,10 +25,73 @@ const char* VERSION = "v0.2.1+"; +#ifdef _WIN32 + +// Windows: Use SEH/UEF; prefer calling only Win32 APIs +#ifdef __cpp_lib_fstream_native_handle +static HANDLE g_dc_h = INVALID_HANDLE_VALUE; +static HANDLE g_das_h = INVALID_HANDLE_VALUE; +#endif + +static LONG WINAPI av_handler(EXCEPTION_POINTERS* /*ep*/) { + const char msg[] = "Access violation caught. Best-effort FlushFileBuffers.\n"; + DWORD wrote = 0; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, sizeof(msg) - 1, &wrote, nullptr); +#ifdef __cpp_lib_fstream_native_handle + if (g_das_h != INVALID_HANDLE_VALUE) FlushFileBuffers(g_das_h); + if (g_dc_h != INVALID_HANDLE_VALUE) FlushFileBuffers(g_dc_h); +#endif + TerminateProcess(GetCurrentProcess(), 0xC0000005); + return EXCEPTION_EXECUTE_HANDLER; +} + +struct SehInstall { + SehInstall() { + // Suppress WER popups; let the UEF handle it directly + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + SetUnhandledExceptionFilter(av_handler); + } +} seh_install_guard; + +#else // !_WIN32 + +#ifdef __cpp_lib_fstream_native_handle +static int g_dc_fd = -1; +static int g_das_fd = -1; + +static void segv_handler(int sig) { + const char msg[] = "Access violation caught. Best-effort fsync.\n"; + // Only use async-signal-safe functions + write(STDERR_FILENO, msg, sizeof(msg)-1); + if (g_das_fd != -1) fsync(g_das_fd); + if (g_dc_fd != -1) fsync(g_dc_fd); + _Exit(128 + sig); +} +#else +static void segv_handler(int sig) { + const char msg[] = "Access violation caught.\n"; + write(STDERR_FILENO, msg, sizeof(msg)-1); + _Exit(128 + sig); +} +#endif + +struct SegvInstall { + SegvInstall() { + struct sigaction sa{}; + sa.sa_handler = segv_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGSEGV, &sa, nullptr); + } +} segv_install_guard; + +#endif // _WIN32 + int main(int argc, char* argv[]) { const char* infile = nullptr; unsigned disasm_flags = 0; + bool unitbuf = false; bool banner = true; std::ofstream dc_out_file; std::ofstream das_out_file; @@ -27,9 +106,12 @@ int main(int argc, char* argv[]) fputs("Options:\n", stderr); fputs(" --pycode-extra Show extra fields in PyCode object dumps\n", stderr); fputs(" --show-caches Don't suprress CACHE instructions in Python 3.11+ disassembly\n", stderr); + fputs(" --unitbuf Set output streams to be unbuffered\n", stderr); fputs(" --no-banner Don't output banner\n", stderr); fputs(" --help Show this help text and then exit\n", stderr); return 0; + } else if (strcmp(argv[arg], "--unitbuf") == 0) { + unitbuf = true; } else if (strcmp(argv[arg], "--no-banner") == 0) { banner = false; } else if (argv[arg][0] == '-') { @@ -58,17 +140,51 @@ int main(int argc, char* argv[]) } dc_out_file.open(prefix_name + ".cdc.py", std::ios_base::out); + if (unitbuf) { + dc_out_file.setf(std::ios::unitbuf); + } if (dc_out_file.fail()) { fprintf(stderr, "Error opening file '%s' for writing\n", (prefix_name + ".cdc.py").c_str()); return 1; } das_out_file.open(prefix_name + ".das", std::ios_base::out); + if (unitbuf) { + das_out_file.setf(std::ios::unitbuf); + } if (das_out_file.fail()) { fprintf(stderr, "Error opening file '%s' for writing\n", (prefix_name + ".das").c_str()); return 1; } +#ifdef __cpp_lib_fstream_native_handle +#ifndef _WIN32 + g_dc_fd = dc_out_file.native_handle(); + g_das_fd = das_out_file.native_handle(); +#else + // Extract underlying handles to flush on exceptions + // MSVC's native_handle is typically a HANDLE; MinGW may return a fd, requiring conversion via _get_osfhandle + auto dc_nh = dc_out_file.native_handle(); + auto das_nh = das_out_file.native_handle(); + using native_handle_t = decltype(dc_nh); + if constexpr (std::is_same_v) { + g_dc_h = dc_nh; + g_das_h = das_nh; + } else if constexpr (std::is_integral_v) { + intptr_t dc_handle = _get_osfhandle(dc_nh); + if (dc_handle != -1 && dc_handle != reinterpret_cast(INVALID_HANDLE_VALUE)) { + g_dc_h = reinterpret_cast(dc_handle); + } + intptr_t das_handle = _get_osfhandle(das_nh); + if (das_handle != -1 && das_handle != reinterpret_cast(INVALID_HANDLE_VALUE)) { + g_das_h = reinterpret_cast(das_handle); + } + } else { + // ignore, keep as INVALID_HANDLE_VALUE + } +#endif +#endif + PycModule mod; try { mod.loadFromOneshotSequenceFile(infile);