From c0d56b127db2578ed2151876e8ecbf3cb4e1b2af Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 6 Oct 2026 09:42:25 +0800 Subject: [PATCH] =?UTF-8?q?chore(comments):=20includes=20=E5=A4=B4?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=8B=B1=E6=96=87=E5=8C=96=EF=BC=88R11=20?= =?UTF-8?q?=E6=94=B6=E5=B0=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 includes 下 64 个 .vp 的文件头注释(含 traceback.vp 第 13-427 行的整块文件级说明)以及 2 处漏网的正文中文注释(ast/lexer.vp、json/__writer.vp)与 1 处中文 docstring(memhub.vp)逐行译为英文;同步把 fltk/gen_ffi.py 的生成模板与其自身注释英文化,使重新生成仍为英文。 只改注释/文档串文字:行数与代码逐字节不变(骨架比对 140 文件漂移 0),运行时中文字符串字面量 305 处保持原样。验证:三重重编 SUCCESS(170.21s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0。 --- ast/lexer.vp | 34 +- fltk/__init__.vp | 20 +- fltk/_cfl.vp | 8 +- fltk/_cfl_box.vp | 8 +- fltk/_cfl_browser.vp | 8 +- fltk/_cfl_button.vp | 8 +- fltk/_cfl_dialog.vp | 8 +- fltk/_cfl_draw.vp | 8 +- fltk/_cfl_group.vp | 8 +- fltk/_cfl_image.vp | 8 +- fltk/_cfl_input.vp | 8 +- fltk/_cfl_lock.vp | 8 +- fltk/_cfl_menu.vp | 8 +- fltk/_cfl_misc.vp | 8 +- fltk/_cfl_prefs.vp | 8 +- fltk/_cfl_printer.vp | 8 +- fltk/_cfl_surface.vp | 8 +- fltk/_cfl_table.vp | 8 +- fltk/_cfl_term.vp | 8 +- fltk/_cfl_text.vp | 8 +- fltk/_cfl_theme.vp | 8 +- fltk/_cfl_tree.vp | 8 +- fltk/_cfl_utils.vp | 8 +- fltk/_cfl_valuator.vp | 8 +- fltk/_cfl_widget.vp | 8 +- fltk/_cfl_window.vp | 8 +- fltk/_enums.vp | 2 +- fltk/_types.vp | 6 +- fltk/gen_ffi.py | 114 +++---- gzip.vp | 14 +- json/__init__.vp | 2 +- json/__writer.vp | 20 +- memhub.vp | 22 +- mlirlite/__builder.vp | 38 +-- mlirlite/__init__.vp | 16 +- mlirlite/__values.vp | 10 +- mlirlite/__verify.vp | 18 +- openssl/__init__.vp | 22 +- openssl/bio.vp | 6 +- openssl/crypto.vp | 4 +- openssl/err.vp | 4 +- openssl/evp.vp | 8 +- openssl/hmac.vp | 4 +- openssl/md5.vp | 4 +- openssl/objects.vp | 4 +- openssl/pem.vp | 8 +- openssl/rand.vp | 4 +- openssl/sha.vp | 8 +- openssl/ssl.vp | 12 +- openssl/types.vp | 18 +- openssl/x509.vp | 6 +- requests/__init__.vp | 8 +- requests/download.vp | 20 +- requests/https.vp | 16 +- torch/__init__.vp | 28 +- traceback.vp | 704 +++++++++++++++++++++--------------------- vqt6/__init__.vp | 6 +- vqt6/_qtcore.vp | 6 +- vqt6/_qtgui.vp | 2 +- vqt6/_qtnetwork.vp | 2 +- vqt6/_qtwidgets.vp | 2 +- vqt6/_types.vp | 6 +- w32/win32dbghelp.vp | 46 +-- w32/winsock2.vp | 2 +- zipfile.vp | 20 +- 65 files changed, 744 insertions(+), 744 deletions(-) diff --git a/ast/lexer.vp b/ast/lexer.vp index 6503fa8..1113670 100644 --- a/ast/lexer.vp +++ b/ast/lexer.vp @@ -11,25 +11,25 @@ from .tokens import ( # ============================================================ -# 整数字面量溢出(词法层 fail-fast) +# Integer literal overflow (lexer-layer fail-fast) # -# 整数常量在 token/AST 中是 int64(tokens.vp `int_val` / exprs.vp `int_val`)。 -# 旧实现用 `string.atoll`(string.vp:292)在 int64 下 `num = num * 10 + digit` -# 逐位累加 ⇒ 值 > 2^64-1 时**静默回绕**(如 2^64 回绕成 0,2^64-1 回绕成 -1)。 -# 本实现在 `_emit_number` 里对**整数**字面量先做纯文本幅值判定 -# (`_lex_int_exceeds_u64`,与 AssignGenMixin._int_text_exceeds_64 同口径:按 -# 进制统计有效位数 / 与 2^64-1 的十进制文本做长度+字典序比较,**不落进任何定宽 -# 容器、不回绕后再比较**),超出 2^64-1 即记录 fail-fast(不再静默回绕)。 -# 合法范围 0..2^64-1 仍按 64 位位模式存入 int64(既有合法路径的产物形态不变, -# 如 0xFFFFFFFFFFFFFFFF → int64 -1、0xA5A5A5A5A5A5A5A5 保持其 64 位位模式)。 -# 各进制(2/8/10/16)口径与 TPC(Python int(x, 0),任意精度)一致:接受区间同为 -# [-2^63, 2^64-1](字面量本身无符号,负数侧由一元减表达),超出即拒绝。 +# Integer constants are int64 in the token/AST (tokens.vp `int_val` / exprs.vp `int_val`). +# The old implementation used `string.atoll` (string.vp:292) to, under int64, do `num = num * 10 + digit` +# digit-by-digit accumulation => when the value > 2^64-1 it **silently wraps** (e.g. 2^64 wraps to 0, 2^64-1 wraps to -1). +# This implementation, in `_emit_number`, first performs a pure-text magnitude check on **integer** literals +# (`_lex_int_exceeds_u64`, using the same criteria as AssignGenMixin._int_text_exceeds_64: by +# radix, counting significant digits / comparing length + lexicographic order against the decimal text of 2^64-1, **without landing in any fixed-width +# container and without wrapping before the comparison**), so exceeding 2^64-1 records a fail-fast (no more silent wrapping). +# The legal range 0..2^64-1 is still stored into int64 as a 64-bit bit pattern (the shape of the results on the existing legal path is unchanged, +# e.g. 0xFFFFFFFFFFFFFFFF -> int64 -1, 0xA5A5A5A5A5A5A5A5 keeps its 64-bit bit pattern). +# The criteria for each radix (2/8/10/16) match TPC's (Python int(x, 0), arbitrary precision): the accepted interval is likewise +# [-2^63, 2^64-1] (literals themselves are unsigned; the negative side is expressed by unary minus), and anything beyond is rejected. # -# 上报通道:lexer 无独立 fatal 出口,故记录到模块级三元组(`_lex_record_overflow`), -# 由 parser 的 parse_tokens 取出后转写 _g_parse_err_*(位置 + 消息),复用既有 -# Traceback/SyntaxError 报告路径("文件:行:列 + 源码行"由该路径补全并终止编译)。 -# 这覆盖全部源码字面量(文件级解析必经 parse_tokens);parse_expression 只用于编译器 -# 合成的短类型串,不承载源码字面量,故不另设通道。 +# Reporting channel: the lexer has no separate fatal exit, so it records into a module-level triple (`_lex_record_overflow`), +# which the parser's parse_tokens reads and transcribes into _g_parse_err_* (position + message), reusing the existing +# Traceback/SyntaxError report path (the "file:line:col + source line" is filled in by that path, which also terminates compilation). +# This covers all source literals (file-level parsing necessarily goes through parse_tokens); parse_expression is used only for compiler- +# synthesized short type strings and carries no source literals, so no separate channel is provided. # ============================================================ _g_lex_err_line: int = 0 _g_lex_err_col: int = 0 diff --git a/fltk/__init__.vp b/fltk/__init__.vp index 9e0f6e1..3a95fc4 100644 --- a/fltk/__init__.vp +++ b/fltk/__init__.vp @@ -1,7 +1,7 @@ # ============================================================ -# fltk — Viper 的 FLTK GUI 绑定包(对接 cfltk C API) +# fltk — Viper FLTK GUI binding package (wraps the cfltk C API) # -# 用户用法: +# Usage: # import fltk # win = fltk.Fl_Window(400, 300, "Hello") # box = fltk.Fl_Box(10, 10, 380, 40, "Hello, World!") @@ -10,18 +10,18 @@ # win.show() # fltk.Fl.run() # -# 说明: -# * 本包只做 extern FFI(fltk._cfl* 模块)+ 少量 Pythonic 包装类; -# 所有 FLTK 对象都是不透明指针,方法即 cfltk 的 Fl_<类>_<动作>。 -# * 完整 C API 通过 `from fltk._cfl import *` 等直接可用。 -# * 回调支持:Viper 模块级函数可直接作为 Fl_Callback 传给 FLTK—— -# TPV 把函数名自动 bitcast 成函数指针(如 CreateThread 传函数)。 -# 回调签名约定: def cb(w: bytes, data: bytes): ...(w=控件指针,data=用户数据) +# Notes: +# * This package only provides extern FFI (the fltk._cfl* modules) + a few Pythonic wrapper classes; +# all FLTK objects are opaque pointers; methods map to cfltk's Fl__. +# * The full C API is directly available via `from fltk._cfl import *` etc. +# * Callback support: a Viper module-level function can be passed to FLTK directly as an Fl_Callback-- +# TPV auto-bitcasts the function name into a function pointer (e.g. CreateThread passes a function). +# Callback signature convention: def cb(w: bytes, data: bytes): ... (w=widget pointer, data=user data) # def on_click(w: bytes, data: bytes): # Fl_Widget_set_label(w, "Clicked!") # btn.set_callback(on_click, 0) # Fl.add_timeout(1.0, on_tick, 0) -# * 事件驱动也可用 Fl.check()/Fl.event*() 轮询。 +# * Event-driven code may also poll with Fl.check()/Fl.event*(). # ============================================================ import t, c diff --git a/fltk/_cfl.vp b/fltk/_cfl.vp index cc58afd..24cac18 100644 --- a/fltk/_cfl.vp +++ b/fltk/_cfl.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl.vp — cfl.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl.vp — extern FFI declarations for cfl.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_run() -> int extern def Fl_check() -> int diff --git a/fltk/_cfl_box.vp b/fltk/_cfl_box.vp index 23f1460..0342d60 100644 --- a/fltk/_cfl_box.vp +++ b/fltk/_cfl_box.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_box.vp — cfl_box.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_box.vp — extern FFI declarations for cfl_box.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Box (widget_declare) ----- extern def Fl_Box_new(x: int, y: int, width: int, height: int, title: str) -> Fl_BoxPtr diff --git a/fltk/_cfl_browser.vp b/fltk/_cfl_browser.vp index d6dc49c..4ef67d9 100644 --- a/fltk/_cfl_browser.vp +++ b/fltk/_cfl_browser.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_browser.vp — cfl_browser.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_browser.vp — extern FFI declarations for cfl_browser.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Browser (widget_declare) ----- extern def Fl_Browser_new(x: int, y: int, width: int, height: int, title: str) -> Fl_BrowserPtr diff --git a/fltk/_cfl_button.vp b/fltk/_cfl_button.vp index ab32ab1..fc2f4bc 100644 --- a/fltk/_cfl_button.vp +++ b/fltk/_cfl_button.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_button.vp — cfl_button.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_button.vp — extern FFI declarations for cfl_button.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Button (widget_declare) ----- extern def Fl_Button_new(x: int, y: int, width: int, height: int, title: str) -> Fl_ButtonPtr diff --git a/fltk/_cfl_dialog.vp b/fltk/_cfl_dialog.vp index deb6c94..2603732 100644 --- a/fltk/_cfl_dialog.vp +++ b/fltk/_cfl_dialog.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_dialog.vp — cfl_dialog.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_dialog.vp — extern FFI declarations for cfl_dialog.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_Native_File_Chooser_new(a0: int) -> Fl_Native_File_ChooserPtr extern def Fl_Native_File_Chooser_delete(self: Fl_Native_File_ChooserPtr) diff --git a/fltk/_cfl_draw.vp b/fltk/_cfl_draw.vp index 9a3bab6..c4b40fc 100644 --- a/fltk/_cfl_draw.vp +++ b/fltk/_cfl_draw.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_draw.vp — cfl_draw.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_draw.vp — extern FFI declarations for cfl_draw.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_set_color_int(c: uint32) extern def Fl_set_color_rgb(r: uint8, g: uint8, b: uint8) diff --git a/fltk/_cfl_group.vp b/fltk/_cfl_group.vp index 66d308c..0c432d1 100644 --- a/fltk/_cfl_group.vp +++ b/fltk/_cfl_group.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_group.vp — cfl_group.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_group.vp — extern FFI declarations for cfl_group.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Group (widget_declare) ----- extern def Fl_Group_new(x: int, y: int, width: int, height: int, title: str) -> Fl_GroupPtr diff --git a/fltk/_cfl_image.vp b/fltk/_cfl_image.vp index e254802..50ef626 100644 --- a/fltk/_cfl_image.vp +++ b/fltk/_cfl_image.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_image.vp — cfl_image.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_image.vp — extern FFI declarations for cfl_image.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Image (image_declare) ----- extern def Fl_Image_draw(self: Fl_ImagePtr, X: int, Y: int, W: int, H: int) diff --git a/fltk/_cfl_input.vp b/fltk/_cfl_input.vp index 1d65e4d..4fa7554 100644 --- a/fltk/_cfl_input.vp +++ b/fltk/_cfl_input.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_input.vp — cfl_input.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_input.vp — extern FFI declarations for cfl_input.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Input (widget_declare) ----- extern def Fl_Input_new(x: int, y: int, width: int, height: int, title: str) -> Fl_InputPtr diff --git a/fltk/_cfl_lock.vp b/fltk/_cfl_lock.vp index 5d0b0b2..5406724 100644 --- a/fltk/_cfl_lock.vp +++ b/fltk/_cfl_lock.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_lock.vp — cfl_lock.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_lock.vp — extern FFI declarations for cfl_lock.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_mutex_lock() extern def Fl_mutex_unlock() diff --git a/fltk/_cfl_menu.vp b/fltk/_cfl_menu.vp index dd085c6..1e2e503 100644 --- a/fltk/_cfl_menu.vp +++ b/fltk/_cfl_menu.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_menu.vp — cfl_menu.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_menu.vp — extern FFI declarations for cfl_menu.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Menu_Bar (widget_declare) ----- extern def Fl_Menu_Bar_new(x: int, y: int, width: int, height: int, title: str) -> Fl_Menu_BarPtr diff --git a/fltk/_cfl_misc.vp b/fltk/_cfl_misc.vp index 8c282bc..eca565f 100644 --- a/fltk/_cfl_misc.vp +++ b/fltk/_cfl_misc.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_misc.vp — cfl_misc.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_misc.vp — extern FFI declarations for cfl_misc.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Spinner (widget_declare) ----- extern def Fl_Spinner_new(x: int, y: int, width: int, height: int, title: str) -> Fl_SpinnerPtr diff --git a/fltk/_cfl_prefs.vp b/fltk/_cfl_prefs.vp index 9d53157..f8110cc 100644 --- a/fltk/_cfl_prefs.vp +++ b/fltk/_cfl_prefs.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_prefs.vp — cfl_prefs.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_prefs.vp — extern FFI declarations for cfl_prefs.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_Preferences_set_file_access(flags: uint32) extern def Fl_Preferences_file_access() -> uint32 diff --git a/fltk/_cfl_printer.vp b/fltk/_cfl_printer.vp index 3ba8bf7..7951f8d 100644 --- a/fltk/_cfl_printer.vp +++ b/fltk/_cfl_printer.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_printer.vp — cfl_printer.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_printer.vp — extern FFI declarations for cfl_printer.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_Printer_new() -> Fl_PrinterPtr extern def Fl_Printer_delete(self: Fl_PrinterPtr) diff --git a/fltk/_cfl_surface.vp b/fltk/_cfl_surface.vp index 150793c..4396bdf 100644 --- a/fltk/_cfl_surface.vp +++ b/fltk/_cfl_surface.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_surface.vp — cfl_surface.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_surface.vp — extern FFI declarations for cfl_surface.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_Surface_Device_set_current(self: Fl_Surface_DevicePtr) extern def Fl_Surface_Device_is_current(self: Fl_Surface_DevicePtr) -> int diff --git a/fltk/_cfl_table.vp b/fltk/_cfl_table.vp index 3bdff99..89dcfd9 100644 --- a/fltk/_cfl_table.vp +++ b/fltk/_cfl_table.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_table.vp — cfl_table.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_table.vp — extern FFI declarations for cfl_table.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Table (widget_declare) ----- extern def Fl_Table_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TablePtr diff --git a/fltk/_cfl_term.vp b/fltk/_cfl_term.vp index 1ccb3c7..56f7c34 100644 --- a/fltk/_cfl_term.vp +++ b/fltk/_cfl_term.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_term.vp — cfl_term.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_term.vp — extern FFI declarations for cfl_term.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Terminal (widget_declare) ----- extern def Fl_Terminal_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TerminalPtr diff --git a/fltk/_cfl_text.vp b/fltk/_cfl_text.vp index 8a331f4..162fa3b 100644 --- a/fltk/_cfl_text.vp +++ b/fltk/_cfl_text.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_text.vp — cfl_text.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_text.vp — extern FFI declarations for cfl_text.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Text_Display (widget_declare) ----- extern def Fl_Text_Display_new(x: int, y: int, width: int, height: int, title: str) -> Fl_Text_DisplayPtr diff --git a/fltk/_cfl_theme.vp b/fltk/_cfl_theme.vp index 92ad7b5..eafcb23 100644 --- a/fltk/_cfl_theme.vp +++ b/fltk/_cfl_theme.vp @@ -1,9 +1,9 @@ # ============================================================ -# fltk/_cfl_theme.vp — Fl_Theme(C++ 主题库)→ Viper extern FFI +# fltk/_cfl_theme.vp — Fl_Theme (C++ theme library) -> Viper extern FFI # -# 对应 Fl_Theme/fltk_theme.cpp 导出的 C 函数(手工维护,gen_ffi 不覆盖)。 -# 链接需 libfltk_theme.a(见 project.vpj linker.static_libs)+ -# libcfltk2.a + libfltk_images.a(Fl_SVG_Image)。 +# Corresponds to the C functions exported by Fl_Theme/fltk_theme.cpp (hand-maintained, not overwritten by gen_ffi). +# Linking requires libfltk_theme.a (see project.vpj linker.static_libs) + +# libcfltk2.a + libfltk_images.a (Fl_SVG_Image). # ============================================================ import t, c diff --git a/fltk/_cfl_tree.vp b/fltk/_cfl_tree.vp index 24c2466..6d72941 100644 --- a/fltk/_cfl_tree.vp +++ b/fltk/_cfl_tree.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_tree.vp — cfl_tree.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_tree.vp — extern FFI declarations for cfl_tree.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Tree (widget_declare) ----- extern def Fl_Tree_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TreePtr diff --git a/fltk/_cfl_utils.vp b/fltk/_cfl_utils.vp index 044e5b0..ed30df9 100644 --- a/fltk/_cfl_utils.vp +++ b/fltk/_cfl_utils.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_utils.vp — cfl_utils.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_utils.vp — extern FFI declarations for cfl_utils.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). extern def Fl_filename_expand(out: str, len_: int, in_: str) -> int extern def Fl_type_name(w: bytes) -> str diff --git a/fltk/_cfl_valuator.vp b/fltk/_cfl_valuator.vp index eb122b4..a4580a8 100644 --- a/fltk/_cfl_valuator.vp +++ b/fltk/_cfl_valuator.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_valuator.vp — cfl_valuator.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_valuator.vp — extern FFI declarations for cfl_valuator.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Slider (widget_declare) ----- extern def Fl_Slider_new(x: int, y: int, width: int, height: int, title: str) -> Fl_SliderPtr diff --git a/fltk/_cfl_widget.vp b/fltk/_cfl_widget.vp index f20894c..2ed8551 100644 --- a/fltk/_cfl_widget.vp +++ b/fltk/_cfl_widget.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_widget.vp — cfl_widget.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_widget.vp — extern FFI declarations for cfl_widget.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Widget (widget_declare) ----- extern def Fl_Widget_new(x: int, y: int, width: int, height: int, title: str) -> Fl_WidgetPtr diff --git a/fltk/_cfl_window.vp b/fltk/_cfl_window.vp index 64eb3a6..8170091 100644 --- a/fltk/_cfl_window.vp +++ b/fltk/_cfl_window.vp @@ -2,12 +2,12 @@ import t, c from fltk._types import * # ============================================================ -# fltk/_cfl_window.vp — cfl_window.h 的 extern FFI 声明(对接 cfltk C API) +# fltk/_cfl_window.vp — extern FFI declarations for cfl_window.h (cfltk C API) # ============================================================ -# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。 -# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str; -# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。 +# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2. +# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str; +# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side). # ----- Fl_Window (widget_declare) ----- extern def Fl_Window_new(x: int, y: int, width: int, height: int, title: str) -> Fl_WindowPtr diff --git a/fltk/_enums.vp b/fltk/_enums.vp index 3e8c400..f47ab54 100644 --- a/fltk/_enums.vp +++ b/fltk/_enums.vp @@ -1,7 +1,7 @@ import t, c # ============================================================ -# fltk/_enums.vp — cfltk 枚举常量(t.CInt) +# fltk/_enums.vp — cfltk enum constants (t.CInt) # ============================================================ Fl_BrowserType_Normal: int = 0 diff --git a/fltk/_types.vp b/fltk/_types.vp index c578833..762ea95 100644 --- a/fltk/_types.vp +++ b/fltk/_types.vp @@ -1,11 +1,11 @@ import t, c # ============================================================ -# fltk/_types.vp — FLTK 不透明对象指针 typedef(对接 cfltk C API) +# fltk/_types.vp — opaque FLTK object pointer typedefs (cfltk C API) # ============================================================ -# 所有 FLTK C++ 对象在 FFI 中一律按不透明指针处理,生命周期由 -# cfltk/fltk 库管理(new/delete 见各 *_delete / *_free)。 +# Every FLTK C++ object is handled as an opaque pointer in FFI; its lifetime is owned +# by the cfltk/fltk library (see each *_delete / *_free). Fl_AdjusterPtr: t.CTypedef = t.CPtr Fl_Anim_GIF_ImagePtr: t.CTypedef = t.CPtr diff --git a/fltk/gen_ffi.py b/fltk/gen_ffi.py index 432e3d9..2d94c10 100644 --- a/fltk/gen_ffi.py +++ b/fltk/gen_ffi.py @@ -1,20 +1,20 @@ # -*- coding: utf-8 -*- """ -fltk/gen_ffi.py — cfltk (FLTK C API) → Viper extern FFI 生成器 +fltk/gen_ffi.py - cfltk (FLTK C API) -> Viper extern FFI generator -从 cfltk/include/cfltk2/*.h 读取全部 C 声明: - * 解析所有 `#define XXX_DECLARE(param) ...` 宏定义(WIDGET/GROUP/WINDOW/ - BUTTON/INPUT/BROWSER/IMAGE/TABLE/DISPLAY/VALUATOR/MENU), - * 按每个头文件里的 `XXX_DECLARE(Class)` 调用展开, - * 连同头文件中的独立函数原型一起转成 extern def。 +Reads all C declarations from cfltk/include/cfltk2/*.h: + * Parse all `#define XXX_DECLARE(param) ...` macro definitions (WIDGET/GROUP/WINDOW/ + BUTTON/INPUT/BROWSER/IMAGE/TABLE/DISPLAY/VALUATOR/MENU), + * Expand each `XXX_DECLARE(Class)` call in every header, + * Convert them, along with the standalone function prototypes in the headers, into extern def. -生成 includes/fltk 下的 .vp FFI 文件: - _types.vp 不透明指针 typedef(每个 Fl_* 类一个 Fl_*Ptr) - _enums.vp cfl_enums.h 的全部枚举常量 - _cfl.vp 全局 Fl 函数(cfl.h) - _cfl_
.vp 各头文件对应 extern def +Generates the .vp FFI files under includes/fltk: + _types.vp opaque pointer typedefs (one Fl_*Ptr per Fl_* class) + _enums.vp all enum constants of cfl_enums.h + _cfl.vp global Fl functions (cfl.h) + _cfl_
.vp extern def per header file -用法: python includes/fltk/gen_ffi.py +Usage: python includes/fltk/gen_ffi.py """ import os import re @@ -23,15 +23,15 @@ HERE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(HERE, "cfltk", "include", "cfltk2") # ============================================================ -# C → Viper 类型映射 +# C -> Viper type mapping # ============================================================ -# 函数指针类型(Viper 无法表达,一律映射为不透明 bytes) +# Function pointer types (not expressible in Viper; always mapped to opaque bytes) CALLBACK_TYPES = { "Fl_Callback", "custom_handler_callback", "custom_draw_callback", "Fl_Awake_Handler", "Fl_Text_Modify_Cb", "Fl_Text_Predelete_Cb", } -# Viper/Python 关键字不能作参数名(C 里 `int Fl_compose(int *del)` 之类) +# Viper/Python keywords cannot be used as parameter names (e.g. C `int Fl_compose(int *del)`) _KEYWORDS = { "def", "class", "from", "import", "global", "nonlocal", "pass", "lambda", "del", "in", "is", "and", "or", "not", "for", "while", "if", "elif", @@ -40,8 +40,8 @@ _KEYWORDS = { "int", "str", "float", "bool", "len", } -# C 类型关键字:作无名参数末尾单词时属于类型的一部分而非参数名 -# (`unsigned int` → 末尾 int 不是参数名;`Fl_color_average(unsigned int, ...)`)。 +# C type keywords: when the last word of an unnamed parameter, they belong to the type, not the name +# (`unsigned int` -> trailing int is not the name; `Fl_color_average(unsigned int, ...)`). _C_TYPE_WORDS = { "int", "char", "short", "long", "unsigned", "signed", "float", "double", "void", "size_t", "uintptr_t", @@ -49,21 +49,21 @@ _C_TYPE_WORDS = { def map_type(t: str) -> str | None: - """返回 Viper 注解类型;void 返回 None。""" + """Return the Viper annotation type; returns None for void.""" t = t.strip() t = re.sub(r"\bconst\b", "", t).strip() t = re.sub(r"\bstruct\b", "", t).strip() t = re.sub(r"\s+", " ", t) if t in CALLBACK_TYPES: return "bytes" - if "(" in t: # 函数指针(未命名的 typedef 形态) + if "(" in t: # function pointer (unnamed typedef form) return "bytes" if t.endswith("[]"): t = t[:-2].strip() + "*" star_cnt = t.count("*") if star_cnt: base = t.replace("*", "").strip() - if star_cnt >= 2: # 指针的指针(int** 等)一律按不透明指针 + if star_cnt >= 2: # pointer-to-pointer (int** etc.) always treated as opaque pointer return "bytes" if base == "char": return "str" @@ -100,11 +100,11 @@ def map_type(t: str) -> str | None: return "t.CSizeT" if t.startswith("Fl_") or t.startswith("custom_"): return t + "Ptr" - return "bytes" # 未知类型一律按不透明指针处理 + return "bytes" # unknown types always treated as opaque pointer def split_args(args: str) -> list[tuple[str, str]]: - """拆分 C 参数串 → [(类型, 参数名)],函数指针/无名参数自动补名。""" + """Split a C argument string -> [(type, name)]; function pointers / unnamed args get an auto name.""" if not args.strip(): return [] parts: list[str] = [] @@ -125,19 +125,19 @@ def split_args(args: str) -> list[tuple[str, str]]: out: list[tuple[str, str]] = [] for p in parts: p = p.strip() - if not p or p == "void": # C 的 (void) 表示无参数 + if not p or p == "void": # C's (void) means no parameters continue if "(" in p and (")(" in p or "*)" in p or re.search(r"\(\s*\*", p)): out.append((p, "cb%d" % len(out))) continue - # C 参数形如 `Fl_Tree *self` / `const char *title`:'*' 紧贴参数名, - # 末尾单词即为参数名,其余部分为类型 + # A C parameter looks like `Fl_Tree *self` / `const char *title`: the '*' sticks to the name, + # so the last word is the name and the rest is the type m = re.search(r"(\w+)\s*$", p) if m: typ = p[:m.start()].strip() nm = m.group(1) - # 纯类型(无名)参数:无类型部分(`int`)或末尾单词本身是 C 类型 - # 关键字(`unsigned int` → 末尾 int 是类型的一部分,非参数名)。 + # Pure type (unnamed) parameter: no type part (`int`) or the last word is itself a C type + # keyword (`unsigned int` -> trailing int is part of the type, not the name). if not typ or nm in _C_TYPE_WORDS: typ, nm = p, "a%d" % len(out) if nm in _KEYWORDS: @@ -152,19 +152,19 @@ def emit_extern(name: str, params: list[tuple[str, str]], ret_t: str | None, indent: str = "") -> str: ps = ", ".join("%s: %s" % (pn, map_type(pt)) for pt, pn in params) rt = map_type(ret_t) if ret_t else None - if rt is None: # void / 无返回 + if rt is None: # void / no return return "%sextern def %s(%s)" % (indent, name, ps) return "%sextern def %s(%s) -> %s" % (indent, name, ps, rt) # ============================================================ -# 头文件解析 +# Header parsing # ============================================================ MACRO_CALL_RE = re.compile(r"(?m)^\s*(\w+_DECLARE)\((\w+)\)\s*$") def strip_macro_blocks(text: str) -> str: - """删除 #define 宏定义块(含全部反斜杠续行),逐行处理兼容 CRLF。""" + """Remove #define macro definition blocks (including all backslash continuations); line-by-line to tolerate CRLF.""" out: list[str] = [] skip = False for line in text.split("\n"): @@ -183,19 +183,19 @@ def strip_macro_blocks(text: str) -> str: def strip_preprocessor(text: str) -> str: text = strip_macro_blocks(text) text = re.sub(r"typedef[^;]*;", "", text, flags=re.S) # typedef - text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) # 注释 + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) # comments return text def extract_protos(text: str) -> list[tuple[str, str, str]]: - """提取独立函数原型 → [(返回类型, 函数名, 参数串)]。 + """Extract standalone function prototypes -> [(return type, function name, argument string)]. - 注意 C 的 `类型 *名字` 中 '*' 紧贴函数名、与类型之间才有空格, - 因此不能用空白分隔的 `类型 名字(` 拆分;改为取第一个 '(' 之前末尾的单词为 - 函数名,其余为返回类型。 + Note that in C `type *name` the '*' sticks to the function name and the space is before the type, + so it cannot be split by the whitespace-separated `type name(` form; instead take the last word + before the first '(' as the function name and the rest as the return type. """ text = strip_preprocessor(text) - text = re.sub(r"(?m)^\s*#.*$", "", text) # 其余预处理行 + text = re.sub(r"(?m)^\s*#.*$", "", text) # other preprocessor lines text = re.sub(MACRO_CALL_RE, "", text) protos: list[tuple[str, str, str]] = [] buf: list[str] = [] @@ -206,7 +206,7 @@ def extract_protos(text: str) -> list[tuple[str, str, str]]: elif ch == ")": depth -= 1 if depth == 0 and ch in "{}": - buf = [] # extern "C" { / 块花括号不进入原型缓冲 + buf = [] # extern "C" { / block braces do not enter the prototype buffer continue if ch == ";" and depth == 0: stmt = "".join(buf).strip() @@ -227,11 +227,11 @@ def extract_protos(text: str) -> list[tuple[str, str, str]]: def extract_macro_defs(text: str) -> dict: - """解析 `#define XXX_DECLARE(param)` 宏体 → 方法模板。 + """Parse `#define XXX_DECLARE(param)` macro bodies -> method templates. - 返回 {宏名: (param, [(方法后缀, 参数串, 返回类型, 'SELF'|'PARAMS'), ...])} - 'SELF' 表示首参为该类自身指针(展开时替换为 self: <类>Ptr), - 'PARAMS' 表示保留参数原样(new/from_dyn_ptr/super_draw 等)。 + Returns {macro name: (param, [(method suffix, argument string, return type, 'SELF'|'PARAMS'), ...])} + 'SELF' means the first argument is the class's own pointer (replaced with self: Ptr on expansion), + 'PARAMS' means the arguments are kept as-is (new/from_dyn_ptr/super_draw etc.). """ out: dict = {} lines = text.split("\n") @@ -252,7 +252,7 @@ def extract_macro_defs(text: str) -> dict: body.append(line) break methods: list = [] - body_src = "\n".join(body).replace("##", "") # widget##_x → widget_x + body_src = "\n".join(body).replace("##", "") # widget##_x -> widget_x for ret, fname, args in extract_protos(body_src): fn = fname.replace("##", "") suffix = fn[len(param):].lstrip("_") @@ -268,7 +268,7 @@ def extract_macro_defs(text: str) -> dict: def extract_typedefs(text: str) -> list[str]: - """提取顶层 typedef 名称(先剥离 #define 宏体,避免误扫 widget 等宏形参)。""" + """Extract top-level typedef names (strip #define macro bodies first to avoid scanning macro params like widget).""" names = [] text = strip_macro_blocks(text) for m in re.finditer(r"typedef\s+(?:struct\s+)?[\w\s\*\(\)]*?(\w+)\s*;", text): @@ -309,7 +309,7 @@ def extract_enums(text: str) -> list[tuple[str, int]]: # ============================================================ -# 生成 +# Generation # ============================================================ CLASS_PTRS: set[str] = set() @@ -320,7 +320,7 @@ def gen_class_methods(cls: str, param: str, methods) -> list[str]: fname = "%s_%s" % (cls, suffix) ret_t = re.sub(r"\b%s\b" % re.escape(param), cls, ret) if kind == "SELF": - rest = split_args(args)[1:] # 丢弃宏里的 self 参数 + rest = split_args(args)[1:] # drop the self parameter inside the macro ps = [(cls + " *", "self")] + rest else: ps = split_args(args) @@ -339,15 +339,15 @@ def gen_header(path: str, out_name: str, defs: dict) -> str: "from fltk._types import *", "", "# ============================================================", - "# fltk/%s — %s 的 extern FFI 声明(对接 cfltk C API)" % (out_name, base), + "# fltk/%s — extern FFI declarations for %s (cfltk C API)" % (out_name, base), "# ============================================================", "", - "# 所有函数直接对应 cfltk 生成的 C 符号(extern \"C\"),链接目标: cfltk2。", - "# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;", - "# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。", + "# Every function maps directly to a cfltk-generated C symbol (extern \"C\"); link target: cfltk2.", + "# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;", + "# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).", "", ] - # 按头文件中宏调用的顺序展开 + # expand in the order of macro calls in the header for mm in MACRO_CALL_RE.finditer(text): kind, cls = mm.group(1), mm.group(2) if kind in defs: @@ -355,7 +355,7 @@ def gen_header(path: str, out_name: str, defs: dict) -> str: lines.append("# ----- %s (%s) -----" % (cls, kind.lower())) lines.extend(gen_class_methods(cls, param, methods)) lines.append("") - # 独立原型 + # standalone prototypes protos = extract_protos(text) for ret, name, args in protos: lines.append(emit_extern(name, split_args(args), ret)) @@ -366,7 +366,7 @@ def main() -> None: os.makedirs(HERE, exist_ok=True) headers = sorted(f for f in os.listdir(SRC) if f.endswith(".h")) - # 1. 收集全部宏定义 + 类指针 typedef + # 1. Collect all macro definitions + class pointer typedefs defs: dict = {} for h in headers: with open(os.path.join(SRC, h), "r", encoding="utf-8", @@ -395,11 +395,11 @@ def main() -> None: "import t, c", "", "# ============================================================", - "# fltk/_types.vp — FLTK 不透明对象指针 typedef(对接 cfltk C API)", + "# fltk/_types.vp — opaque FLTK object pointer typedefs (cfltk C API)", "# ============================================================", "", - "# 所有 FLTK C++ 对象在 FFI 中一律按不透明指针处理,生命周期由", - "# cfltk/fltk 库管理(new/delete 见各 *_delete / *_free)。", + "# Every FLTK C++ object is handled as an opaque pointer in FFI; its lifetime is owned", + "# by the cfltk/fltk library (see each *_delete / *_free).", "", ] for p in sorted_ptrs: @@ -416,7 +416,7 @@ def main() -> None: "import t, c", "", "# ============================================================", - "# fltk/_enums.vp — cfltk 枚举常量(t.CInt)", + "# fltk/_enums.vp — cfltk enum constants (t.CInt)", "# ============================================================", "", ] @@ -425,7 +425,7 @@ def main() -> None: with open(os.path.join(HERE, "_enums.vp"), "w", encoding="utf-8") as f: f.write("\n".join(enum_lines) + "\n") - # 4. 各头文件 + # 4. Per header file for h in headers: if h in ("cfl_macros.h", "cfl_enums.h"): continue diff --git a/gzip.vp b/gzip.vp index d6b71fc..36986f1 100644 --- a/gzip.vp +++ b/gzip.vp @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- # ============================================================ -# gzip - 单文件 gzip 解码器(RFC 1952 + RFC 1951 deflate) +# gzip - single-file gzip decoder (RFC 1952 + RFC 1951 deflate) # -# OOP 范式对齐 zipfile: 数据由 GzipFile 对象持有并负责释放; -# 内部位流/Huffman 助手为模块级私有函数。 +# OOP paradigm aligned with zipfile: data is held and freed by the GzipFile object; +# internal bit-stream/Huffman helpers are module-level private functions. # -# 用法: +# Usage: # gz: rawptr[gzip.GzipFile] = gzip.GzipFile("train-images-idx3-ubyte.gz") # if gz is None or gz.ok == 0: ... -# buf: bytes = gz.read_all() # 内部 malloc, read_all 返回即解压完成 -# n: sizet = gz.dst_len # 原始长度(由对象持有, close 时释放) +# buf: bytes = gz.read_all() # internally malloc'd, decompression is complete when read_all returns +# n: sizet = gz.dst_len # original length (held by the object, freed on close) # gz.close() # -# 覆盖 stored/fixed/dynamic 三种 deflate 块; 不解 CRC。 +# Covers the three deflate block types (stored/fixed/dynamic); does not decode CRC. # ============================================================ import t, c import stdlib diff --git a/json/__init__.vp b/json/__init__.vp index b4db1ca..5134dcc 100644 --- a/json/__init__.vp +++ b/json/__init__.vp @@ -5,7 +5,7 @@ import string import viperio # ============================================================ -# JSON 值类型枚举 +# JSON value type enum # ============================================================ define JSON_NULL = 0 define JSON_BOOL = 1 diff --git a/json/__writer.vp b/json/__writer.vp index efa6fbb..7778070 100644 --- a/json/__writer.vp +++ b/json/__writer.vp @@ -10,16 +10,16 @@ from . import ( # ============================================================ -# 容量约定(cap 只由调用方显式传入,签名里没有默认值) -# write —— 三参入口,固定用 WRITE_CAP_DEFAULT(4096)作容量。 -# write_cap —— 四参入口,容量由调用方显式传入。 -# cap = 输出缓冲总字节数(含结尾 NUL)。所有写入先判容量: -# 写 n 字节要求 pos + n <= cap - 1(末位留给 write 收尾的 '\0')。 -# 放不下时不写,并把 pos 置为"溢出标志" cap——此后任何写入都以 -# `pos >= cap` 短路返回,最终 write 判 `end_pos >= cap` 返回 None。 -# 即:内容超出容量时既不写穿缓冲,也不再继续写(绝不越界)。 -# cap 的合法区间是 [WRITE_CAP_MIN, WRITE_CAP_MAX]:越界一律返回 None, -# 非法容量不进入 pool.alloc(不会按可疑值去申请内存)。 +# Capacity contract (cap is passed explicitly only by the caller; there is no default in the signature) +# write -- three-arg entry point; always uses WRITE_CAP_DEFAULT (4096) as the capacity. +# write_cap -- four-arg entry point; the capacity is passed explicitly by the caller. +# cap = total bytes of the output buffer (including the trailing NUL). Every write first checks capacity: +# writing n bytes requires pos + n <= cap - 1 (the last byte is reserved for the '\0' that write appends). +# if it does not fit, nothing is written and pos is set to the "overflow marker" cap -- from then on any write returns early via +# the `pos >= cap` short-circuit, and the final write returns None when `end_pos >= cap`. +# That is: when the content exceeds the capacity it neither writes through the buffer nor keeps writing (never out of bounds). +# The legal range of cap is [WRITE_CAP_MIN, WRITE_CAP_MAX]: anything out of range always returns None, +# and an illegal capacity does not enter pool.alloc (no memory is requested using a suspicious value). # ============================================================ define WRITE_CAP_MIN = 64# lower bound: shortest JSON doc (e.g. {"id":""}) incl NUL fits in 64 bytes define WRITE_CAP_MAX = 67108864# upper bound: 64 MiB, not less than the largest output buffer a caller may request diff --git a/memhub.vp b/memhub.vp index 82d28af..d646486 100644 --- a/memhub.vp +++ b/memhub.vp @@ -2017,19 +2017,19 @@ class MemBuddy(MemManager): fb, self.free_count()) def dump_alive_orders(self): - """OOM 归属画像:按 order 统计「记录在案且仍存活」的块,并给出整池账目。 + """OOM attribution profile: per order, counts blocks that are "recorded and still alive" and gives whole-pool accounting. - 数据全部来自既有状态,无需 --detection: - - `chk_stack[0, chk_depth)` 是 alloc 时压栈的块地址; - - 块头为 `(order << 1) | 1` 表示存活,偶数表示已被单独 free(跳过,与 - reset_to / check_alive_blocks 同判据)。 - 块大小 = MEMBUDDY_MIN_BLOCK << order,故这份分布能直接区分 - “大量小块” 与 “少量大块”,是定位池被谁吃满的关键画像。 + All data comes from existing state; --detection is not required: + - `chk_stack[0, chk_depth)` holds the block addresses pushed during alloc; + - a block header of `(order << 1) | 1` means alive, an even value means it was separately freed (skipped, using the + same criteria as reset_to / check_alive_blocks). + Block size = MEMBUDDY_MIN_BLOCK << order, so this distribution directly distinguishes + "many small blocks" from "a few large blocks", and is the key profile for locating who filled the pool. - 整池账目(关键判据):池内每一字节必然属于「存活块」或「空闲块」二者之一, - 故 `usable_size - Σ存活 - Σ空闲链表` 若显著不为 0,说明有块既不在空闲链表、 - 也没被任何记录项持有 —— 即空闲链表被写穿后**链被静默截断**(`_fl_pop` 允许 - next==0 与池内任意对齐地址,截断不会报错),这些块从此再也分配不到:池漏满。 + Whole-pool accounting (key criterion): every byte in the pool necessarily belongs to either a "live block" or a "free block", + so if `usable_size - sum(live) - sum(free lists)` is significantly non-zero, some blocks are neither in the free lists + nor held by any record entry -- i.e. after the free lists are written through, **the chain is silently truncated** (`_fl_pop` allows + next==0 and any in-pool aligned address, so truncation raises no error) and those blocks can never be allocated again: the pool leaks full. """ if self.chk_stack is None or self.usable is None: return diff --git a/mlirlite/__builder.vp b/mlirlite/__builder.vp index fa05dad..df1ddf8 100644 --- a/mlirlite/__builder.vp +++ b/mlirlite/__builder.vp @@ -11,12 +11,12 @@ from .__values import Value, new_value, SSAValue from .__function import Function, BasicBlock, block_append_text, block_set_terminated, block_get_terminated, function_move_block_to_end, _ll_name_needs_quote, block_insert_text_before_terminator # ============================================================ -# IRBuilder: 游标式 IR 指令发射器 +# IRBuilder: cursor-style IR instruction emitter # -# 跟踪当前 BasicBlock,将指令文本追加到块的指令链表。 -# 维护递增的 SSA 名计数器(%0, %1, %2, ...)。 +# Tracks the current BasicBlock and appends instruction text to the block's instruction list. +# Maintains an incrementing SSA name counter (%0, %1, %2, ...). # -# 用法: +# Usage: # builder = new_builder(pool, func) # builder.PositionAtEnd(entry_block) # a = builder.build_alloca(i32_ty) @@ -25,24 +25,24 @@ from .__function import Function, BasicBlock, block_append_text, block_set_termi # r = builder.build_load(i32_ty, a) # builder.build_ret(r) # -# 注意: 加 @t.NoVTable 以与硬编码 BUILDER_SIZE=40 保持一致 -# (方法以实例方法实现,无虚表;文件末尾的同名顶层函数为一行委托)。 +# Note: add @t.NoVTable to stay consistent with the hardcoded BUILDER_SIZE=40 +# (methods are implemented as instance methods, with no vtable; the same-named top-level function at the end of the file is a one-line delegate). # ============================================================ -# 指令文本缓冲尺寸 +# Instruction text buffer sizing # -# MLIR 形态的类型文本会**内联具名结构体的完整字段体** -# (如 `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, …)>`),单个类型可达数百字节, -# 远长于 LLVM 形态的 `%"sha1.Cls"` 引用(十几字节)。故指令行缓冲必须按 -# "一行最多内嵌几个类型文本"给足:_LINE_CAP 覆盖 5 个类型文本 + 固定部分。 -# 渲染侧 TypePrint/MlirTypeText 已加溢出护栏:一旦截断即响亮报错,不再静默 -# 产出畸形 MLIR(历史故障:alloca 行类型被 128 字节缓冲截断 → mlir-opt 报 -# "unbalanced '(' character in pretty dialect name")。 +# An MLIR-form type text **inlines the full field body of a named struct** +# (e.g. `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, ...)>`), so a single type can reach hundreds of bytes, +# far longer than the LLVM-form `%"sha1.Cls"` reference (a dozen-odd bytes). Therefore the instruction line buffer must be sized by +# "how many type texts can be embedded at most in one line": _LINE_CAP covers 5 type texts + the fixed part. +# The rendering side (TypePrint/MlirTypeText) already has an overflow guard: any truncation errors loudly instead of silently +# producing malformed MLIR (historical failure: the alloca line type was truncated by a 128-byte buffer -> mlir-opt reported +# "unbalanced '(' character in pretty dialect name"). # ============================================================ -# 单个类型文本的容量(builders 内嵌类型经 TypePrint 渲染为 MLIR 形态)。 -# 内联具名结构体在使用处展开**完整字段体**,类型文本可达数百字节(TransPyV -# 自身类名/嵌套更深)⇒ 512 偏小会触发 TypePrint 的"类型文本超出输出缓冲被 -# 截断"响亮报错。提到 2048;注意 arg_cap 由本常量推导 -# ((arg_count+1)*(_TY_TEXT_CAP+32)),过高会让多参调用单次分配逼近模块池单块上限。 +# Capacity of a single type text (types embedded by the builders are rendered to MLIR form by TypePrint). +# An inlined named struct expands its **full field body** at the use site, so a type text can reach hundreds of bytes (TransPyV +# class names / nesting are deeper) => 512 is too small and would trigger TypePrint's "type text exceeds the output buffer and is +# truncated" loud error. Raised to 2048; note that arg_cap is derived from this constant +# ((arg_count+1)*(_TY_TEXT_CAP+32)), so a too-high value makes a single multi-arg call allocation approach the module pool's per-block limit. _TY_TEXT_CAP: sizet = 2048 _LINE_CAP: sizet = 2560 diff --git a/mlirlite/__init__.vp b/mlirlite/__init__.vp index 8ca8ba7..857bc08 100644 --- a/mlirlite/__init__.vp +++ b/mlirlite/__init__.vp @@ -5,15 +5,15 @@ import string import viperlib # ============================================================ -# mlirlite —— 与 llvmlite 逐名同形的 MLIR(llvm 方言) 文本发射库 +# mlirlite -- an MLIR (llvm dialect) text emission library, isomorphic to llvmlite name for name # -# 设计原则: -# * 内部模型(LLVMType/Value/Function/BasicBlock/IRBuilder/Module…)与 -# includes/llvmlite **逐名逐签名同形**,因此 TPV 侧只需把 -# `llvmlite.` 重命名为 `mlirlite.` 即可编译; -# * 差异只在"打印"阶段:TypePrint / ValuePrint / FunctionPrint / -# LLVMModulePrint 输出 MLIR 文本,而 builders 仍按原样生成中间指令文本; -# * 名字保留 LLVMxxx(同形优先)。语义差异见 README.md。 +# Design principles: +# * The internal model (LLVMType/Value/Function/BasicBlock/IRBuilder/Module...) is +# **isomorphic to includes/llvmlite name for name and signature for signature**, so on the TPV side it suffices to rename +# `llvmlite.` to `mlirlite.` in order to compile; +# * The difference lies only in the "printing" stage: TypePrint / ValuePrint / FunctionPrint / +# LLVMModulePrint emit MLIR text, while the builders still generate the intermediate instruction text as before; +# * Names keep the LLVMxxx form (isomorphism first). See README.md for semantic differences. # ============================================================ # Import submodules diff --git a/mlirlite/__values.vp b/mlirlite/__values.vp index ed040b7..97f397b 100644 --- a/mlirlite/__values.vp +++ b/mlirlite/__values.vp @@ -9,13 +9,13 @@ from .__types import LLVMType, TypePrint # ============================================================ -# Value: SSA 值表示 +# Value: SSA value representation # -# 表示一个 LLVM IR 值:SSA 临时值(%0/%result)或常量字面量(42/0)。 -# 每个值有类型指针、名字、是否常量标志。 -# 继承 GSListNode[Value] 获取强类型 Next: Value|CPtr(值池链表) +# Represents an LLVM IR value: an SSA temporary (%0/%result) or a constant literal (42/0). +# Each value has a type pointer, a name, and an is-constant flag. +# Inherits GSListNode[Value] to get a strongly-typed Next: Value|CPtr (the value pool list) # -# 注意: 必须加 @t.NoVTable(理由见 __function.py 的 Line 注释)。 +# Note: @t.NoVTable must be added (see the Line comment in __function.py for the reason). # ============================================================ @t.NoVTable class Value(GSListNode[Value]): diff --git a/mlirlite/__verify.vp b/mlirlite/__verify.vp index dd349b7..cc3e0a8 100644 --- a/mlirlite/__verify.vp +++ b/mlirlite/__verify.vp @@ -9,17 +9,17 @@ from .__module import LLVMModule # ============================================================ -# SSA 验证器 +# SSA verifier # -# 与 llvmlite **逐名同形、语义相同**:验证的是**结构化内部模型 + 中间指令 -# 文本**(builders 生成的 %N 定义/使用、块终止、phi 位置), -# 与 MLIR 渲染文本无关 —— MLIR 的合法性由 mlir-opt/mlir-translate 负责。 +# Isomorphic to llvmlite **name for name and semantically identical**: it verifies the **structured internal model + intermediate instruction +# text** (the %N definitions/uses produced by the builders, block termination, phi placement), +# independent of the rendered MLIR text -- MLIR validity is the responsibility of mlir-opt/mlir-translate. # -# 检查项: -# 1. 块终止: 每个基本块必须以终止指令结尾 (ret/br/switch/unreachable) -# 2. SSA 唯一性: 每个 %name 只能被定义一次 -# 3. 未定义引用: 所有 %name 使用必须有对应定义 -# 4. Phi 位置: phi 指令只能出现在块首(非 phi 指令之前) +# Checks: +# 1. Block termination: every basic block must end with a terminator (ret/br/switch/unreachable) +# 2. SSA uniqueness: every %name may be defined only once +# 3. Undefined references: every %name use must have a matching definition +# 4. Phi placement: a phi instruction may appear only at the head of a block (before any non-phi instruction) # ============================================================ # Error codes diff --git a/openssl/__init__.vp b/openssl/__init__.vp index b5e5a70..3e5a461 100644 --- a/openssl/__init__.vp +++ b/openssl/__init__.vp @@ -1,19 +1,19 @@ # ============================================================ -# openssl — Viper 的 OpenSSL 3.x FFI 绑定包 +# openssl — Viper FFI binding package for OpenSSL 3.x # -# 用户用法: +# Usage: # import openssl -# v = openssl.OpenSSL_version(openssl.OpenSslVersion) # 版本文本 +# v = openssl.OpenSSL_version(openssl.OpenSslVersion) # version text # openssl.OPENSSL_init_ssl(0, None) # -# 说明: -# * 全部为 extern FFI(extern def,直连 libcrypto/libssl 导出符号), -# 无 C++ 桥接层;对象均为不透明指针(openssl.types 里 *Ptr typedef)。 -# * 头文件按 openssl 官方头分组:crypto/err/rand/sha/md5/hmac/objects/ -# evp/bio/pem/x509/ssl。 -# * 返回指针的函数返回 *Ptr,拿到对象后按需调用对应 *_free/SSL_free 等释放。 -# * 密码回调类参数一律 bytes 占位,不使用时传 t.CPtr(0)/None。 -# * 链接库:libcrypto + libssl(Windows 上 mingw 对应 -lcrypto -lssl)。 +# Notes: +# * Everything is extern FFI (extern def, directly bound to libcrypto/libssl +# exported symbols), with no C++ bridge layer; objects are all opaque pointers (the *Ptr typedefs in openssl.types). +# * Headers are grouped by the official openssl headers: crypto/err/rand/sha/md5/hmac/objects/ +# evp/bio/pem/x509/ssl. +# * Functions that return pointers return *Ptr; after acquiring an object, call the corresponding *_free/SSL_free etc. to release it as needed. +# * Password-callback parameters are always placeholder bytes; pass t.CPtr(0)/None when unused. +# * Linked libraries: libcrypto + libssl (on Windows with mingw, this means -lcrypto -lssl). # ============================================================ import t diff --git a/openssl/bio.vp b/openssl/bio.vp index 5fff092..bc987e2 100644 --- a/openssl/bio.vp +++ b/openssl/bio.vp @@ -2,10 +2,10 @@ import t, c from openssl.types import * # ============================================================ -# openssl/bio.vp — BIO(内存 / 文件 / Socket / Connect 抽象 IO) -# 对应头文件: bio.h +# openssl/bio.vp — BIO (memory / file / Socket / Connect abstract IO) +# Corresponding header: bio.h # -# 含 BIO_ADDR/BIO_ADDRINFO:主机名解析(getaddrinfo 封装)+ TCP 连接/读写。 +# Includes BIO_ADDR/BIO_ADDRINFO: hostname resolution (getaddrinfo wrapper) + TCP connect/read/write. # ============================================================ # ---- BIO_METHOD accessors ---- diff --git a/openssl/crypto.vp b/openssl/crypto.vp index 6ed4bc5..632003b 100644 --- a/openssl/crypto.vp +++ b/openssl/crypto.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/crypto.vp — OpenSSL 全局初始化 / 版本 / 内存工具 -# 对应头文件: crypto.h / opensslv.h +# openssl/crypto.vp — OpenSSL global initialization / version / memory utilities +# Corresponding headers: crypto.h / opensslv.h # ============================================================ # ---- Version query types (type arg of OpenSSL_version(type)) ---- diff --git a/openssl/err.vp b/openssl/err.vp index 66fa0de..d225a3b 100644 --- a/openssl/err.vp +++ b/openssl/err.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/err.vp — OpenSSL 错误队列 -# 对应头文件: err.h +# openssl/err.vp — OpenSSL error queue +# Corresponding header: err.h # ============================================================ # unsigned long ERR_get_error(void) -- pop queue head and return error code diff --git a/openssl/evp.vp b/openssl/evp.vp index 6133714..b1dd1c9 100644 --- a/openssl/evp.vp +++ b/openssl/evp.vp @@ -2,11 +2,11 @@ import t, c from openssl.types import * # ============================================================ -# openssl/evp.vp — EVP 高层密码 API -# 对应头文件: evp.h +# openssl/evp.vp — EVP high-level crypto API +# Corresponding header: evp.h # -# 包含:消息摘要(EVP_Digest*)、对称加解密(EVP_Cipher*/Encrypt*/Decrypt*)、 -# Base64 编解码(EVP_EncodeBlock/DecodeBlock)、非对称(EVP_PKEY* 签名/验签/加解密) +# Includes: message digests (EVP_Digest*), symmetric encryption/decryption (EVP_Cipher*/Encrypt*/Decrypt*), +# Base64 encoding/decoding (EVP_EncodeBlock/DecodeBlock), asymmetric (EVP_PKEY* sign/verify/encrypt/decrypt) # ============================================================ define EvpMaxMdSize = 64 diff --git a/openssl/hmac.vp b/openssl/hmac.vp index be9b729..3162465 100644 --- a/openssl/hmac.vp +++ b/openssl/hmac.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/hmac.vp — HMAC(一次性 + 上下文) -# 对应头文件: hmac.h +# openssl/hmac.vp — HMAC (one-shot + context) +# Corresponding header: hmac.h # ============================================================ # ---- One-shot HMAC ---- diff --git a/openssl/md5.vp b/openssl/md5.vp index 0881825..1ae6357 100644 --- a/openssl/md5.vp +++ b/openssl/md5.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/md5.vp — MD5 一次性摘要(兼容用,非密码学安全) -# 对应头文件: md5.h +# openssl/md5.vp — MD5 one-shot digest (for compatibility, not cryptographically secure) +# Corresponding header: md5.h # ============================================================ define Md5DigestLength = 16 diff --git a/openssl/objects.vp b/openssl/objects.vp index fe46146..656e21e 100644 --- a/openssl/objects.vp +++ b/openssl/objects.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/objects.vp — ASN.1 对象(OID)与 NID -# 对应头文件: objects.h / obj_mac.h +# openssl/objects.vp — ASN.1 objects (OID) and NID +# Corresponding headers: objects.h / obj_mac.h # ============================================================ # ---- Common NIDs (obj_mac.h constants) ---- diff --git a/openssl/pem.vp b/openssl/pem.vp index f65c948..609fdc9 100644 --- a/openssl/pem.vp +++ b/openssl/pem.vp @@ -2,11 +2,11 @@ import t, c from openssl.types import * # ============================================================ -# openssl/pem.vp — PEM 编解码 -# 对应头文件: pem.h +# openssl/pem.vp — PEM encoding/decoding +# Corresponding header: pem.h # -# 全部走 BIO 输入(PEM_read_bio_*);证书/密钥的 x 出参可为 NULL -# (函数返回新对象)。回调 cb 用 bytes(Viper 传 t.CPtr(0))。 +# Everything takes BIO input (PEM_read_bio_*); the x out-parameter for certificates/keys may be NULL +# (the function returns a new object). Callback cb uses bytes (Viper passes t.CPtr(0)). # ============================================================ # ---- X509 certificate ---- diff --git a/openssl/rand.vp b/openssl/rand.vp index 74049a3..081f7fe 100644 --- a/openssl/rand.vp +++ b/openssl/rand.vp @@ -2,8 +2,8 @@ import t, c from openssl.types import * # ============================================================ -# openssl/rand.vp — 安全随机数 -# 对应头文件: rand.h +# openssl/rand.vp — secure random numbers +# Corresponding header: rand.h # ============================================================ # int RAND_bytes(unsigned char *buf, int num) -- cryptographically secure random diff --git a/openssl/sha.vp b/openssl/sha.vp index 148f4c9..8ffe76c 100644 --- a/openssl/sha.vp +++ b/openssl/sha.vp @@ -2,11 +2,11 @@ import t, c from openssl.types import * # ============================================================ -# openssl/sha.vp — SHA-1 / SHA-2 一次性摘要 -# 对应头文件: sha.h +# openssl/sha.vp — SHA-1 / SHA-2 one-shot digests +# Corresponding header: sha.h # -# OpenSSL 3.x 推荐走 EVP(见 evp.vp: EVP_DigestInit_ex/Update/Final_ex), -# 这里保留传统一次性接口,方便快速算摘要。 +# OpenSSL 3.x recommends using EVP (see evp.vp: EVP_DigestInit_ex/Update/Final_ex); +# the legacy one-shot interfaces are kept here for quick digest computation. # ============================================================ define ShaDigestLength = 20 diff --git a/openssl/ssl.vp b/openssl/ssl.vp index 0c43419..c4f3d44 100644 --- a/openssl/ssl.vp +++ b/openssl/ssl.vp @@ -2,17 +2,17 @@ import t, c from openssl.types import * # ============================================================ -# openssl/ssl.vp — SSL/TLS 连接层(握手 + SocketIO + 校验证书) -# 对应头文件: ssl.h +# openssl/ssl.vp — SSL/TLS connection layer (handshake + SocketIO + certificate verification) +# Corresponding header: ssl.h # -# 典型客户端流程: +# Typical client flow: # OPENSSL_init_ssl(...) # ctx = SSL_CTX_new(TLS_client_method()) -# SSL_CTX_set_verify(ctx, SslVerifyPeer, 0) # 校验对端 +# SSL_CTX_set_verify(ctx, SslVerifyPeer, 0) # verify peer # SSL_CTX_load_verify_locations(ctx, cafile, None) # ssl = SSL_new(ctx) -# SSL_set_fd(ssl, fd) # 底层已连好的 TCP socket -# SSL_connect(ssl) # 握手 +# SSL_set_fd(ssl, fd) # underlying TCP socket already connected +# SSL_connect(ssl) # handshake # SSL_write/SSL_read # ============================================================ diff --git a/openssl/types.vp b/openssl/types.vp index 8139c95..70e69b5 100644 --- a/openssl/types.vp +++ b/openssl/types.vp @@ -1,17 +1,17 @@ import t, c # ============================================================ -# openssl/types.vp — OpenSSL 3.x 不透明对象句柄 typedef +# openssl/types.vp — opaque object handle typedefs for OpenSSL 3.x # -# 对接 libcrypto / libssl(OpenSSL 3.x 导出符号)。 -# 对象一律按不透明指针 (void*) 处理,生命周期由 OpenSSL 库管理 -# (X509_free/SSL_free/BIO_free/CRYPTO_free 等对应释放函数)。 -# 链接目标:ViperOpenSSLProject 的 project.vpj 链接 openssl_358_C -# 自编译静态库(libcrypto.a / libssl.a)。 +# Interfaces with libcrypto / libssl (OpenSSL 3.x exported symbols). +# Objects are always treated as opaque pointers (void*); their lifetime is +# managed by the OpenSSL library (corresponding X509_free/SSL_free/BIO_free/CRYPTO_free etc. release functions). +# Link target: project.vpj of ViperOpenSSLProject links openssl_358_C +# self-compiled static libraries (libcrypto.a / libssl.a). # -# 注意:typedef 用 `t.CPtr` 表达(与 vqt6 一致),生成 i8* -# 指针参数,可被 ASan 插桩;纯 `t.CPtr` 会生成 opaque struct 参数, -# opt -passes=asan 会报 invalid type for function argument。 +# Note: typedefs are expressed with `t.CPtr` (consistent with vqt6), which +# generates i8* pointer parameters that can be instrumented by ASan; a plain `t.CPtr` generates opaque struct parameters, +# and opt -passes=asan reports invalid type for function argument. # ============================================================ # ---- Base contexts ---- diff --git a/openssl/x509.vp b/openssl/x509.vp index a58e868..fc0b4f2 100644 --- a/openssl/x509.vp +++ b/openssl/x509.vp @@ -2,10 +2,10 @@ import t, c from openssl.types import * # ============================================================ -# openssl/x509.vp — X509 证书解析 / 校验 + 必要 ASN.1 助手 -# 对应头文件: x509.h / x509_vfy.h / asn1.h +# openssl/x509.vp — X509 certificate parsing / verification + required ASN.1 helpers +# Corresponding headers: x509.h / x509_vfy.h / asn1.h # -# 证书网络解析场景:读取 subject/issuer/SAN/有效期/公钥、链式校验。 +# Certificate network-parsing scenario: read subject/issuer/SAN/validity/public key, chain verification. # ============================================================ define X509VOk = 0 diff --git a/requests/__init__.vp b/requests/__init__.vp index 7d605c8..b02c634 100644 --- a/requests/__init__.vp +++ b/requests/__init__.vp @@ -9,11 +9,11 @@ import zlib.pyzlib as pyzlib # ============================================================ -# requests/__init__.vp — HTTP(S) 客户端库包入口(Python requests 风格) +# requests/__init__.vp - HTTP(S) client library package entry point (Python requests style) # -# 包结构: -# requests HTTP 客户端(纯 socket 实现,本文件) -# requests.https HTTPS 客户端(openssl SSL/TLS + 证书核验,见 https.vp) +# Package structure: +# requests HTTP client (pure socket implementation, this file) +# requests.https HTTPS client (openssl SSL/TLS + certificate verification, see https.vp) # # Usage: # requests._mbuddy = my_mbuddy diff --git a/requests/download.vp b/requests/download.vp index 917f4d8..30f3f4d 100644 --- a/requests/download.vp +++ b/requests/download.vp @@ -12,24 +12,24 @@ import w32.win32sync as wsync # ============================================================ -# requests/download.vp — 大文件切片下载(Range + 串行/多线程) +# requests/download.vp - large-file ranged download (Range + serial/multi-threaded) # -# 用法: +# Usage: # import requests.download as rd # rc: INT = rd.download("https://host/big.bin", "big.bin", threads=4, # chunk_size=262144, verify=True, ca_file="ca.crt") # if rc != 0: # print(rd.last_error()) # -# 策略: -# threads == 1 串行 Range 切片(顺序拉取并写入文件) -# threads > 1 多线程:文件按线程数均分为若干带(band),每线程一个 -# 连接顺序下载自己带的切片,写入同一文件的对应偏移。 +# Strategy: +# threads == 1 serial Range chunks (fetched and written to the file in order) +# threads > 1 multi-threaded: the file is split into bands by thread count, each thread uses one +# connection to download its band's chunks in order, writing at the corresponding offsets of the same file. # -# 说明: -# - 通过 Content-Range 探测总大小与服务器 Range 支持; -# - http:// 走 requests(纯 socket),https:// 走 requests.https(openssl); -# - 每片请求不带 Accept-Encoding,响应为原始字节,直接落盘。 +# Notes: +# - probes total size and server Range support via Content-Range; +# - http:// goes through requests (pure socket), https:// goes through requests.https (openssl); +# - each chunk request omits Accept-Encoding, the response is raw bytes and is written straight to disk. # ============================================================ define DL_MAX_THREADS = 8 diff --git a/requests/https.vp b/requests/https.vp index fba95f7..7596713 100644 --- a/requests/https.vp +++ b/requests/https.vp @@ -11,9 +11,9 @@ import memhub # ============================================================ -# requests/https.vp — HTTPS 客户端(openssl SSL/TLS + 证书核验) +# requests/https.vp - HTTPS client (openssl SSL/TLS + certificate verification) # -# 与 requests(HTTP)同名 API,但走 TLS 并默认校验对端证书: +# Same API names as requests (HTTP), but over TLS and by default it verifies the peer certificate: # import requests.https as rhttps # resp = rhttps.get("https://example.com/", verify=True) # if resp is not None: @@ -21,12 +21,12 @@ import memhub # print(resp.text) # requests.release(resp) # -# 说明: -# - verify=True 时校验证书链 + 主机名(hostname 校验 SSL_set1_host), -# 提供 ca_file 指向 PEM CA bundle;不提供则走系统默认路径 -# (Windows 上通常为空,真实站点会因"无法验证证书链"失败返回 None)。 -# - 失败时返回 None,可用 last_error() 获取原因文本。 -# - 握手成功后可用 last_tls_info() 查看 TLS 版本 / 加密套件 / 对端证书主题。 +# Notes: +# - with verify=True it verifies the certificate chain + hostname (hostname check via SSL_set1_host), +# provide ca_file pointing to a PEM CA bundle; if not provided it uses the system default path +# (on Windows this is usually empty, so real sites fail with "unable to verify certificate chain" and return None). +# - on failure returns None; use last_error() to get the reason text. +# - after a successful handshake, use last_tls_info() to view the TLS version / cipher suite / peer certificate subject. # ============================================================ define HTTPS_PORT = 443 diff --git a/torch/__init__.vp b/torch/__init__.vp index 8108352..e88b0cb 100644 --- a/torch/__init__.vp +++ b/torch/__init__.vp @@ -1,20 +1,20 @@ # -*- coding: utf-8 -*- # ============================================================ -# torch-lite: 仿 PyTorch 的极简张量/神经网络库(Viper) +# torch-lite: a minimal PyTorch-like tensor/neural-network library (Viper) # -# 设计原则: -# * "足够用就行": 只覆盖 MNIST 两层 MLP 训练/推理所需的原语。 -# * API 命名贴近 PyTorch: Tensor / linear_fwd(bias 融合) / relu / -# relu_backward / softmax_crossentropy(前向+梯度一体) / sgd_step。 -# * 张量为 2D 稠密 double (rows x cols, 行主序), 权重形状同 PyTorch: -# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b。 -# * 计算热点结合 vipersimd: -# - linear_fwd 内积按 k%4 用 simd_dot4d 累加(纯 AVX2 点积); -# - relu_fwd 用 simd_max4d(x, 0) 四路取大; -# - 向量乘标量用 simd_mul_scalar4d(归一化 /255 等)。 -# backward / softmax / sgd 等按标量写(数据量小, 可读性优先)。 -# * 内存: 所有 Tensor 及 data/grad 都从调用方传入的 memhub.MemBuddy -# 分配(与 includes 既有约定一致, pool.alloc 免手动 free)。 +# Design principles: +# * "good enough": covers only the primitives needed to train/infer a two-layer MNIST MLP. +# * API names follow PyTorch closely: Tensor / linear_fwd(fused bias) / relu / +# relu_backward / softmax_crossentropy(forward+gradient combined) / sgd_step. +# * Tensors are 2D dense double (rows x cols, row-major), weight shapes match PyTorch: +# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b. +# * Compute hotspots use vipersimd: +# - linear_fwd dot product accumulates with simd_dot4d over k%4 (pure AVX2 dot product); +# - relu_fwd uses simd_max4d(x, 0) for 4-wide maximum; +# - vector-times-scalar uses simd_mul_scalar4d (normalization /255, etc.). +# backward / softmax / sgd, etc. are written scalar-wise (small data volume, readability first). +# * Memory: all Tensors and their data/grad are allocated from a caller-provided memhub.MemBuddy +# (consistent with the existing includes convention, pool.alloc avoids manual free). # ============================================================ from stdint import * import t, c diff --git a/traceback.vp b/traceback.vp index 2ac5590..0bc34c8 100644 --- a/traceback.vp +++ b/traceback.vp @@ -10,420 +10,420 @@ import w32.win32process as wp import w32.win32memory as wm import w32.win32dbghelp as dbg -# 转储重入保护(见 DumpMainThread)**不使用 InterlockedCompareExchange**:该名字在 x64 上是 -# winnt.h 的编译器内部函数(映射到 lock cmpxchg 指令),**不是可链接符号**,直接引用会在链接期 -# 报 undefined symbol(实测:ld.lld: undefined symbol: InterlockedCompareExchange)。 -# 本模块改用普通 32 位标志做「检查并占用」:并发回调最多导致一次重复转储(诊断工具可接受), -# 且不引入任何链接期依赖。 +# Dump reentrancy protection (see DumpMainThread) does **NOT use InterlockedCompareExchange**: on x64 that +# name is a winnt.h compiler intrinsic (mapped to the lock cmpxchg instruction), **not a linkable symbol**, and +# referring to it directly fails at link time with an undefined symbol (observed: ld.lld: undefined symbol: InterlockedCompareExchange). +# This module instead uses a plain 32-bit flag for test-and-set: concurrent callbacks cause at most one duplicate +# dump (acceptable for a diagnostic facility) and introduce no link-time dependency. # ============================================================================= -# traceback — 运行时「按键取栈」模块(Ctrl+C / Ctrl+Break 时转储主线程调用栈) +# traceback -- runtime "press-key-to-capture-stack" module (dumps the main-thread call stack on Ctrl+C / Ctrl+Break) # -# 解决的问题:编译器(或任何长任务)卡死时,用户按一个键就能拿到「现在卡在哪一行」 -# 的完整调用栈——从 CRT 入口(mainCRTStartup / __tmainCRTStartup)到当前帧。 +# Problem solved: when the compiler (or any long task) hangs, one keypress gives the user "where it is stuck right now" +# as a complete call stack -- from the CRT entry (mainCRTStartup / __tmainCRTStartup) down to the current frame. # # ----------------------------------------------------------------------------- -# 一、触发语义(SetConsoleCtrlHandler 回调) +# 1. Trigger semantics (SetConsoleCtrlHandler callback) # ----------------------------------------------------------------------------- -# Ctrl+C (CTRL_C_EVENT) → 打印调用栈后**返回 FALSE**(交还系统默认处理 ⇒ 进程终止) -# Ctrl+Break (CTRL_BREAK_EVENT)→ 打印调用栈后**返回 TRUE**(已处理 ⇒ 进程继续运行) -# 其它控制事件(关窗/注销/关机,CTRL_CLOSE_EVENT 等)→ 一律返回 FALSE,不转储、不干预。 -# **崩溃**(未处理异常,经 SetUnhandledExceptionFilter 捕获)→ 在**出错线程**上打印调用栈后 -# 返回 EXCEPTION_CONTINUE_SEARCH(异常交还系统 ⇒ 退出码 / WER / ASan 报告行为原样保留), -# 见第十四节。 -# 回调由系统在**新建的线程**上执行,不是主线程——所以「当前执行到哪」必须靠 -# SuspendThread(主线程) + GetThreadContext 取得主线程寄存器,再回溯主线程的栈。 -# 同一时刻**只允许一次转储**:连续按键会由系统在多个新线程上并发回调,而它们共用 -# 同一个 LineBuf 与输出句柄 ⇒ 输出互相撕裂(实测出现过"降级:"行重复且交织)。 -# 故 DumpMainThread 入口用内联汇编 `lock cmpxchg`(AtomicTryEnterDump)对 _DumpLock -# 做原子"检查并占用":抢占失败的调用直接返回(不挂起主线程、不输出任何内容)。 -# 每次转储还会打印自增 Seq 与回调线程 id:若两条转储记录的 Seq 不同 ⇒ 顺序两次; -# 同一 Seq 的行却互相截断 ⇒ 说明仍存在共享缓冲的并发写(据此定位,不再靠猜)。 +# Ctrl+C (CTRL_C_EVENT) -> prints the call stack, then **returns FALSE** (back to default handling => process terminates) +# Ctrl+Break (CTRL_BREAK_EVENT) -> prints the call stack, then **returns TRUE** (handled => process keeps running) +# Other control events (close/logoff/shutdown, CTRL_CLOSE_EVENT, etc.) -> always return FALSE, no dump, no intervention. +# **Crash** (unhandled exception, caught via SetUnhandledExceptionFilter) -> prints the call stack on the **faulting +# thread**, then returns EXCEPTION_CONTINUE_SEARCH (exception handed back to the system => exit code / WER / ASan report +# behavior is preserved); see section 14. +# The callback runs on a **newly created thread**, not the main thread -- so "where execution currently is" must come +# from SuspendThread(main thread) + GetThreadContext to obtain the main-thread registers, then walking its stack. +# **Only one dump at a time** is allowed: repeated keypresses make the system invoke the callback concurrently on +# several new threads, and they share the same LineBuf and output handle => output is torn apart (observed: duplicated, +# interleaved "degrade:" lines). So DumpMainThread's entry uses one inline-asm `lock cmpxchg` (AtomicTryEnterDump) to +# test-and-set _DumpLock atomically: a call that loses the race returns immediately (does not suspend the main +# thread, outputs nothing). Each dump also prints an auto-incrementing Seq and the callback thread id: two dump records +# with different Seqs => two sequential dumps; lines with the same Seq that truncate each other => concurrent writes to the shared buffer still exist (locate by this, not by guessing). # # ----------------------------------------------------------------------------- -# 二、安装时机与总开关(本模块的开关落地点) +# 2. Install timing and master switch (where this module's switch lands) # ----------------------------------------------------------------------------- -# 约定:**旁文件存在** ⟺ 编译时用了 TPC 的 `--traceback`。 -# Install() 先求 <当前 exe 完整路径> + ".stackmap";该文件不存在(CreateFileA 报 -# ERROR_FILE_NOT_FOUND)则**立刻返回 0,零副作用**(不分配、不注册 handler); -# 存在才注册 SetConsoleCtrlHandler(并挂上未处理异常过滤器,见第十四节)。故 TPC 侧无需 -# 生成任何其它开关代码,只需在启用该选项时让产物链接 dbghelp(见第五节)并调用一次 -# Install()(见第六节)。 -# 文件存在但其头部/记录区非法时:打印可定位的警告并把映射标记为不可用——调用栈仍 -# 照常转储(只有地址),不会静默退出(诊断工具取不到信息时给降级信息,不掩盖错误)。 +# Convention: **the sidecar file exists** <=> the build used TPC's `--traceback`. +# Install() first forms + ".stackmap"; if that file is absent (CreateFileA reports +# ERROR_FILE_NOT_FOUND) it **returns 0 immediately with zero side effects** (no allocation, no handler registered); +# only when present does it register SetConsoleCtrlHandler (and attach the unhandled-exception filter, see +# section 14). So the TPC side needs no other switch code: it only has to make the artifact link dbghelp (see section 5) +# and call Install() once (see section 6) when the option is enabled. +# If the file exists but its header/record area is invalid: print a locatable warning and mark the mapping unusable -- the +# call stack is still dumped (addresses only); it never exits silently (a diagnostic gives degraded info instead of hiding errors). # # ----------------------------------------------------------------------------- -# 三、旁文件(.stackmap)格式 —— 小端 +# 3. Sidecar (.stackmap) format -- little-endian # ----------------------------------------------------------------------------- -# 头部 20 字节:magic="TPCS"(4B) + u32 version=1 + u32 entry_count + u32 str_count -# + u32 str_bytes -# 记录区 entry_count × 20B(**按 rva 升序**):u64 rva; u32 file_off; u32 line; u32 func_off -# 字符串池 str_bytes 字节:\0 结尾 UTF-8 串;file_off/func_off 是池内偏移 -# (func_off = 0 表示无函数名) -# 运行时:GetModuleHandleA(NULL) 取映像基址,帧地址 - 基址 = rva,再二分查找记录区。 -# 命中 → 帧行 " File \"<文件>\", line <行>, in <函数名>",其下再打一行 4 空格缩进的源码 -# (运行期重开该文件读第 <行> 行;读不到就整行不打,见第十三节); -# 未命中 → " File \"<0x<地址> +0x no source line mapping>\", line 0, in "; -# 地址落在主模块之外(更低,典型为 DLL/系统帧)→ " File \"<0x<地址> external>\", line 0, in "。 -# 三种形态都把地址/RVA 保留在同一行(见第十三节)。 -# 装载时做一次全表校验(rva 严格升序、字符串偏移落在池内),不合法即禁用映射—— -# 二分查找依赖升序,破损的表会给出**错行号**,比"只打地址"更坏。 -# 校验失败一律**逐项打印挂在哪一项**(含头部实读的 magic/version/count/str_bytes -# 与文件实际大小),失败原因不外泄为一句"旁文件非法"。 -# ⚠ 读字节必须逐个 & 0xFF:Viper 的 Buf[i] 是**有符号** char,>= 0x80 的字节若直接 -# 转 u32 会符号扩展(例:记录数 1000=0x3E8 的首字节 0xE8 → 0xFFFFFFE8),使头部的 -# entry_count/str_bytes 变成天文数字、`20+count*20+str_bytes > 文件大小` 必然成立 -# ⇒ 映射永远装载失败(这正是"源行映射=无"的真因,详见 ReadU32)。 +# Header 20 bytes: magic="TPCS"(4B) + u32 version=1 + u32 entry_count + u32 str_count +# + u32 str_bytes +# Record area entry_count x 20B (**sorted by ascending rva**): u64 rva; u32 file_off; u32 line; u32 func_off +# String pool str_bytes bytes: NUL-terminated UTF-8 strings; file_off/func_off are pool offsets +# (func_off = 0 means no function name) +# Runtime: GetModuleHandleA(NULL) gets the image base, frame address - base = rva, then binary-search the record area. +# Hit -> frame line " File \"\", line , in ", followed by one more line of 4-space-indented source +# (at runtime the file is reopened and line is read; if it cannot be read the whole line is omitted, see section 13); +# Miss -> " File \"<0x
+0x no source line mapping>\", line 0, in "; +# Address below the main module (typically a DLL/system frame) -> " File \"<0x
external>\", line 0, in ". +# All three forms keep the address/RVA on the same line (see section 13). +# Load time does one full-table validation (rva strictly ascending, string offsets inside the pool); if invalid the mapping is disabled -- +# the binary search relies on ascending order, and a corrupt table yields **wrong line numbers**, worse than "addresses only". +# A validation failure always **prints which item it failed on** (including the actually-read header magic/version/count/str_bytes +# and the real file size); the reason is never collapsed into a single "sidecar invalid". +# NOTE: each byte read must be masked with 0xFF: Viper's Buf[i] is a **signed** char, so a byte >= 0x80 cast +# to u32 sign-extends (e.g. record count 1000=0x3E8, first byte 0xE8 -> 0xFFFFFFE8), making the header's +# entry_count/str_bytes astronomical and `20+count*20+str_bytes > file size` always true +# => the mapping can never load (this is the real cause of "source line mapping = none"; see ReadU32). # # ----------------------------------------------------------------------------- -# 四、回溯两条路径与降级条件(**本轮已主副互换,见第十一节 B**) +# 4. The two walk paths and degrade conditions (**main/aux swapped this round, see section 11 B**) # ----------------------------------------------------------------------------- -# ⚠ 先读第十一节 B:本轮起**主路径 = RBP 链**(零 dbghelp 依赖),dbghelp 降为 -# "可选加强"。下面这段 dbghelp 说明仍有效,只是它描述的是**加强路径**的历史细节。 -# 加强路径(可选):dbghelp!StackWalk64(MachineType=IMAGE_FILE_MACHINE_AMD64)。 +# NOTE: read section 11 B first: from this round **the main path = the RBP chain** (zero dbghelp dependency) and dbghelp is demoted +# to "optional augmentation". The dbghelp notes below still hold; they describe the historical detail of the **augmentation path**. +# Augmentation path (optional): dbghelp!StackWalk64 (MachineType=IMAGE_FILE_MACHINE_AMD64). # SymSetOptions(SYMOPT_DEFERRED_LOADS|SYMOPT_UNDNAME|SYMOPT_NO_PROMPTS) + -# SymInitialize(GetCurrentProcess(), NULL, **TRUE**) —— TRUE 是硬要求:它让 dbghelp -# 枚举已加载模块填进自己的模块表;置 FALSE 时模块表为空,SymFunctionTableAccess64 -# 取不到 RUNTIME_FUNCTION、SymGetModuleBase64 返回 0,StackWalk64 **首次调用即失败** -# (表现为"符号回溯=可用但 0 帧")。配合 DEFERRED_LOADS 不会真的去加载 PDB。 -# 三个回调地址里 FunctionTableAccess/GetModuleBase 仍在 Install 时用 GetProcAddress -# 取(本仓既有的动态解析写法,先例 VET/App/lib/VGuard.vp:852-867):避免依赖 -# 「跨模块导入的函数名作为函数指针实参」这一未经本仓验证的用法,取不到就传 NULL。 -# ReadProcessMemory 回调改用**本模块内定义**的 ReadMemCb(同模块内"函数名作 -# 函数指针实参"已由 CtrlHandler 先例验证):直接传 kernel32!ReadProcessMemory -# 本体时拿不到失败原因,而回调返回 0 会让 StackWalk64 立即终止(恰好是"0 帧")。 -# 主路径(**RBP 链手工回溯**):无条件先走它。它拿不到更多帧(<=1)时**先走第二兜底 -# = 栈扫描(ScanStack,不依赖帧指针)**;再之后才轮到 dbghelp 加强(编译期开关为 1、 -# Install 时 SymInitialize/SymRefreshModuleList 成功、两个回调地址与帧缓冲齐备、且 -# SymGetModuleBase64(主模块) 探针通过;**ASan 运行时存在只表示 DbgHelp 状态共享,不再 -# 一票否决**);任一前提不满足都会打印明确原因(`[diag] symwalk=disabled reason=…`), -# **不静默 0 帧**。 -# 本轮新增:每步无条件打 `[diag] rbp:` 原始读数 + 判定、以及 `walk: fallback=stack_scan` -# 扫描推断帧(`~` 前缀)——详见第十二节。 -# 主路径实现:打印当前 RIP,然后沿主线程 RBP 链读 [rbp](调用者 rbp)与 [rbp+8] -# (返回地址)逐帧回溯;要求 rbp 8 字节对齐、严格向高地址增长(`[rbp] > rbp`)、 -# 单步跨度 <= TRACEBACK_RBP_MAX_STEP、返回地址落在主模块基址区间内,否则立即停止 -# (防野指针把回溯带飞);**读内存一律经 ReadProcessMemory,绝不直接解引用野 rbp**。 -# RBP 链只覆盖保持帧指针的函数;帧指针一旦未保留([rbp+8] 不是返回地址)链即在第 1 -# 步断——此时由 ScanStack 兜底,另叠一层 dbghelp 加强(可用时)。 -# --traceback 时 TPC 给 llc 加 `-frame-pointer=all`(Phase2Translator.py:2187-2193); -# 该 flag 是否真正生效见第十二节 C 的调查结论与修法落点。 -# 【取证:先取证,再决策】每次转储在 banner 之后打印一组前置诊断(**TRACEBACK_DIAG=1 时**)—— -# Seq / 回调线程 id / 主线程 id / Dumping 进入前值 / MapOk / SymReady / ModInited / -# SymGetModuleBase64(主模块) 的**实际返回值** / 主模块基址 / 帧缓冲与两个回调地址 -# 是否就位;进入 dbghelp 主路径后,**首帧**再打印 StackWalk64 的返回码、传入的 -# machine/RIP/RSP/RBP、返回的 AddrPC,以及 SymFunctionTableAccess64(proc, AddrPC) 的 -# 返回值。这些行**先于任何降级判断**产出,使"0 帧"的根因可直接判定(见第九节)。 +# SymInitialize(GetCurrentProcess(), NULL, **TRUE**) -- TRUE is a hard requirement: it makes dbghelp +# enumerate loaded modules into its own module table; with FALSE the table is empty, SymFunctionTableAccess64 +# cannot get a RUNTIME_FUNCTION, SymGetModuleBase64 returns 0, and StackWalk64 **fails on its very first call** +# (seen as "symbol walk = available but 0 frames"). Combined with DEFERRED_LOADS it does not actually load PDBs. +# Of the three callback addresses, FunctionTableAccess/GetModuleBase are still fetched in Install via GetProcAddress +# (this repo's existing dynamic-resolution style; precedent VET/App/lib/VGuard.vp:852-867): this avoids relying on +# "a cross-module imported function name as a function-pointer argument", unverified in this repo; pass NULL if unavailable. +# The ReadProcessMemory callback uses ReadMemCb **defined inside this module** (an in-module "function name as +# function-pointer argument" is already proven by the CtrlHandler precedent): passing kernel32!ReadProcessMemory +# itself gives no failure reason, and a 0 return makes StackWalk64 stop immediately (exactly the "0 frames"). +# Main path (**manual RBP-chain walk**): always tried first. When it yields no more frames (<=1) it **first runs the second +# fallback = stack scan (ScanStack, no frame-pointer dependency)**; only after that comes the dbghelp augmentation (compile-time switch = 1, +# SymInitialize/SymRefreshModuleList succeeded at Install, both callback addresses and the frame buffer are ready, and +# the SymGetModuleBase64(main module) probe passes; **the presence of an ASan runtime only indicates DbgHelp state sharing, no longer +# a veto**); any unsatisfied precondition prints an explicit reason (`[diag] symwalk=disabled reason=...`), +# **never a silent 0 frames**. +# New this round: every step unconditionally prints `[diag] rbp:` raw readings + verdict, and `walk: fallback=stack_scan` +# plus scanned inferred frames (`~` prefix) -- see section 12. +# Main-path implementation: print the current RIP, then walk the main-thread RBP chain reading [rbp] (caller rbp) and [rbp+8] +# (return address) frame by frame; requires rbp to be 8-byte aligned, strictly increasing towards higher addresses (`[rbp] > rbp`), +# a single-step span <= TRACEBACK_RBP_MAX_STEP, and the return address within the main-module base range, else it stops at once +# (to keep a wild pointer from flying the walk off track); **all memory reads go through ReadProcessMemory, never a direct dereference of a wild rbp**. +# The RBP chain only covers functions that keep a frame pointer; once the frame pointer is not preserved ([rbp+8] is not the return address) the chain +# breaks at step 1 -- then ScanStack is the fallback, with a further dbghelp augmentation layer on top (when available). +# With --traceback, TPC adds `-frame-pointer=all` to llc (Phase2Translator.py:2187-2193); +# whether that flag actually takes effect is covered by the investigation result and fix location in section 12 C. +# [Evidence first: gather evidence, then decide] After the banner each dump prints a set of preflight diagnostics (**only when TRACEBACK_DIAG=1**) -- +# Seq / callback thread id / main thread id / pre-entry Dumping value / MapOk / SymReady / ModInited / +# the **actual return value** of SymGetModuleBase64(main module) / main-module base / whether the frame buffer and the two callback addresses +# are in place; after entering the dbghelp main path the **first frame** additionally prints StackWalk64's return code, the +# machine/RIP/RSP/RBP passed in, the returned AddrPC, and the return value of SymFunctionTableAccess64(proc, AddrPC). +# These lines are produced **before any degrade decision**, so the root cause of "0 frames" can be judged directly (see section 9). # # ----------------------------------------------------------------------------- -# 五、链接期依赖(TPC 侧必须知道) +# 5. Link-time dependencies (the TPC side must know this) # ----------------------------------------------------------------------------- -# dbghelp(-ldbghelp)+ kernel32(既有)。SymInitialize / StackWalk64 / SymSetOptions / -# SymGetModuleBase64 / SymFunctionTableAccess64 是**静态**引用(后两个用于首帧取证诊断), -# 产物必须能解析 dbghelp 的导入库,否则链接失败。若不便新增 -ldbghelp, -# 把 TRACEBACK_USE_DBGHELP 置 0:该常量比较会在编译期把唯一含 dbghelp 调用的分支 -# 整体去掉(见 TRACEBACK_USE_DBGHELP 的注释),只保留 RBP 链回溯。 +# dbghelp (-ldbghelp) + kernel32 (existing). SymInitialize / StackWalk64 / SymSetOptions / +# SymGetModuleBase64 / SymFunctionTableAccess64 are **static** references (the latter two are used for first-frame evidence), +# so the artifact must resolve dbghelp's import library or the link fails. If adding -ldbghelp is inconvenient, +# set TRACEBACK_USE_DBGHELP to 0: that constant comparison folds away at compile time the only branch containing +# dbghelp calls (see the TRACEBACK_USE_DBGHELP comment), leaving only the RBP-chain walk. # # ----------------------------------------------------------------------------- -# 六、TPC 侧调用入口(符号与签名) +# 6. TPC-side call entry (symbol and signature) # ----------------------------------------------------------------------------- # import traceback -# traceback.Install() # 返回 int:1=handler 已注册;0=未安装(无旁文件/失败) -# # 注册两个:SetConsoleCtrlHandler(Ctrl+C/Break)+ -# # SetUnhandledExceptionFilter(崩溃),见第一节/第十四节 -# 调用时机:进程启动期一次即可。两种等价做法—— -# (a) 在入口 main 的第一条语句调用; -# (b) 注入一个 @llvm.global_ctors(.CRT$XCU 语义)构造器调用该符号(本仓先例: -# TransPyC/lib/core/LLVMCG/BaseGen.py:2061-2097 用 llvm.global_ctors 注册 -# Ctrl+C 处理器)。构造器在 CRT 初始化期执行,能覆盖「main 之前就卡住」的场景。 -# 本模块自身**没有**模块级构造器(状态对象由 Install 懒分配),故与 TPC 注入的 -# 构造器不存在初始化顺序依赖,重复调用是幂等的。 -# ⚠ 本模块把「主线程」定义为**调用 Install() 的那个线程**(Install 里 GetCurrentThreadId -# 记录其 id,回调时挂起/回溯的就是它)。故必须在真正的入口线程调用(main 或 CRT -# 构造器所在线程);若在启动期的工作线程里调用,转储的将是那个线程的栈。 +# traceback.Install() # returns int: 1 = handler registered; 0 = not installed (no sidecar/failure) +# # registers two: SetConsoleCtrlHandler (Ctrl+C/Break) + +# # SetUnhandledExceptionFilter (crash), see section 1 / section 14 +# Call timing: once during process startup. Two equivalent ways -- +# (a) call it as the first statement of entry main; +# (b) inject an @llvm.global_ctors (.CRT$XCU semantics) constructor that calls this symbol (repo precedent: +# TransPyC/lib/core/LLVMCG/BaseGen.py:2061-2097 registers a Ctrl+C handler via llvm.global_ctors); +# the constructor runs during CRT initialization, covering the "stuck before main" case. +# This module itself has **no** module-level constructor (the state object is lazily allocated by Install), so it has +# no initialization-order dependency on TPC's injected constructor, and repeated calls are idempotent. +# NOTE: this module defines the "main thread" as **the thread that called Install()** (Install records its id +# via GetCurrentThreadId; that is the thread suspended/walked on callback). So it must be called on the real entry +# thread (main, or the thread running a CRT constructor); if called on a startup worker thread, the dump is that thread's stack. # # ----------------------------------------------------------------------------- -# 七、为什么直接 WriteFile 而不是 printf +# 7. Why WriteFile directly instead of printf # ----------------------------------------------------------------------------- -# 转储发生在「主线程已被挂起」的时刻:若主线程恰好停在 CRT 的 stdout 锁内,回调线程 -# 再调用 printf 会**永久等待同一把锁**,把"卡死"变成"按了键彻底不动"。 -# 故输出走 kernel32!WriteFile(GetStdHandle(STD_ERROR_HANDLE))(无用户态 CRT 流锁), -# 格式化走 CRT snprintf(只写内存缓冲,不碰 FILE 锁)。stderr 的另一好处:stdout 常被 -# 管道占用(如被父工具读取),stderr 一般直达终端。句柄与全部缓冲都在 Install 时备好, -# 回调线程**零分配**(MemBuddy 非线程安全,回调里绝不触碰池)。 +# The dump happens at a moment when **the main thread is already suspended**: if the main thread happens to be stopped inside +# the CRT's stdout lock, the callback thread calling printf would **wait forever on the same lock**, turning "hang" into "key press and nothing moves". +# So output goes through kernel32!WriteFile(GetStdHandle(STD_ERROR_HANDLE)) (no user-mode CRT stream lock), +# and formatting goes through CRT snprintf (writes only to a memory buffer, never touching FILE locks). Another benefit of stderr: stdout is often +# taken by a pipe (e.g. read by a parent tool) while stderr usually goes straight to the terminal. Handles and all buffers are prepared at Install, +# and the callback thread does **zero allocation** (MemBuddy is not thread-safe; the callback never touches the pool). # # ----------------------------------------------------------------------------- -# 七之二、转储**同时落文件** + 控制台纯 ASCII(本轮的两次修正) +# 7b. Dump **also written to a file** + pure-ASCII console (two fixes this round) # ----------------------------------------------------------------------------- -# (a) 落文件(解决"没有 Break 键"与"控制台代码页吞字"):Install 成功时以 -# ` + ".stackdump.txt"` 打开一个**追加写**句柄(OPEN_ALWAYS + -# FILE_APPEND_DATA,不存在则创建),EmitLine 把**同一份内容**既写控制台又追加落盘。 -# 文件是 UTF-8 字节原样,不受控制台代码页影响;每次转储在段前追加一小段分隔头 -# (本地时间戳/pid/触发键/主线程 id/Seq,中文标签,仅文件)。文件写失败**不阻断** -# 控制台,且只提示一次(见 EmitToFile)。Install 成功时打印一行 -# `[traceback] stack dump also written to: <路径>` 便于确认去向。 -# (b) 控制台纯 ASCII:原先的运行时文本是中文(UTF-8 字节),而 cmd 的控制台输出代码页 -# 默认为 936(GBK)——WriteFile 到控制台会按 936 解释 UTF-8 字节,一段多字节在多次 -# WriteFile 之间被拆开/重组,表现为**整行消失**(最长的"首帧"行首当其冲)或相邻行错位、 -# 看似"重复且残缺"(含 '�')。故**运行时输出到控制台的文本一律改英文/ASCII** -# (banner / [diag] / Trigger= / exe base= / walk: primary=rbp_chain / frames=N 等), -# 任何代码页下都可读、可 grep;` File "<文件>", line <行>, in <函数名>` 帧行保留源路径 -# 原文(分隔符/新增说明文字一律 ASCII)。 -# 中文仅保留在:本文件的注释/文档串、以及**只写文件**的分隔头。 +# (a) File output (solves "no Break key" and "console code page eats characters"): on successful Install, open an +# **append** handle for ` + ".stackdump.txt"` (OPEN_ALWAYS + +# FILE_APPEND_DATA, creating it if absent); EmitLine writes **the same content** both to the console and appending to disk. +# The file is raw UTF-8 bytes, unaffected by the console code page; each dump prepends a small separator header +# (local timestamp/pid/trigger key/main thread id/Seq, Chinese labels, file only). A file write failure **does not block** +# the console and is reported only once (see EmitToFile). On successful Install it prints a line +# `[traceback] stack dump also written to: ` to confirm the destination. +# (b) Pure-ASCII console: the runtime text was originally Chinese (UTF-8 bytes), while cmd's console output code page +# defaults to 936 (GBK) -- WriteFile to the console interprets UTF-8 bytes as 936, and a multi-byte sequence split/rejoined +# across several WriteFile calls appears as **whole lines disappearing** (the longest "first frame" line first) or adjacent lines shifting, +# looking "duplicated and mangled" (including the U+FFFD replacement character). So **all runtime text sent to the console is changed to English/ASCII** +# (banner / [diag] / Trigger= / exe base= / walk: primary=rbp_chain / frames=N, etc.), +# readable and greppable under any code page; the ` File "", line , in ` frame line keeps the source path +# text as-is (separators/added explanatory text are always ASCII). +# Chinese is kept only in: this file's comments/docstrings, and the **file-only** separator header. # # ----------------------------------------------------------------------------- -# 八、依赖的既有声明(未改动)与本模块新增声明 +# 8. Existing declarations relied on (unchanged) and declarations added by this module # ----------------------------------------------------------------------------- -# 既有(`from w32.win32base import *` 取裸名):GetModuleHandleA / GetProcAddress / +# Existing (`from w32.win32base import *` brings bare names): GetModuleHandleA / GetProcAddress / # CloseHandle / GetLastError / INVALID_HANDLE_VALUE / ERROR_FILE_NOT_FOUND / -# LARGE_INTEGER / SYSTEMTIME / GetLocalTime / HANDLE / BOOL / ULONG / VOIDPTR; +# LARGE_INTEGER / SYSTEMTIME / GetLocalTime / HANDLE / BOOL / ULONG / VOIDPTR; # wf.CreateFileA / wf.ReadFile / wf.WriteFile / wf.GetFileSizeEx / wf.GetStdHandle / # wf.GENERIC_READ / wf.FILE_SHARE_READ / wf.FILE_SHARE_WRITE / wf.OPEN_EXISTING / # wf.OPEN_ALWAYS / wf.FILE_ATTRIBUTE_NORMAL / -# wf.STD_ERROR_HANDLE / wf.STD_OUTPUT_HANDLE; +# wf.STD_ERROR_HANDLE / wf.STD_OUTPUT_HANDLE; # wp.OpenThread / wp.SuspendThread / wp.ResumeThread / wp.GetCurrentThreadId / # wp.GetCurrentProcess / wp.GetCurrentProcessId / wp.THREAD_SUSPEND_RESUME / -# wp.THREAD_GET_CONTEXT; -# string.memset / string.strcpy / stdlib.malloc / free / c.Addr。 -# 本轮新增:wc.SetConsoleCtrlHandler / wc.CTRL_C_EVENT / wc.CTRL_BREAK_EVENT; -# wp.CONTEXT(x64 完整布局)/ wp.CONTEXT_FULL / wp.GetThreadContext; -# wm.ReadProcessMemory(由本模块 ReadMemCb 包装后作 StackWalk64 回调); -# w32/win32dbghelp.vp(STACKFRAME64/ADDRESS64/KDHELP64 + SymSetOptions/ +# wp.THREAD_GET_CONTEXT; +# string.memset / string.strcpy / stdlib.malloc / free / c.Addr. +# New this round: wc.SetConsoleCtrlHandler / wc.CTRL_C_EVENT / wc.CTRL_BREAK_EVENT; +# wp.CONTEXT (full x64 layout) / wp.CONTEXT_FULL / wp.GetThreadContext; +# wm.ReadProcessMemory (wrapped by this module's ReadMemCb as the StackWalk64 callback); +# w32/win32dbghelp.vp (STACKFRAME64/ADDRESS64/KDHELP64 + SymSetOptions/ # SYMOPT_* + SymInitialize/SymFunctionTableAccess64/SymGetModuleBase64/ -# StackWalk64); -# 转储文件:wf.WriteFile 追加写 .stackdump.txt + 自备常量 -# TRACEBACK_FILE_APPEND_DATA(FILE_APPEND_DATA,wf 未定义); -# 转储重入保护用内联汇编的一条 `lock cmpxchg`(AtomicTryEnterDump)对模块级 -# 32 位 _DumpLock 做原子"检查并占用"(不用 InterlockedCompareExchange:x64 上是 -# 编译器内部函数、无导出符号、不可链接)。 -# 本轮新增(真 Python traceback 输出):kernel32!GetEnvironmentVariableA 的**本模块自声明** -# (win32base 未声明,仓库仅 VET/App/lib/VDirs.vp:45 有;kernel32 本就链接,TPC/TPV 均 -# 可解析),用于 TRACEBACK_DIAG 开关;其余仅用既有 API(见第十三节)。 +# StackWalk64); +# Dump file: wf.WriteFile appends to .stackdump.txt + this module's own constant +# TRACEBACK_FILE_APPEND_DATA (FILE_APPEND_DATA, not defined by wf); +# Dump reentrancy protection uses one inline-asm `lock cmpxchg` (AtomicTryEnterDump) on the module-level +# 32-bit _DumpLock for atomic test-and-set (not InterlockedCompareExchange: on x64 it is +# a compiler intrinsic, has no exported symbol, and is not linkable). +# New this round (real Python traceback output): **this module's own declaration** of kernel32!GetEnvironmentVariableA +# (not declared by win32base; the repo declares it only in VET/App/lib/VDirs.vp:45; kernel32 is linked anyway, so both TPC and +# TPV can resolve it), used for the TRACEBACK_DIAG switch; everything else uses only existing APIs (see section 13). # # ----------------------------------------------------------------------------- -# 九、"0 帧"的可执行结论(哪种情形 → 看哪个现场字段) +# 9. Actionable conclusions for "0 frames" (which case -> which live field to look at) # ----------------------------------------------------------------------------- -# ⚠ 本节所有取证行都是 `[diag]` 行,故**只在 TRACEBACK_DIAG=1 时产出**(见第十三节 C); -# 排查"0 帧"时请先用 `set TRACEBACK_DIAG=1` 启动目标进程。 -# 首帧取证两行(" [diag] StackWalk64 first: ret=… addrpc=… fta=…" 与紧随其后的 -# " [diag] StackWalk64 in: machine=… rip=… rsp=… rbp=…"——原为单条 143 字符长行, -# 拆成两条短行以消除窄终端折行造成的"显示残缺"假象,字段一个不减) -# 与前置诊断行("[diag] Seq=… … modinited=…" / "SymGetModuleBase64(main)=…")合起来直接给出结论: -# (1) modinited == 0 且 SymGetModuleBase64(main) == 0 ⇒ dbghelp 的模块表不含主模块。 -# 根因是 SymInitialize 的 fInvadeProcess 参数(必须 TRUE)或调用时机(要在进程启动期、 -# 模块枚举可用时调用,本模块放在 Install 里)。修法:确认 Install 里 SymInitialize 的 -# 第三个实参为 1,且 Install 在入口线程、模块全部加载后调用;必要时改用 -# SymRefreshModuleList 重新枚举。 -# (2) 首帧 ret == 0,且紧邻有 "[diag] readmem failed addr=… size=… GetLastError=…" -# ⇒ 读栈内存失败。`ReadProcessMemory` 在回调里返回 0 会让 StackWalk64 立刻终止(=0 帧)。 -# 修法:核对 ReadMemCb 的宽度口径(dbghelp 侧是 DWORD nSize + LPDWORD,kernel32 侧是 -# SIZE_T,本模块已用本地 sizet 承接再写回 DWORD)与进程句柄/权限。 -# (3) 首帧 ret != 0 但返回 addrpc == 0,或 fta(SymFunctionTableAccess64(addrpc))== 0 -# ⇒ 上下文/模块基址口径问题(传给 StackWalk64 的 RIP/RSP/RBP 或模块基址不对)。 -# 修法:核对 CONTEXT 的 Rip/Rsp/Rbp 取值与 GetModuleHandleA(NULL) 基址。 -# 补充:首帧行里 `ret=not-called` 表示 SymInitialize/回调地址/帧缓冲前提不全、根本没调用 -# StackWalk64(此时该行仍会产出,保证"转 RBP 链"之前永远有首帧证据)。 -# 三者互斥且可判:只要把上面两组打印字段与判据对照,就能**当场**确定属于哪一类。 +# NOTE: every evidence line in this section is a `[diag]` line, so it is **produced only when TRACEBACK_DIAG=1** (see section 13 C); +# when investigating "0 frames" first start the target process with `set TRACEBACK_DIAG=1`. +# The two first-frame evidence lines (" [diag] StackWalk64 first: ret=... addrpc=... fta=..." and the one immediately after, +# " [diag] StackWalk64 in: machine=... rip=... rsp=... rbp=..." -- originally one 143-char line, +# split into two short lines to remove the "mangled display" illusion caused by wrapping in a narrow terminal, with no field dropped) +# together with the preflight lines ("[diag] Seq=... ... modinited=..." / "SymGetModuleBase64(main)=...") directly give the conclusion: +# (1) modinited == 0 and SymGetModuleBase64(main) == 0 => dbghelp's module table does not contain the main module. +# Root cause is SymInitialize's fInvadeProcess argument (must be TRUE) or the call timing (it must be called during process +# startup while module enumeration is available; this module does it in Install). Fix: confirm Install's SymInitialize +# third argument is 1, and that Install is called on the entry thread after all modules are loaded; if needed use +# SymRefreshModuleList to re-enumerate. +# (2) first frame ret == 0 with an adjacent "[diag] readmem failed addr=... size=... GetLastError=..." +# => reading stack memory failed. A `ReadProcessMemory` returning 0 in the callback makes StackWalk64 stop at once (= 0 frames). +# Fix: check ReadMemCb's width convention (dbghelp side is DWORD nSize + LPDWORD, kernel32 side is +# SIZE_T; this module receives into a local sizet then writes back as DWORD) and the process handle/privileges. +# (3) first frame ret != 0 but returned addrpc == 0, or fta (SymFunctionTableAccess64(addrpc)) == 0 +# => a context/module-base convention problem (the RIP/RSP/RBP passed to StackWalk64 or the module base is wrong). +# Fix: check CONTEXT's Rip/Rsp/Rbp values against the GetModuleHandleA(NULL) base. +# Note: `ret=not-called` in the first-frame line means the SymInitialize/callback-address/frame-buffer preconditions are incomplete and +# StackWalk64 was never called (the line is still produced then, guaranteeing first-frame evidence always exists before "switching to the RBP chain"). +# The three cases are mutually exclusive and decidable: just compare the two printed field groups above against the criteria to determine **on the spot** which case it is. # # ----------------------------------------------------------------------------- -# 十、本轮新增的两组定位打印(应答"walk 到底跑到没有"与"重复残缺真因") +# 10. Two groups of locating prints added this round (answering "did the walk run at all" and "the real cause of duplicate/mangled") # ----------------------------------------------------------------------------- -# A. walk 是否被调用、走了哪条出口(全部走 DumpMainThread / WalkViaDbgHelp 的既有输出通道; -# 这些行**只在 TRACEBACK_DIAG=1 时输出**): -# * DumpMainThread 在**调用前**打 " [diag] walk: enter use_dbghelp=… symready=… -# modinited=… frame_buf=… mapok=… asan=…",**调用后**打 " [diag] walk: leave -# frames=… mapok=…"。两行都在 ⇒ walk 一定进了 RBP 主路径(dbghelp 加强可选)。 -# * 若 **连 walk: enter 都不出现**(且 TRACEBACK_DIAG=1)⇒ DumpMainThread 在本行之前就返回: -# 只可能是三处 fallback(OpenThread / SuspendThread / GetThreadContext 失败,各自都有 -# "fallback: … cannot dump stack" 行)——据该行即可定位被跳过的分支。 -# * WalkViaDbgHelp 的**每一条 return** 都先打 " [diag] walk: return reason=<…> frames=…", -# reason ∈ {null_state, disabled, null_frame_buf, null_context, stackwalk_first_failed, -# stackwalk_failed, addrpc_zero, frame_limit}(含义见函数内注释),故 walk 的走向 -# 必可判定、不再靠猜。本轮起主路径是 RBP 链,DumpMainThread 另打 -# `walk: primary=rbp_chain` / `walk: rbp_chain frames=…` / `walk: dbghelp enhancement engaged`; -# dbghelp 不可用时另打 `[diag] symwalk=disabled reason=…`(见第十一节 B)。 -# B. "重复残缺"真因的判别凭据(唯一凭据 = 行号是否重复): -# * EmitLine 在 **TRACEBACK_DIAG=1** 时给**每一行**加全局递增前缀 " [dump#] " -# (k 由 _DumpLineNo 每次 +1);关闭(默认)时**不加前缀**,使帧行保持纯 Python -# traceback 形态(见第十三节)。 -# * 同一段文本出现多份且**序号重复**(同一 k 出现两次)⇒ 该文本被写了两次,即**并发写 -# 同一 LineBuf**;此时才需要"输出互斥"。同一序号不可能出自单线程的一次 EmitLine。 -# * 文本被**截断/换行错位**但**序号严格递增、无重复** ⇒ 内容只写了一次(乱不了号), -# 只可能是**终端长行折行/回显**层的显示效果;修法是缩短长行(本轮已把三条最长行拆短), -# **不动锁**。 -# * 本轮补打:`[dump#skip]` 仅在使用输出互斥时才需要;本模块判定为"显示层", -# 故未加互斥、未打印该标记(判据与结论见交付说明)。 +# A. Whether the walk was called and which exit it took (all via DumpMainThread / WalkViaDbgHelp's existing output channels; +# these lines are **output only when TRACEBACK_DIAG=1**): +# * DumpMainThread prints, **before the call**, " [diag] walk: enter use_dbghelp=... symready=... +# modinited=... frame_buf=... mapok=... asan=...", and, **after the call**, " [diag] walk: leave +# frames=... mapok=...". Both lines present => the walk did enter the RBP main path (dbghelp augmentation optional). +# * If **even walk: enter does not appear** (and TRACEBACK_DIAG=1) => DumpMainThread returned before this line: +# it can only be one of three fallbacks (OpenThread / SuspendThread / GetThreadContext failing, each with its own +# "fallback: ... cannot dump stack" line) -- that line locates the skipped branch. +# * **Every return** in WalkViaDbgHelp first prints " [diag] walk: return reason=<...> frames=...", +# with reason in {null_state, disabled, null_frame_buf, null_context, stackwalk_first_failed, +# stackwalk_failed, addrpc_zero, frame_limit} (meanings in the in-function comments), so the walk's path +# is always decidable, never guessed. Since this round the main path is the RBP chain; DumpMainThread additionally prints +# `walk: primary=rbp_chain` / `walk: rbp_chain frames=...` / `walk: dbghelp enhancement engaged`; +# when dbghelp is unavailable it additionally prints `[diag] symwalk=disabled reason=...` (see section 11 B). +# B. Evidence for the real cause of "duplicate/mangled" (the only evidence = whether line numbers repeat): +# * EmitLine adds a globally increasing prefix " [dump#] " to **every line** when **TRACEBACK_DIAG=1** +# (k is incremented per call via _DumpLineNo); when off (default) it adds **no prefix**, keeping frame lines as plain Python +# traceback form (see section 13). +# * The same text appearing several times with **repeated numbers** (the same k twice) => that text was written twice, i.e. **concurrent writes to +# the same LineBuf**; only then is "output mutual exclusion" needed. The same number cannot come from a single-threaded EmitLine. +# * Text **truncated / line-wrapped out of place** but with **strictly increasing, non-repeating numbers** => the content was written once (numbers cannot be scrambled), +# so it can only be a **terminal long-line wrap/echo** display effect; the fix is to shorten long lines (this round split the three longest), +# **without touching the lock**. +# * Added this round: `[dump#skip]` is only needed when output mutual exclusion is used; this module judged it a "display-layer" +# issue, so no mutual exclusion was added and that marker is not printed (criteria and conclusion in the delivery notes). # # ----------------------------------------------------------------------------- -# 十一、本轮两根因修复(崩溃 = 属性访问被降级;0 帧 = dbghelp 与环境互斥) +# 11. Two root-cause fixes this round (crash = attribute access silently demoted; 0 frames = dbghelp conflicting with the environment) # ----------------------------------------------------------------------------- -# A. **崩溃根因不是分配失败,而是 TPC 把 `_State.<字段>` 静默降级成常量 null。** -# 实测证据(构建产物 IR,模块前缀 = 本文件 sha1 前 16 位 9b4ee486e08837c5): -# * 源码 `string.memset(_State.FrameBuf, 0, …)` 被生成为 -# `call i8* @memset(i8* null, i32 0, i64 248)` —— 参数就是字面 null; -# 同一函数里 `_State.LineBuf`(snprintf)、`_State.FuncTableAddr / ModuleBaseAddr` -# (StackWalk64 的两个回调槽)同样全被折成 null。 -# * `Install` 里的 `calloc(1,248)` 与其"分配失败即 return 0"的判空**都在且正确** -# (IR 里 `@calloc` 返回值被正常 store 到字段 20),故**不是** calloc 返回 NULL。 -# 机理(TPC 侧,只报不改):`TransPyC/lib/core/Handles/HandlesExprAttr.py` -# `_resolve_var_classname`(约 L285-293)对 `rawptr[X]` 形态的模块级全局只看 -# `GVar.type.pointee`(= `X*`,仍是指针)⇒ 认不出 `X`,返回 None;兜底的 -# `_recover_struct_class`(约 L723-740)虽能多解一层指针,但要求 `X` 已进入 -# **函数级**的 `Gen.variables/global_vars`(`HandlesFunctions.py:1848` 每函数清空), -# 而该登记只发生在"裸名 `X` 被求值一次"时(`HandlesExpr.py:479/487`)。 -# ⇒ 凡**一上手就直接读写 `_State.<字段>`、且函数内从未单独求值过裸名 `_State` -# 的函数**,`_handle_attr_name` 在 L507-508 返回 None: -# - 作为调用实参 ⇒ 被静默替换为 null(→ memset(null) 崩溃); -# - 作为 `if X == None:` 的条件 ⇒ 整个 `if` 被 `HandlesIf` L170-171 丢弃 -# (→ 我们原来写的 `if _State.FrameBuf == None: …` **根本没被生成**)。 -# 受影响函数(IR 佐证:这些函数体内 `@_State` 的 load 次数为 0): -# `EntryRva` / `LookupRva` / `EmitFrame` / `WalkViaDbgHelp`。 -# 其余函数(`EmitLine` / `DumpMainThread` / `ReadMemCb` / `Install` …)入口都有 -# `_State == None` 判空 ⇒ 裸名已登记 ⇒ 字段访问正常(这就是为什么 -# `DumpMainThread` 打出的 `frame_buf=1`、`modinited=1` 都是真的)。 -# 本模块的修法(**不去改 TPC**):凡直接使用 `_State.<字段>` 的函数,入口 -# **第一句就写 `if _State == None: …`**(即使是冗余判空)——它既是真正的兜底, -# 也把裸名 `_State` 登记进 `Gen.variables`,使其后的字段访问被正常解析。 -# 崩溃点再加一道局部量兜底:`FBuf: VOIDPTR = _State.FrameBuf` → 判 `FBuf == None` -# → `memset(FBuf, …)`,把"局部变量"这条最可靠的通道用在最危险的一行上。 -# B. **0 帧 / DbgHelp 状态共享**:ASan 运行时会自己注册 DbgHelp 符号化器,本模块的 -# SymInitialize 因此被拒(DbgHelp 进程上下文是**进程级**状态、按句柄唯一)。但这**不是 -# 冲突**:状态是共享的,用同一个 GetCurrentProcess() 伪句柄即可复用其模块表。故 Install -# 的判定改为**取证式**三步:SymInitialize 成功 → SymReady=1;失败 → SymRefreshModuleList -# 刷新共享模块表(官方"外部初始化后再刷新"写法);再统一用 SymGetModuleBase64(主模块) -# 探针裁决——探针为 0 则 SymReady 回退 0 并打印原因,绝不静默 0 帧。回溯路径顺序: -# * 主路径 = `WalkRbpChain`(纯 RBP 链,**零 dbghelp 依赖**); -# * **第二兜底 = `ScanStack` 栈扫描**(零 dbghelp、且**不依赖 RBP / 帧指针**): -# RBP 链只拿到当前 RIP(<=1 帧)时启用(`walk: fallback=stack_scan`),见第十二节; -# * dbghelp 加强:只有 RBP 链拿不到更多帧(<=1)且 DbgUsable != 0(= 探针通过)时才走 -# `WalkViaDbgHelp`。它**清空已收帧并整段重建**(顺带丢掉扫描推断帧),是唯一能用 PE -# 展开表(.pdata)穿过**无帧指针的系统帧**、回到 .vp 栈的路径——Ctrl+C 的典型现场 -# (主线程阻塞在系统 DLL 里、RBP=0)只有它能还原真实调用链。 -# * 降级**必须给原因**(不静默 0 帧):`[diag] symwalk=disabled reason=…`, -# reason ∈ {compile_time_switch, syminitialize_failed_or_main_module_missing, -# callback_missing, null_frame_buf};ASan 探测用 GetModuleHandleA 两个常见 -# ASan 运行时名(clang_rt / libclang_rt 前缀),**只用于诊断行**,不再参与裁决。 -# `WalkRbpChain` 同步加固(升级为主路径后它必须"绝不把卡死变成崩溃"): -# * 读 [rbp]/[rbp+8] 一律走 `wm.ReadProcessMemory`(**不直接解引用**野 rbp); -# * **每一步无条件打 `[diag] rbp:` 原始读数 + 判定**(先取证后决策,含被拒步;见第十二节); -# * 自洽校验:非 0 / 8 字节对齐 / `[rbp] > rbp` / 单步跨度 <= RBP_MAX_STEP / -# 返回地址落在主模块基址区间 [Base, Base+TRACEBACK_MODULE_MAX) 内才**接受**该帧。 +# A. **The crash root cause is not an allocation failure but TPC silently demoting `_State.` to the null constant.** +# Evidence (build-artifact IR; module prefix = first 16 hex digits of this file's sha1, 9b4ee486e08837c5): +# * The source `string.memset(_State.FrameBuf, 0, ...)` was generated as +# `call i8* @memset(i8* null, i32 0, i64 248)` -- the argument is the literal null; +# in the same function `_State.LineBuf` (snprintf), `_State.FuncTableAddr / ModuleBaseAddr` +# (StackWalk64's two callback slots) were likewise all folded to null. +# * `Install`'s `calloc(1,248)` and its null check "return 0 if allocation failed" **are both present and correct** +# (in the IR `@calloc`'s return value is stored to field 20 normally), so it is **not** calloc returning NULL. +# Mechanism (TPC side, report only, no change): `TransPyC/lib/core/Handles/HandlesExprAttr.py` +# `_resolve_var_classname` (approx. L285-293) looks only for a module-level global of the form `rawptr[X]` at +# `GVar.type.pointee` (= `X*`, still a pointer) => it fails to recognize `X` and returns None; the fallback +# `_recover_struct_class` (approx. L723-740) can peel one more pointer level, but requires `X` to already be in +# the **function-level** `Gen.variables/global_vars` (`HandlesFunctions.py:1848` clears it per function), +# and that registration only happens when "the bare name `X` is evaluated once" (`HandlesExpr.py:479/487`). +# => For any function that **reads/writes `_State.` right at the start and never separately evaluates the bare name `_State` +# inside the function**, `_handle_attr_name` returns None at L507-508: +# - as a call argument => silently replaced with null (-> memset(null) crash); +# - as the condition of `if X == None:` => the whole `if` is dropped by `HandlesIf` L170-171 +# (-> the `if _State.FrameBuf == None: ...` we originally wrote **was never generated**). +# Affected functions (IR corroboration: these function bodies load `@_State` 0 times): +# `EntryRva` / `LookupRva` / `EmitFrame` / `WalkViaDbgHelp`. +# Other functions (`EmitLine` / `DumpMainThread` / `ReadMemCb` / `Install` ...) all have a +# `_State == None` null check at entry => the bare name is registered => field access works (this is why +# the `frame_buf=1`, `modinited=1` printed by `DumpMainThread` are real). +# This module's fix (**without changing TPC**): every function that uses `_State.` directly gets +# **`if _State == None: ...` as its very first statement** (even if redundant) -- it is both a genuine fallback +# and registers the bare name `_State` into `Gen.variables`, so later field accesses resolve normally. +# At the crash point add one more local-variable fallback: `FBuf: VOIDPTR = _State.FrameBuf` -> check `FBuf == None` +# -> `memset(FBuf, ...)`, using the most reliable channel (a local variable) on the most dangerous line. +# B. **0 frames / DbgHelp state sharing**: the ASan runtime registers its own DbgHelp symbolizer, so this module's +# SymInitialize is rejected (the DbgHelp process context is a **process-level** state, unique per handle). But this is **not +# a conflict**: the state is shared, and using the same GetCurrentProcess() pseudo-handle reuses its module table. So Install's +# decision becomes an **evidence-based** three-step one: SymInitialize succeeds -> SymReady=1; fails -> SymRefreshModuleList +# refreshes the shared module table (the official "refresh after external init" idiom); then a SymGetModuleBase64(main module) +# probe arbitrates -- if the probe is 0, SymReady falls back to 0 and the reason is printed, never a silent 0 frames. Walk-path order: +# * main path = `WalkRbpChain` (pure RBP chain, **zero dbghelp dependency**); +# * **second fallback = `ScanStack` stack scan** (zero dbghelp and **no RBP / frame-pointer dependency**): +# enabled when the RBP chain yields only the current RIP (<=1 frame) (`walk: fallback=stack_scan`), see section 12; +# * dbghelp augmentation: only when the RBP chain yields no more frames (<=1) and DbgUsable != 0 (= probe passed) does it run +# `WalkViaDbgHelp`. It **clears the collected frames and rebuilds the whole stack** (dropping scan-inferred frames too) and is the only path that can use the PE +# unwind table (.pdata) to cross **frame-pointer-less system frames** back to the .vp stack -- the typical Ctrl+C scene +# (main thread blocked in a system DLL, RBP=0) can only have its real call chain recovered by it. +# * A degrade **must give a reason** (no silent 0 frames): `[diag] symwalk=disabled reason=...`, +# reason in {compile_time_switch, syminitialize_failed_or_main_module_missing, +# callback_missing, null_frame_buf}; the ASan probe uses GetModuleHandleA with two common +# ASan runtime names (clang_rt / libclang_rt prefixes), **for diagnostic lines only**, no longer part of the decision. +# `WalkRbpChain` is hardened in step (after becoming the main path it must "never turn a hang into a crash"): +# * reads of [rbp]/[rbp+8] always go through `wm.ReadProcessMemory` (**no direct dereference** of a wild rbp); +# * **every step unconditionally prints `[diag] rbp:` raw readings + verdict** (evidence before decision, including rejected steps; see section 12); +# * self-consistency checks: non-zero / 8-byte aligned / `[rbp] > rbp` / single-step span <= RBP_MAX_STEP / +# return address within the main-module base range [Base, Base+TRACEBACK_MODULE_MAX) -- only then is the frame **accepted**. # # ----------------------------------------------------------------------------- -# 十二、本轮新增:RBP 链逐步原始读数 + 栈扫描兜底(应答"栈走不长") +# 12. New this round: per-step RBP-chain raw readings + stack-scan fallback (answering "the walk is too short") # ----------------------------------------------------------------------------- -# 现场(用户实测,`#0` 已正确解析:RIP 在主模块、映射/查表/帧 #0 全对):`#1 = 0x121fa` -# ——远小于主模块基址、也不是栈地址 ⇒ 链在第 1 步即断。⇒ 先把"断在哪一步、为什么断" -# 变成可读数据,再补一条**不依赖 RBP**的路径。 -# A. **逐步原始读数**(`WalkRbpChain` 内,前缀 `[diag] rbp:`,**先于任何 reject 早退**, -# TRACEBACK_DIAG=1 时才输出): -# frame= rbp=0x… [rbp]=0x… [rbp+8]=0x… ← 读到的原始 8 字节值 -# frame= rpm_ok=<0/1> rpm_err= accept=<0/1> reject_reason=<…> -# reject_reason ∈ {rbp_zero, not_aligned, rpm_failed, ret_zero, ret_out_of_module, -# not_growing, step_too_large};被接受时 reason=`accept`、accept=1。 -# 仅被接受的步才 FramePush 入帧缓冲(旧实现在校验前就输出,会把 `0x121fa` 这类垃圾值当成 -# 一帧 external 打出来,误导读者)。三种断链根因据此一眼可分:读失败 / 值本身不是返回 -# 地址(帧指针未保留)/ 被自洽校验档掉。 -# B. **栈扫描兜底**(`ScanStack`):RBP 链 <=1 帧时启用(`walk: fallback=stack_scan`)。 -# 在 [RSP, RSP+2MiB) 内按 8 字节步长遍历,**4 KiB/块整块读**(失败即停并打地址/err); -# 候选 = 落在主模块区间、且**相邻 8 字节字(A-8 或 A+8)像栈地址**的字;按值去重、 -# 上限 64(诊断打 `scan_candidates=`);推断帧入缓冲时带 Scanned=1,打印时在帧行末尾加 -# ` [inferred]`(` File "…", line 0, in [inferred]`)明确标注**扫描推断** -# (可含误报,不与权威帧混淆,见第十三节)。全部读取只经 ReadProcessMemory, -# 绝不直接解引用主线程内存(诊断设施不许把"卡死"变成"崩溃")。 -# C. **帧指针是否保留**(调查结论与 TPC 侧修法落点,本模块只报不改): -# * 单一 llc 调用点 = `TransPyC/lib/Projectrans/Phase2Translator.py:2160 _llc_compile`, -# 它在 `--traceback` 打开时把 `-frame-pointer=all` 追加到命令(2187-2193); -# App 模块(如 `TransPyV/App/lib/core/Gen/Expr/ExprGenOps.vp`,模块 sha1 -# `8a96a2885bae09ca`)走的是**同一个**调用点(2608 的 _dso.ll / 2319 的内联合并), -# 故 flag 的落点本身没问题。 -# * 缓存失效:App 模块 obj = `output/{sha1}.obj`,`_compile_ll_files` 见其存在即跳过 -# (2621),但 `--traceback` 时 `hotspot_on`(1144)为真 ⇒ 1196-1204 会删除旧 obj -# 强制重编;include 模块另有隔离目录 `includes.binary_traceback`(3097-3099)+ -# 旧 obj 删除(3181-3190)。⇒ App/include 都不会静默复用"无帧指针的旧 obj"。 -# * 交付结论:若 `[diag] rbp:` 显示 `[rbp+8]` 仍不是返回地址(`ret_out_of_module`), -# 则说明 `-frame-pointer=all`(llc 默认值级别的开关)没有生效,最稳的改法是在**IR 里 -# 按函数显式标注** `"frame-pointer"="all"` —— 落点 -# `TransPyV/App/lib/core/Gen/Func/FuncGenBase.vp`(属性拼装 `_append_llvm_attr`,154-181) -# 与 `TransPyC/lib/core/Handles/HandlesFunctions.py:1735`(`func.attributes.add('noredzone')` -# 旁)—— 让帧指针成为**函数属性**而非 llc 命令行默认值,从根上免疫"llc 是否采纳默认值"。 -# * 【补充实测,Test/LLvmLiteTest Ctrl+C】`RBP=0x0`、RIP 落在系统 DLL:拒绝原因是 -# `rbp_zero`(不是 `ret_out_of_module`)⇒ 断链点**不在 .vp 函数**,而在被中断的那一层 -# 系统帧。RBP 是 callee-saved,但系统/CRT 的免帧指针代码会把它当**临时寄存器**用 -# (`xor ebp,ebp` 是常见的清零写法),故中断现场的 RBP 与"最近一个维护帧指针的祖先" -# 无关。⇒ 结论:**给 .vp 函数保留帧指针无法救这条路**(链在更内层就已断),只有基于 -# PE 展开表(.pdata)的回溯(dbghelp!StackWalk64,见第十一节 B)才能从这种现场还原 -# 真实调用链。帧指针开关因此**不是**本轮的修法落点,保留在上仅作历史记录。 +# Live case (user measurement, `#0` resolved correctly: RIP in the main module, mapping/lookup/frame #0 all correct): `#1 = 0x121fa` +# -- far below the main-module base and not a stack address => the chain breaks at step 1. => First turn "where it broke and why" +# into readable data, then add a path that **does not depend on RBP**. +# A. **Per-step raw readings** (inside `WalkRbpChain`, prefix `[diag] rbp:`, **before any reject early-return**, +# output only when TRACEBACK_DIAG=1): +# frame= rbp=0x... [rbp]=0x... [rbp+8]=0x... <- raw 8-byte values read +# frame= rpm_ok=<0/1> rpm_err= accept=<0/1> reject_reason=<...> +# reject_reason in {rbp_zero, not_aligned, rpm_failed, ret_zero, ret_out_of_module, +# not_growing, step_too_large}; when accepted, reason=`accept`, accept=1. +# Only accepted steps are FramePush-ed into the frame buffer (the old implementation output before validating, printing garbage values like `0x121fa` +# as an external frame, misleading the reader). The three chain-break root causes are distinguishable at a glance: read failure / the value itself is not a return +# address (frame pointer not preserved) / rejected by the self-consistency checks. +# B. **Stack-scan fallback** (`ScanStack`): enabled when the RBP chain yields <=1 frame (`walk: fallback=stack_scan`). +# Scans [RSP, RSP+2MiB) in 8-byte steps, **reading 4 KiB per chunk whole** (stops on failure and prints address/err); +# a candidate = a word within the main-module range whose **adjacent 8-byte word (A-8 or A+8) looks like a stack address**; de-duplicated by value, +# capped at 64 (diagnostic prints `scan_candidates=`); inferred frames enter the buffer with Scanned=1 and append +# ` [inferred]` (` File "...", line 0, in [inferred]`) to the frame line to mark them clearly as **scan inferences** +# (may contain false positives; never confused with authoritative frames; see section 13). All reads go only through ReadProcessMemory, +# never a direct dereference of main-thread memory (a diagnostic facility must not turn a "hang" into a "crash"). +# C. **Whether the frame pointer is preserved** (investigation conclusion and TPC-side fix location; this module reports only, no change): +# * The single llc call site = `TransPyC/lib/Projectrans/Phase2Translator.py:2160 _llc_compile`, +# which appends `-frame-pointer=all` to the command when `--traceback` is on (2187-2193); +# the App module (e.g. `TransPyV/App/lib/core/Gen/Expr/ExprGenOps.vp`, module sha1 +# `8a96a2885bae09ca`) goes through the **same** call site (2608's _dso.ll / 2319's inlining merge), +# so the flag's location itself is fine. +# * Cache invalidation: App module obj = `output/{sha1}.obj`, `_compile_ll_files` skips it if it exists +# (2621), but with `--traceback` `hotspot_on` (1144) is true => 1196-1204 deletes the old obj +# to force recompilation; the include module has a separate isolated dir `includes.binary_traceback` (3097-3099) + +# old-obj deletion (3181-3190). => Neither App nor include silently reuses a "frame-pointer-less old obj". +# * Delivery conclusion: if `[diag] rbp:` shows `[rbp+8]` is still not a return address (`ret_out_of_module`), +# then `-frame-pointer=all` (a flag at the level of an llc default) did not take effect; the most robust fix is to annotate +# `"frame-pointer"="all"` explicitly per function **in the IR** -- location +# `TransPyV/App/lib/core/Gen/Func/FuncGenBase.vp` (attribute assembly `_append_llvm_attr`, 154-181) +# and `TransPyC/lib/core/Handles/HandlesFunctions.py:1735` (next to `func.attributes.add('noredzone')` +# ) -- making the frame pointer a **function attribute** rather than an llc command-line default, immune at the root to "whether llc honors the default". +# * [Extra measurement, Test/LLvmLiteTest Ctrl+C] `RBP=0x0`, RIP in a system DLL: the reject reason is +# `rbp_zero` (not `ret_out_of_module`) => the break is **not in a .vp function** but in the interrupted +# system frame. RBP is callee-saved, but frame-pointer-less system/CRT code uses it as a **scratch register** +# (`xor ebp,ebp` is a common zeroing idiom), so the interrupted site's RBP is unrelated to "the nearest ancestor +# that maintains a frame pointer". => Conclusion: **keeping a frame pointer for .vp functions cannot save this path** (the chain is already broken further in); only +# PE unwind-table (.pdata) based walking (dbghelp!StackWalk64, see section 11 B) can recover +# the real call chain from such a site. The frame-pointer switch is therefore **not** this round's fix location and is kept above only as a historical record. # # ----------------------------------------------------------------------------- -# 十三、本轮:真 Python traceback 风格输出 + TRACEBACK_DIAG 开关 +# 13. This round: real Python-traceback-style output + the TRACEBACK_DIAG switch # ----------------------------------------------------------------------------- -# A. 输出形状(默认,TRACEBACK_DIAG 未开)——整段 traceback 形如: +# A. Output shape (default, TRACEBACK_DIAG off) -- the whole traceback looks like: # Traceback (most recent call last): # File "D:\...\App\main.vp", line 238, in main # w_upper: str = w_str.upper() # File "<0x7ff6... +0x1a2b no source line mapping>", line 0, in # File "<0x7ffb... external>", line 0, in [inferred] -# * **帧序反过来了**:回溯从最内层(当前 RIP)向调用者走,收集顺序 = 从内到外,打印时 -# 逆序 ⇒ **最外层(CRT/调用者)在前、最内层最后**(与 Python traceback 一致)。故必须 -# 先把帧收集进缓冲(FramePush),走栈结束后再逆序打印(DumpMainThread)。 -# * 命中源位置的帧打两行:第一行 2 空格缩进 `File "路径", line N, in 函数名`(函数名取不到 -# 写 ``);第二行 4 空格缩进 + 该行源码——**运行期重开该文件**顺序读第 N 行 -# (去行首尾空白、超长按 TRACEBACK_LINE_CAP 截断);**打不开 / 行号越界 / 读失败则整行 -# 不打**(无占位、无报错,静默跳过),实现见 EmitSourceText(栈上缓冲,回调线程零堆分配)。 -# * ScanStack 推断帧在帧行末尾加 ` [inferred]`(保留"可能是误报"的信息,不再用 `~` 前缀)。 -# * 未命中映射的帧 → `File "<0x<地址> +0x no source line>", line 0, in ` -# (地址+RVA 保留在同一行);落在主模块之外的帧 → `File "<0x<地址> external>", line 0, in `。 -# B. 帧缓冲:TracebackState.FrameAddr / FrameScan(`t.CArray[uint64/int32, TRACEBACK_FRAME_BUF]`, -# `TRACEBACK_FRAME_BUF = TRACEBACK_MAX_FRAMES + TRACEBACK_SCAN_MAX_HITS = 128`), -# **内嵌于状态对象** ⇒ 由 Install 的 `TracebackState.__sizeof__()` 分配覆盖,无需独立 malloc、 -# 回调线程零分配。缓冲满时新帧直接丢弃(不越界),最终 `frames=` 以实际收集数为准。 -# C. **TRACEBACK_DIAG 开关**:读环境变量 `TRACEBACK_DIAG`,值 == "1" 才开;**每进程只读一次 -# 并缓存**(_DiagFlag -1/0/1,见 DiagEnabled)。关闭(默认)时不输出任何 `[diag] ...` / -# `srcline map: ...` 行,也不加 ` [dump#] ` 行号前缀(见第十节 B),使 dump 成为干净的 -# traceback;打开时诊断信息量与既有一致(含逐帧 `[diag] rbp:` / `[diag] scan:` / symwalk 行)。 -# 取值 API = kernel32!GetEnvironmentVariableA(本模块顶部自声明,见第八节)。 -# D. 转储序号与落文件提示移到整段 traceback **之后**(` [traceback] dump seq=… also appended -# to .stackdump.txt` 行),不再插在帧中间;`Ctrl+C` 转储后返回 FALSE(进程退出)、 -# `Ctrl+Break` 转储后返回 TRUE(继续运行)语义不变。 +# * **Frame order is reversed**: the walk goes from the innermost (current RIP) outward to callers, so collection order = inner to outer, and printing reverses it +# => **the outermost (CRT/caller) first, the innermost last** (matching Python traceback). So frames must +# first be collected into a buffer (FramePush), then printed in reverse after the walk ends (DumpMainThread). +# * A frame that hits a source location prints two lines: the first is 2-space-indented `File "path", line N, in function` (function name +# is `` if unavailable); the second is 4-space-indented + that source line -- **the file is reopened at runtime** and line N read sequentially +# (leading/trailing whitespace stripped, over-long truncated to TRACEBACK_LINE_CAP); **if it cannot be opened / the line number is out of range / the read fails, the whole line is +# omitted** (no placeholder, no error, silently skipped); see EmitSourceText (stack buffer, zero heap allocation from the callback thread). +# * A ScanStack inferred frame appends ` [inferred]` to the frame line (keeping "may be a false positive" information, no longer a `~` prefix). +# * A frame missing from the mapping -> `File "<0x
+0x no source line>", line 0, in ` +# (address+RVA kept on the same line); a frame below the main module -> `File "<0x
external>", line 0, in `. +# B. Frame buffer: TracebackState.FrameAddr / FrameScan (`t.CArray[uint64/int32, TRACEBACK_FRAME_BUF]`, +# `TRACEBACK_FRAME_BUF = TRACEBACK_MAX_FRAMES + TRACEBACK_SCAN_MAX_HITS = 128`), +# **inlined into the state object** => covered by Install's `TracebackState.__sizeof__()` allocation, no separate malloc, +# zero allocation from the callback thread. When full, new frames are simply dropped (no overflow); the final `frames=` reflects the actual collected count. +# C. **TRACEBACK_DIAG switch**: reads env var `TRACEBACK_DIAG`, enabled only when the value == "1"; **read once per process +# and cached** (_DiagFlag -1/0/1, see DiagEnabled). When off (default) it emits no `[diag] ...` / +# `srcline map: ...` lines and adds no ` [dump#] ` line-number prefix (see section 10 B), making the dump a clean +# traceback; when on, the diagnostic detail matches the previous amount (including per-frame `[diag] rbp:` / `[diag] scan:` / symwalk lines). +# The read API = kernel32!GetEnvironmentVariableA (declared at the top of this module, see section 8). +# D. The dump sequence number and the file-output notice move to **after** the whole traceback ( +# ` [traceback] dump seq=... also appended to .stackdump.txt`), no longer inserted between frames; `Ctrl+C` dumping returns FALSE (process exits), +# `Ctrl+Break` dumping returns TRUE (keeps running) -- semantics unchanged. # # ----------------------------------------------------------------------------- -# 十四、崩溃转储(未处理异常;应答"崩溃无错误堆栈") +# 14. Crash dump (unhandled exception; answering "crash has no error stack") # ----------------------------------------------------------------------------- -# A. 动机:Ctrl+C 有栈,**崩溃没有**——ASan 一旦过了它的报告窗口就什么都不打印,OS 默认只留一个 -# 退出码(同一处崩溃实测:开 ASan = 0x80000003 STATUS_BREAKPOINT(int3),关 ASan = -# 0xC0000005 ACCESS_VIOLATION,两者都**没有堆栈**)。故 Install 追加注册 -# kernel32!SetUnhandledExceptionFilter(CrashHandler),由 CrashHandler → DumpCrashThread -# 在**出错线程**上补出这条缺失的栈。 -# B. DumpCrashThread 的三条纪律: -# * **绝不吞异常**:过滤器恒定返回 EXCEPTION_CONTINUE_SEARCH(0),退出码 / WER / ASan 报告 -# 的行为原样保留,本模块只加堆栈; -# * **不解引用 OS 指针**:EXCEPTION_POINTERS({ExceptionRecord, ContextRecord})与 -# EXCEPTION_RECORD 头部(u32 ExceptionCode / u32[…] ExceptionAddress)一律经 -# ReadProcessMemory 读取; -# * **不写 OS 的 CONTEXT**:StackWalk64 每走一帧都会回写它遍历的那条记录,故先把 OS 给的 -# CONTEXT 拷进 _State.ContextBuf(尺寸已与 winnt.h 对齐 0x4D0)再从副本走栈。 -# C. 走栈顺序与打印:dbghelp 优先(崩溃点通常在系统 DLL 里,RBP 链必然无从下手)→ RBP 链 → -# 栈扫描,与 Ctrl+C 路径同一套 WalkViaDbgHelp / WalkRbpChain / ScanStack 与 PrintFrames; -# 与 Ctrl+C 转储**共用同一把重入锁**(AtomicTryEnterDump),故一次只可能有一份 dump: -# 崩溃若发生在转储进行中,本次崩溃不转储(锁被占),避免二次伤害。 -# 输出形状:`=== TPC crash dump (unhandled exception; the process keeps terminating) ===` -# → ` Trigger=unhandled-exception code=0x… addr=0x… av_kind=… av_addr=0x… crashthread=…` -# → ` exe base=… RIP=… RSP=… RBP=…` → ` regs: rcx=… rdx=… r8=… r9=… r12=…` -# → 与 Ctrl+C 逐字同形的 Traceback 帧(含源码行回读)→ ` frames=N` → 落文件提示 -# → `=== crash dump end ===`。 -# * `av_kind`/`av_addr` 取自 EXCEPTION_RECORD 的 ExceptionInformation[0/1](0=读 1=写 8=执行; -# 数据地址),只在 0xC0000005 类访问违规时有效——崩溃现场正是"原始数字胜过猜测"的场合; -# * `regs` 给的是**出错帧的实参现场**(Win64 前 4 个整型实参 = rcx/rdx/r8/r9;r12 是该帧 -# 用来解引用指针的寄存器):一条就能区分「实参传错」与「实参对、指针所指内容错」, -# 不必再来一轮重编。Ctrl+C 路径不打印它(那里没有"出错指令的算子"可看)。 -# * **不打印 `[RBP+8]` 当返回地址**:.vp 帧的 RBP 是 locals base(`leaq N(%rsp), %rbp`), -# 返回地址在 RBP + 帧字节数 - 8(逐函数不同;实测 _TypePrintDepth 是 RBP+0x38), -# `[RBP+8]` 只是无关局部量。调用者一律以 .stackmap 走栈结果为准。 -# 中文件头(栈转储/触发=)只属于 Ctrl+C 路径,崩溃路径不动它。 -# D. 已知边界:ASan 的 VEH 优先于未处理异常过滤器——若某次崩溃由 ASan 正常报告(它自带堆栈), -# 本路径不会被调用;本路径覆盖的正是「ASan 报不出来 / 没开 ASan」那两类(实测的两个退出码 -# 都在其中)。栈耗尽类崩溃(本路径自身也需要栈)不在保障范围内。 +# A. Motivation: Ctrl+C has a stack, **a crash does not** -- once ASan passes its report window it prints nothing, and the OS leaves only +# an exit code (measurements of the same crash: ASan on = 0x80000003 STATUS_BREAKPOINT(int3), ASan off = +# 0xC0000005 ACCESS_VIOLATION; neither has **a stack**). So Install additionally registers +# kernel32!SetUnhandledExceptionFilter(CrashHandler), and CrashHandler -> DumpCrashThread +# produces the missing stack on the **faulting thread**. +# B. DumpCrashThread's three rules: +# * **Never swallow the exception**: the filter always returns EXCEPTION_CONTINUE_SEARCH(0); exit code / WER / ASan report +# behavior is preserved; this module only adds the stack; +# * **Never dereference OS pointers**: EXCEPTION_POINTERS ({ExceptionRecord, ContextRecord}) and +# the EXCEPTION_RECORD header (u32 ExceptionCode / u32[...] ExceptionAddress) are always read via +# ReadProcessMemory; +# * **Never write the OS's CONTEXT**: StackWalk64 writes back to the record it walks on every frame, so first copy the OS-provided +# CONTEXT into _State.ContextBuf (size already aligned with winnt.h, 0x4D0) and walk from the copy. +# C. Walk order and printing: dbghelp first (the crash point is usually in a system DLL, where the RBP chain is hopeless) -> RBP chain -> +# stack scan, the same WalkViaDbgHelp / WalkRbpChain / ScanStack and PrintFrames as the Ctrl+C path; +# it **shares the same reentrancy lock** with the Ctrl+C dump (AtomicTryEnterDump), so only one dump can exist at a time: +# if a crash happens while a dump is in progress, this crash is not dumped (lock held), avoiding a second hit. +# Output shape: `=== TPC crash dump (unhandled exception; the process keeps terminating) ===` +# -> ` Trigger=unhandled-exception code=0x... addr=0x... av_kind=... av_addr=0x... crashthread=...` +# -> ` exe base=... RIP=... RSP=... RBP=...` -> ` regs: rcx=... rdx=... r8=... r9=... r12=...` +# -> Traceback frames word-for-word identical to Ctrl+C (including source-line re-read) -> ` frames=N` -> file-output notice +# -> `=== crash dump end ===`. +# * `av_kind`/`av_addr` come from EXCEPTION_RECORD's ExceptionInformation[0/1] (0=read 1=write 8=execute; +# data address) and are valid only for 0xC0000005 access-violation-type faults -- a crash site is exactly where "raw numbers beat guessing"; +# * `regs` gives **the argument state of the faulting frame** (Win64's first 4 integer arguments = rcx/rdx/r8/r9; r12 is the register that frame +# uses to dereference a pointer): one line distinguishes "wrong argument passed" from "argument correct, pointed-to content wrong", +# with no need for another rebuild. The Ctrl+C path does not print it (there is no "operand of the faulting instruction" to see there). +# * **Does not print `[RBP+8]` as a return address**: a .vp frame's RBP is a locals base (`leaq N(%rsp), %rbp`), +# and the return address is at RBP + frame size - 8 (varies per function; measured _TypePrintDepth is RBP+0x38), +# `[RBP+8]` is just an unrelated local. Callers always rely on the .stackmap walk result. +# The Chinese file header (stack dump/trigger=) belongs only to the Ctrl+C path; the crash path leaves it alone. +# D. Known boundaries: ASan's VEH takes priority over the unhandled-exception filter -- if a crash is reported normally by ASan (which has its own stack), +# this path is not called; this path covers exactly the two cases where "ASan cannot report it / ASan is off" (the two measured exit codes +# are both among them). Stack-exhaustion crashes (this path itself needs stack) are out of scope. # ============================================================================= # This module needs kernel32!GetEnvironmentVariableA for the TRACEBACK_DIAG switch, but # includes/w32/win32base.vp does not declare it (the repository declares it only in diff --git a/vqt6/__init__.vp b/vqt6/__init__.vp index 4355eed..2a47c0a 100644 --- a/vqt6/__init__.vp +++ b/vqt6/__init__.vp @@ -1,8 +1,8 @@ # ============================================================ -# vqt6 — Viper 的 Qt6 绑定包 +# vqt6 - Viper's Qt6 binding package # -# 公共 Pythonic API(基于 C 桥接层) -# 用户用法: +# Public Pythonic API (built on the C bridge layer) +# Usage: # import vqt6 # app = vqt6.QApplication([]) # win = vqt6.QWidget() diff --git a/vqt6/_qtcore.vp b/vqt6/_qtcore.vp index cca93ec..d9b390f 100644 --- a/vqt6/_qtcore.vp +++ b/vqt6/_qtcore.vp @@ -3,10 +3,10 @@ from stdint import * from vqt6._types import * # ============================================================ -# vqt6/_qtcore.py — QtCore 的 t.State FFI 声明 +# vqt6/_qtcore.py - t.State FFI declarations for QtCore # -# 所有函数都是 C 桥接层 (libvqt6_bridge.a) 的 extern "C" 函数 -# 命名规范: vqt6__ +# All functions are extern "C" functions of the C bridge layer (libvqt6_bridge.a) +# Naming convention: vqt6__ # ============================================================ # ============================================================ diff --git a/vqt6/_qtgui.vp b/vqt6/_qtgui.vp index 798df01..379a260 100644 --- a/vqt6/_qtgui.vp +++ b/vqt6/_qtgui.vp @@ -3,7 +3,7 @@ from stdint import * from vqt6._types import * # ============================================================ -# vqt6/_qtgui.py — QtGui 的 t.State FFI 声明 +# vqt6/_qtgui.py - t.State FFI declarations for QtGui # ============================================================ # ============================================================ diff --git a/vqt6/_qtnetwork.vp b/vqt6/_qtnetwork.vp index 1ba51a4..5de885a 100644 --- a/vqt6/_qtnetwork.vp +++ b/vqt6/_qtnetwork.vp @@ -3,7 +3,7 @@ from stdint import * from vqt6._types import * # ============================================================ -# vqt6/_qtnetwork.py — QtNetwork 的 t.State FFI 声明 +# vqt6/_qtnetwork.py - t.State FFI declarations for QtNetwork # ============================================================ # ============================================================ diff --git a/vqt6/_qtwidgets.vp b/vqt6/_qtwidgets.vp index 5b02f94..7df6892 100644 --- a/vqt6/_qtwidgets.vp +++ b/vqt6/_qtwidgets.vp @@ -3,7 +3,7 @@ from stdint import * from vqt6._types import * # ============================================================ -# vqt6/_qtwidgets.py — QtWidgets 的 t.State FFI 声明 +# vqt6/_qtwidgets.py - t.State FFI declarations for QtWidgets # ============================================================ # ============================================================ diff --git a/vqt6/_types.vp b/vqt6/_types.vp index 22e9712..ed83505 100644 --- a/vqt6/_types.vp +++ b/vqt6/_types.vp @@ -2,10 +2,10 @@ import t from stdint import * # ============================================================ -# vqt6/_types.py — 公共类型定义 +# vqt6/_types.py - common type definitions # -# Qt6 C++ 类在 Viper 端都是不透明指针 (void*) -# 不暴露 C++ 类的内部布局,只暴露指针 +# Qt6 C++ classes are all opaque pointers (void*) on the Viper side +# The internal layout of C++ classes is not exposed, only pointers are # ============================================================ # Opaque pointer typedefs (Viper-side representation of Qt6 C++ classes) diff --git a/w32/win32dbghelp.vp b/w32/win32dbghelp.vp index fcfece8..045cd62 100644 --- a/w32/win32dbghelp.vp +++ b/w32/win32dbghelp.vp @@ -3,33 +3,33 @@ from stdint import * from w32.win32base import * # ============================================================================= -# dbghelp / StackWalk64 声明(供 includes/traceback.vp 的 Ctrl+C 栈转储使用) +# dbghelp / StackWalk64 declarations (used by the Ctrl+C stack dump in includes/traceback.vp) # -# 声明本仓用到的 dbghelp 栈回溯 API 与三个栈帧结构体: -# SymInitialize 初始化 dbghelp 的进程上下文(fInvadeProcess=TRUE 只枚举模块、 -# 配合 SYMOPT_DEFERRED_LOADS 不加载 PDB) -# SymRefreshModuleList 本进程的 DbgHelp 已被其它组件初始化(本模块的 SymInitialize 被拒) -# 时,按其既有进程上下文重新枚举模块表("外部初始化后再刷新") -# SymSetOptions 设定 SymInitialize 之后固定的选项集 -# SymFunctionTableAccess64 按 RIP 查 RUNTIME_FUNCTION(PE 异常目录;不需要调试符号) -# SymGetModuleBase64 按 RIP 取模块基址 -# StackWalk64 逐帧回溯(MachineType=IMAGE_FILE_MACHINE_AMD64) +# Declares the dbghelp stack-walking APIs used by this repo plus three stack frame structs: +# SymInitialize initializes the dbghelp process context (fInvadeProcess=TRUE enumerates modules only, +# combined with SYMOPT_DEFERRED_LOADS it does not load PDBs) +# SymRefreshModuleList when this process's DbgHelp was already initialized by another component (this module's SymInitialize is refused), +# re-enumerates the module table using its existing process context ("refresh after external init") +# SymSetOptions sets the option set fixed by SymInitialize +# SymFunctionTableAccess64 looks up the RUNTIME_FUNCTION by RIP (PE exception directory; does not need debug symbols) +# SymGetModuleBase64 gets the module base address by RIP +# StackWalk64 walks frame by frame (MachineType=IMAGE_FILE_MACHINE_AMD64) # -# 其中 SymInitialize / SymSetOptions / StackWalk64 由 traceback.vp **直接调用**(静态引用); -# SymFunctionTableAccess64 / SymGetModuleBase64 既作为 StackWalk64 的回调原型在此登记,也被 -# traceback.vp **直接调用**做首帧取证诊断(首帧返回码/AddrPC/SymFunctionTableAccess64 返回值, -# 用于判定 StackWalk64 "0 帧"的根因)——同样属静态引用。traceback.vp 仍用 GetProcAddress 取它们 -# 的地址以填入 StackWalk64 的回调槽,为的是避开「跨模块函数名 → 函数指针」的隐式转换 -# (该动态解析写法见 VET/App/lib/VGuard.vp:852-867)。 +# Among these, SymInitialize / SymSetOptions / StackWalk64 are **called directly** by traceback.vp (static references); +# SymFunctionTableAccess64 / SymGetModuleBase64 are both registered here as StackWalk64 callback prototypes and +# **called directly** by traceback.vp for first-frame forensic diagnostics (first-frame return code / AddrPC / SymFunctionTableAccess64 return value, +# used to determine the root cause of StackWalk64 "0 frames") -- also static references. traceback.vp still uses GetProcAddress to get their +# addresses to fill StackWalk64's callback slots, in order to avoid the implicit conversion of "cross-module function name -> function pointer" +# (see VET/App/lib/VGuard.vp:852-867 for that dynamic-resolution idiom). # -# 链接期依赖:dbghelp.lib(MinGW: libdbghelp.a,-ldbghelp);ReadProcessMemory 在 -# kernel32(includes/w32/win32memory.vp:72)。本文件不引入其它链接期符号。 +# Link-time dependencies: dbghelp.lib (MinGW: libdbghelp.a, -ldbghelp); ReadProcessMemory is in +# kernel32 (includes/w32/win32memory.vp:72). This file introduces no other link-time symbols. # -# 结构体布局(x64,自然对齐;逐字段核对 winnt.h / dbghelp.h): -# ADDRESS64 0x00 Offset(u64) 0x08 Segment(u16) 0x0C Mode(enum=i32) → 16 字节 -# KDHELP64 96 字节(见字段注释) -# STACKFRAME64 248 字节(见字段注释) -# 三者只用于传给 StackWalk64(它按自身定义写入),故尺寸/偏移必须与 C 侧逐字节一致。 +# Struct layouts (x64, natural alignment; verified field by field against winnt.h / dbghelp.h): +# ADDRESS64 0x00 Offset(u64) 0x08 Segment(u16) 0x0C Mode(enum=i32) -> 16 bytes +# KDHELP64 96 bytes (see field comments) +# STACKFRAME64 248 bytes (see field comments) +# All three are only passed to StackWalk64 (which writes per its own definition), so sizes/offsets must match the C side byte for byte. # ============================================================================= # IMAGE_FILE_MACHINE_* (winnt.h; x64 = 0x8664) diff --git a/w32/winsock2.vp b/w32/winsock2.vp index 244a014..e134407 100644 --- a/w32/winsock2.vp +++ b/w32/winsock2.vp @@ -4,7 +4,7 @@ from w32.win32base import * # ============================================================ -# w32/winsock2.py — WinSock2 API 声明 (ws2_32.dll) +# w32/winsock2.py - WinSock2 API declarations (ws2_32.dll) # ============================================================ # Version constants diff --git a/zipfile.vp b/zipfile.vp index 374e842..18da550 100644 --- a/zipfile.vp +++ b/zipfile.vp @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# zipfile.vp — ZIP 归档读写模块(Python zipfile 子集) +# zipfile.vp - ZIP archive read/write module (subset of Python zipfile) # -# 支持: -# - 写入成员: ZIP_STORED / ZIP_DEFLATED(raw deflate,wbits=-15,CRC32 校验) -# - 读取成员: 自动识别 ZIP_STORED / ZIP_DEFLATED,解压后做 CRC32 校验 -# - 读取时从文件尾部回扫 EOCD(容忍注释/前导数据) +# Supports: +# - writing members: ZIP_STORED / ZIP_DEFLATED (raw deflate, wbits=-15, CRC32 check) +# - reading members: auto-detects ZIP_STORED / ZIP_DEFLATED, checks CRC32 after decompression +# - on read, scans back from the end of the file for EOCD (tolerates a comment/leading data) # -# 不支持: zip64(>4GB/成员数>65535)、加密、分卷、数据描述符(flag bit 3)等。 +# Not supported: zip64 (>4GB / more than 65535 members), encryption, spanning, data descriptors (flag bit 3), etc. # -# 成员名为原始 UTF-8 字节(写入时置 flag bit 11,读取时不自动解码)。 +# Member names are raw UTF-8 bytes (flag bit 11 is set when writing, not auto-decoded when reading). # -# 用法(对齐 Python 语义,只读模式自动把整个 zip 读入内存): +# Usage (aligned with Python semantics; read-only mode reads the whole zip into memory automatically): # w: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "w") -# w.write("a.txt", buf, buf_len) # 默认 ZIP_DEFLATED +# w.write("a.txt", buf, buf_len) # defaults to ZIP_DEFLATED # w.close() # # r: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "r") # olen: sizet = 0 -# data: bytes = r.read("a.txt", &olen) # malloc 缓冲,用后 free +# data: bytes = r.read("a.txt", &olen) # malloc buffer, free after use # err = zipfile.error_code() / zipfile.error() import stdlib import string