From 4887b2f64e382704cf1edd8c2d239aba96989532 Mon Sep 17 00:00:00 2001 From: cnc Date: Fri, 5 Jun 2026 15:51:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=EF=BC=9A=E8=A1=A5?= =?UTF-8?q?=E9=BD=90=20PythonPlugin=20C=20API=20WASM=20shim=20=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:按 LinuxCNC src/emc/pythonplugin/python_plugin.cc 的初始化、导入、配置和 callable 调用路径,把 PyConfig/PyStatus、PyImport_*、PyDict_SetItemString、Py_InitializeFromConfig、PyRun_SimpleString、Py_GetVersion、PyObject_Call 等符号纳入 inert WASM Python C API shim;真实 Python 执行仍保持禁用。 检查:./test-linuxcnc-wasm-python-c-api-symbols.sh 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。 --- core/wasm_shims/boost/python/object.hpp | 24 ++++++++ core/wasm_shims/python_c_api_probe_main.cc | 35 +++++++++++ core/wasm_shims/python_c_api_shim.cc | 67 ++++++++++++++++++++++ docs/linuxcnc-wasm-shims-source-map.md | 7 ++- test-linuxcnc-wasm-python-c-api-symbols.sh | 20 ++++--- 5 files changed, 143 insertions(+), 10 deletions(-) diff --git a/core/wasm_shims/boost/python/object.hpp b/core/wasm_shims/boost/python/object.hpp index 9c11520..8349af7 100644 --- a/core/wasm_shims/boost/python/object.hpp +++ b/core/wasm_shims/boost/python/object.hpp @@ -85,6 +85,18 @@ struct _inittab { PyObject *(*initfunc)(void); }; +struct PyStatus { + int _type; + const char *func; + const char *err_msg; + int exitcode; +}; + +struct PyConfig { + wchar_t *program_name; + int buffered_stdio; +}; + using PyCFunction = PyObject *(*)(PyObject *, PyObject *); using getter = PyObject *(*)(PyObject *, void *); using setter = int (*)(PyObject *, PyObject *, void *); @@ -146,6 +158,7 @@ void Py_DecRef(PyObject *); int PyArg_ParseTuple(PyObject *, const char *, ...); int PyArg_VaParse(PyObject *, const char *, va_list); PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...); +PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *); PyObject *PyObject_GetAttrString(PyObject *, const char *); int PyObject_SetAttrString(PyObject *, const char *, PyObject *); int PyObject_IsTrue(PyObject *); @@ -165,6 +178,17 @@ Py_ssize_t PySequence_Length(PyObject *); PyObject *Py_BuildValue(const char *, ...); PyObject *PyModule_Create(PyModuleDef *); int PyModule_AddObject(PyObject *, const char *, PyObject *); +int PyImport_ExtendInittab(_inittab *); +PyObject *PyImport_GetModuleDict(void); +PyObject *PyImport_AddModule(const char *); +int PyDict_SetItemString(PyObject *, const char *, PyObject *); +void PyConfig_InitPythonConfig(PyConfig *); +PyStatus PyConfig_SetString(PyConfig *, wchar_t **, const wchar_t *); +void PyConfig_Clear(PyConfig *); +PyStatus Py_InitializeFromConfig(PyConfig *); +wchar_t *Py_DecodeLocale(const char *, std::size_t *); +int PyRun_SimpleString(const char *); +const char *Py_GetVersion(void); int PyType_Ready(PyTypeObject *); PyObject *PyType_GenericNew(PyTypeObject *, PyObject *, PyObject *); void *PyObject_NewShim(std::size_t, PyTypeObject *); diff --git a/core/wasm_shims/python_c_api_probe_main.cc b/core/wasm_shims/python_c_api_probe_main.cc index 2dde26a..65cbbac 100644 --- a/core/wasm_shims/python_c_api_probe_main.cc +++ b/core/wasm_shims/python_c_api_probe_main.cc @@ -66,6 +66,7 @@ int main() { return 11; } if (PyObject_CallMethod(Py_None, const_cast("noop"), const_cast("")) != Py_None || + PyObject_Call(Py_None, Py_None, Py_None) != Py_None || PyObject_GetAttrString(Py_None, "noop") != Py_None || PyObject_SetAttrString(Py_None, "noop", Py_None) != 0) { return 19; @@ -82,6 +83,40 @@ int main() { if (PyModule_Create(nullptr) != Py_None || PyModule_AddObject(Py_None, "value", Py_None) != 0) { return 13; } + _inittab inittab[] = { + {"interpreter", nullptr}, + {nullptr, nullptr}, + }; + if (PyImport_ExtendInittab(inittab) != 0 || PyImport_GetModuleDict() == Py_None || + PyImport_AddModule("interpreter") == Py_None || + PyDict_SetItemString(PyImport_GetModuleDict(), "interpreter", PyImport_AddModule("interpreter")) != 0) { + return 21; + } + PyConfig config{}; + PyConfig_InitPythonConfig(&config); + if (config.program_name != nullptr || config.buffered_stdio != 1) { + return 22; + } + const wchar_t program_name[] = L"linuxcnc"; + PyStatus config_status = PyConfig_SetString(&config, &config.program_name, program_name); + if (config_status._type != 0 || config.program_name != program_name) { + return 23; + } + PyStatus init_status = Py_InitializeFromConfig(&config); + if (init_status._type != 0) { + return 24; + } + std::size_t decoded_size = 99; + if (Py_DecodeLocale("linuxcnc", &decoded_size) != nullptr || decoded_size != 8) { + return 25; + } + if (PyRun_SimpleString("pass") != 0 || std::strcmp(Py_GetVersion(), "wasm-python-disabled") != 0) { + return 26; + } + PyConfig_Clear(&config); + if (config.program_name != nullptr || config.buffered_stdio != 0) { + return 27; + } if (PyType_Ready(&PyList_Type) != 0 || PyType_GenericNew(&PyList_Type, Py_None, Py_None) != Py_None) { return 14; } diff --git a/core/wasm_shims/python_c_api_shim.cc b/core/wasm_shims/python_c_api_shim.cc index 89fa8fa..ed40c0b 100644 --- a/core/wasm_shims/python_c_api_shim.cc +++ b/core/wasm_shims/python_c_api_shim.cc @@ -1,6 +1,7 @@ #include "boost/python/object.hpp" #include +#include // LinuxCNC source basis: src/emc/rs274ngc/interp_python.cc, // interp_namedparams.cc, gcodemodule.cc, and src/emc/pythonplugin/python_plugin.cc @@ -8,7 +9,11 @@ extern "C" { static PyTypeObject none_type = {"NoneType"}; +static PyTypeObject module_type = {"module"}; +static PyTypeObject dict_type = {"dict"}; static PyObject none_object = {&none_type}; +static PyObject module_object = {&module_type}; +static PyObject dict_object = {&dict_type}; PyObject *PyExc_KeyError = reinterpret_cast(1); PyObject *PyExc_KeyboardInterrupt = reinterpret_cast(2); @@ -111,6 +116,10 @@ PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...) { return &none_object; } +PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *) { + return &none_object; +} + PyObject *PyObject_GetAttrString(PyObject *, const char *) { return &none_object; } @@ -187,6 +196,64 @@ int PyModule_AddObject(PyObject *, const char *, PyObject *) { return 0; } +int PyImport_ExtendInittab(_inittab *) { + return 0; +} + +PyObject *PyImport_GetModuleDict(void) { + return &dict_object; +} + +PyObject *PyImport_AddModule(const char *) { + return &module_object; +} + +int PyDict_SetItemString(PyObject *, const char *, PyObject *) { + return 0; +} + +void PyConfig_InitPythonConfig(PyConfig *config) { + if (!config) { + return; + } + config->program_name = nullptr; + config->buffered_stdio = 1; +} + +PyStatus PyConfig_SetString(PyConfig *, wchar_t **config_str, const wchar_t *value) { + if (config_str) { + *config_str = const_cast(value); + } + return PyStatus{0, nullptr, nullptr, 0}; +} + +void PyConfig_Clear(PyConfig *config) { + if (!config) { + return; + } + config->program_name = nullptr; + config->buffered_stdio = 0; +} + +PyStatus Py_InitializeFromConfig(PyConfig *) { + return PyStatus{0, nullptr, nullptr, 0}; +} + +wchar_t *Py_DecodeLocale(const char *arg, std::size_t *size) { + if (size) { + *size = arg ? std::strlen(arg) : 0; + } + return nullptr; +} + +int PyRun_SimpleString(const char *) { + return 0; +} + +const char *Py_GetVersion(void) { + return "wasm-python-disabled"; +} + int PyType_Ready(PyTypeObject *) { return 0; } diff --git a/docs/linuxcnc-wasm-shims-source-map.md b/docs/linuxcnc-wasm-shims-source-map.md index 0ad52e4..39a518e 100644 --- a/docs/linuxcnc-wasm-shims-source-map.md +++ b/docs/linuxcnc-wasm-shims-source-map.md @@ -63,9 +63,10 @@ only; it must not add CNC behavior and must not expand the temporary smoke parse `PythonPlugin` status edges for `PLUGIN_NO_SECTION`, `PLUGIN_BAD_INIFILE`, `PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, and `PLUGIN_EXCEPTION` while keeping Python execution disabled. -- The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or - `PyObject_Call`; those remain browser blockers until implemented as a - source-backed browser-safe LinuxCNC adaptation. +- The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or real + Python callable dispatch; `PyObject_Call` is an inert C API symbol only. + Browser Python execution remains blocked until implemented as a source-backed + browser-safe LinuxCNC adaptation. ## Boundaries diff --git a/test-linuxcnc-wasm-python-c-api-symbols.sh b/test-linuxcnc-wasm-python-c-api-symbols.sh index e7cab4d..69d6084 100755 --- a/test-linuxcnc-wasm-python-c-api-symbols.sh +++ b/test-linuxcnc-wasm-python-c-api-symbols.sh @@ -4,9 +4,9 @@ set -euo pipefail cd "$(dirname "$0")" # LinuxCNC source basis: this probe derives required Python C API exports from -# RS274 Python/remap sources in src/emc/rs274ngc, including -# interp_namedparams.cc, interp_o_word.cc, interp_python.cc, interp_remap.cc, -# rs274ngc_pre.cc, and the py*/module binding sources. +# RS274 Python/remap sources in src/emc/rs274ngc and from +# src/emc/pythonplugin/python_plugin.cc initialization/configuration paths, +# while compiling only the RS274 binding objects that need wasm-safe symbols. linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then @@ -58,6 +58,10 @@ linuxcnc_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-f src/emc/rs274ngc/gcodemodule.cc) mapfile -t linuxcnc_sources <<<"$linuxcnc_source_output" python_binding_sources=("${linuxcnc_sources[@]:6}") +python_plugin_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ + src/emc/pythonplugin/python_plugin.cc) +mapfile -t python_plugin_sources <<<"$python_plugin_source_output" +python_token_sources=("${linuxcnc_sources[@]}" "${python_plugin_sources[@]}") common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh) mapfile -t common_flags <<<"$common_flag_output" @@ -143,14 +147,14 @@ source_symbols_signature="$build_dir/python-c-api.source-symbols.signature" source_symbols_next_signature="$build_dir/python-c-api.source-symbols.signature.next" { printf 'SOURCE_SYMBOL_RULE_VERSION=1\n' - printf 'LINUXCNC_SOURCES:\n' - printf '%s\n' "${linuxcnc_sources[@]}" + printf 'LINUXCNC_TOKEN_SOURCES:\n' + printf '%s\n' "${python_token_sources[@]}" printf 'SOURCE_STATS:\n' - stat -c 'SOURCE=%n:%s:%y' "${linuxcnc_sources[@]}" + stat -c 'SOURCE=%n:%s:%y' "${python_token_sources[@]}" } > "$source_symbols_next_signature" if [[ ! -f "$source_symbols_signature" || ! -f "$build_dir/linuxcnc.py-tokens" ]] || ! cmp -s "$source_symbols_signature" "$source_symbols_next_signature"; then - rg -o 'Py[A-Za-z0-9_]+' "${linuxcnc_sources[@]}" \ + rg -o 'Py[A-Za-z0-9_]+' "${python_token_sources[@]}" \ | sed 's/^.*://' \ | LC_ALL=C sort -u \ > "$build_dir/linuxcnc.py-tokens" @@ -204,12 +208,14 @@ awk ' $0 == "PyModuleDef_HEAD_INIT" { next } $0 == "Py_TPFLAGS_DEFAULT" { next } $0 == "PyCFunction" { next } + $0 == "PyConfig" { next } $0 == "PyGetSetDef" { next } $0 == "PyMemberDef" { next } $0 == "PyMethodDef" { next } $0 == "PyModuleDef" { next } $0 == "PyMODINIT_FUNC" { next } $0 == "PyObject_New" { next } + $0 == "PyStatus" { next } $0 == "PyInit_emccanon" { next } $0 == "PyInit_gcode" { next } $0 == "PyInit_interpreter" { next }