Wednesday, April 20, 2022

Python on Webassembly Part II: Rolling your own

Getting to compile Python for WASM/WASI, funny enough the first step is installing the Python-based tools that make "wasienv":

$ pip install wasienv
Processing /home/user/.cache/pip/wheels/15/72/ed/35789dff12f6ca3d9cd98454519aff343d102da520b98326dd/wasienv-0.5.4-py3-none-any.whl
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (from wasienv) (2.22.0)
Installing collected packages: wasienv
Successfully installed wasienv-0.5.4

Now following instructions from https://github.com/wapm-packages/python/blob/master/README.rst, and tinkering a bit with python cmake options...

$ git clone --filter=blob:none https://github.com/wapm-packages/python.git
$ mkdir -p python/wasi/install && cd python/wasi
$ wasimake cmake -DCMAKE_INSTALL_PREFIX:PATH="$(realpath install)" -DUSE_SYSTEM_LIBRARIES=OFF ..
-- The ASM compiler identification is unknown -- Found assembler: /home/agustin/bin/wasicc -- Warning: Did not find file Compiler/-ASM ... -- Configuring done -- Generating done -- Build files have been written to: /home/agustin/wasm/python/python-wasi

Ready to make it are we?

$ make -j7
[  0%] Built target extension_testcapi
[  5%] Built target pgen
[  6%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/listobject.c.obj
[  6%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/longobject.c.obj
[  7%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/memoryobject.c.obj
[  7%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/moduleobject.c.obj
[  7%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/methodobject.c.obj
[  7%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/object.c.obj
[  7%] Building C object CMakeBuild/libpython/CMakeFiles/_freeze_importlib.dir/__/__/Python-3.6.7/Objects/obmalloc.c.obj
In file included from :1:
/home/agustin/.local/lib/python3.8/site-packages/wasienv/stubs/preamble.h:30:9: warning: 'ESHUTDOWN' macro redefined [-Wmacro-redefined]
#define ESHUTDOWN 0

...

6 warnings generated.
[ 42%] Linking C executable _freeze_importlib
[ 52%] Built target _freeze_importlib
[ 52%] Generating ../../../Python-3.6.7/Python/importlib_external.h, ../../../Python-3.6.7/Python/importlib.h
cannot open '/home/user/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py' for reading
make[2]: *** [CMakeBuild/libpython/CMakeFiles/libpython-static.dir/build.make:64: ../Python-3.6.7/Python/importlib_external.h] Error 1
make[1]: *** [CMakeFiles/Makefile2:1179: CMakeBuild/libpython/CMakeFiles/libpython-static.dir/all] Error 2
make: *** [Makefile:141: all] Error 2

But the file is there, what magic is forbidding freeze_importlib from opening it? Let's check with strace...

$ strace ./_freeze_importlib /home/user/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py /home/user/wasm/python/Python-3.6.7/Python/importlib_external.h
execve("./_freeze_importlib", ["./_freeze_importlib", "/home/user/wasm/python/Python"..., "/home/user/wasm/python/Python"...], 0x7ffd0490d690 /* 58 vars */) = 0
...
access("/home/user/bin/wasirun", R_OK) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fed289eaa10) = 69585
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGINT, {sa_handler=0x55d072a77480, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fed28a300c0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fed28a300c0}, 8) = 0
wait4(-1, cannot open '/home/user/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py' for reading (errno=44)
fopen: No such file or directory

Looks like the binary is wrapped by "wasirun", I guess to run it inside a virtualroot sandbox, and some required paths are not visible there.

Time to debug...

"DEBUG" can be enabled by tweaking wasirun/tools.py:

Now the same call to make dumps what wasienv is doing:

[ 52%] Generating ../../../Python-3.6.7/Python/importlib_external.h, ../../../Python-3.6.7/Python/importlib.h
wasienv run process: wasmer run --dir=. --enable-all /home/user/wasm/python/python-wasi/CMakeBuild/libpython/_freeze_importlib.wasm -- /home/user/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py /home/user/wasm/python/Python-3.6.7/Python/importlib_external.h
cannot open '/home/agustin/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py' for reading (errno=44)
fopen: No such file or directory

The problem is that only current dir "." is exposed in the WASM context. Let's call wasmer run directly with dir=/

$ wasmer run --dir / --enable-all /home/user/wasm/python/python-wasi/CMakeBuild/libpython/_freeze_importlib.wasm -- /home/user/wasm/python/Python-3.6.7/Lib/importlib/_bootstrap_external.py /home/user/wasm/python/Python-3.6.7/Python/importlib_external.h
error: failed to run `/home/user/wasm/python/python-wasi/CMakeBuild/libpython/_freeze_importlib.wasm`
│   1: RuntimeError: indirect call type mismatch
           at _PyCFunction_FastCallDict (_freeze_importlib.wasm[1839]:0x15c040)
           at _PyObject_FastCallDict (_freeze_importlib.wasm[658]:0x86973)
           at callmethod (_freeze_importlib.wasm[737]:0x905ee)
           at _PyObject_CallMethodId (_freeze_importlib.wasm[736]:0x90451)
           at flush_std_files (_freeze_importlib.wasm[2975]:0x279fee)
           at Py_FinalizeEx (_freeze_importlib.wasm[2980]:0x27afa9)
           at Py_Finalize (_freeze_importlib.wasm[2979]:0x27aeed)
           at main (_freeze_importlib.wasm[42]:0x4a08)
           at __original_main (_freeze_importlib.wasm[7190]:0x69da7e)
           at _start (_freeze_importlib.wasm[30]:0x3ca8)
╰─▶ 2: bad_sig

Now the files are reachable, and feeze_importlib is crashing.

We need better debug information...

Meet "The Pain of Debugging WebAssembly".

1 comment: