Conversation
…/ohos-node)
- src/, build/, package.json: switch from electerm desktop source to the
electerm-web codebase (same base as electerm-android) — pure-node backend,
no electron APIs
- build/web/build.mjs: vite frontend + esbuild backend bundle into
entry/src/main/resources/resfile/electerm (readable directly at runtime,
no extraction)
- scripts/prepare-node.sh: download hqzing/ohos-node v24.19.0, strip,
install as entry/libs/arm64-v8a/libnode.so
- entry module rewritten as pure ArkTS app:
- Index.ets: Web component + start node backend via
childProcessManager.startNativeChildProcess + HTTP poll + loadUrl
- cpp/node_launcher.c: native child entry that execv()s libnode.so
(memfd fallback if the lib dir is noexec), full boot log
- cpp/node_ctl.c: NAPI kill(pid) for EntryAbility.onDestroy
- scripts/build-web-app.sh: unsigned hvigor assembleApp + hap-sign-tool
signing, entry module only (no web_engine)
- .github/workflows/build-web.yml: CI for dev2 branch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d artifacts - cdc36aa accidentally committed the whole build/ directory deletion and never included build/web/{build,vite.web}.mjs (CI failed: MODULE_NOT_FOUND); restored build/{bin,vite,web} + logos from electerm-android / transcript - untrack entry/.cxx (CMake/Ninja), entry/oh_modules, local.properties and ignore them going forward Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tep) The dir is gitignored and was only present locally from an earlier manual run, so CI's vite build failed on unresolved ../electerm-react imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bare entry/src/main/resources/rawfile ignore rule was for the old electron build's generated rawfile content; dev2 generates nothing there and the Web component loads loading.html from $rawfile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s resources Device log (cloud debug) showed the native child spawn worked (cterm:Native_libnode_launcher0, pid 56633) but exited with the launcher's code 40 (script missing): the packaged HAP contains resources/resfile/electerm (plural), Index.ets built .../entry/resource/... - resolveScriptPath(): try both bundleCodeDir shapes, verify with fs.stat - switch Index logging to hilog (console.info is filtered in release — that's why no electerm.Index lines appeared in the device log) - on boot failure, dump node-boot.log tail via hilog for cloud-debug - EntryAbility.onDestroy: actually kill the child (BackendManager), the old LocalStorage lookup always failed - launcher: retry boot log via /data/storage/el2/base junction if the parent's filesDir path is not mounted in the child namespace - build-web-app.sh: extract SDK version with python (BSD sed lacks \+, broke local builds with SDK component missing) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reen The 13:25 device capture started after spawn and showed only connection- refused polling — child fate unknown, node-boot.log unreachable from the cloud-debug mirror. Make the next round self-diagnosing: - dataDir now uses /data/storage/el2/base/files/electerm-data when the junction exists (mounted in every app process incl. the native child, unlike the parent's sandbox-style filesDir string) - while polling, the overlay shows the latest node-boot.log line (~2s), so a stuck/failing boot is readable from the screen mirror alone - on timeout the overlay shows the last log line (or 'no node-boot.log — child never ran'), and the 800-char tail goes to hilog (electerm.Index) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ode= param) Device round 2: script path fixed (exit 40 gone) but launcher exited 41 — none of the el1-junction candidates matched in the native child's mount namespace. Now: - launcher logs every step to native hilog (electerm.launcher, via libhilog_ndk.z.so) so diagnosis no longer depends on pulling the boot log - node binary candidates: parent-provided node= param (verbatim), dladdr() on Main() (authoritative load path), every arm64 .so dir visible in /proc/self/maps, then the el1 junction layouts - Index.ets logs the libs dir listing (parent-side proof whether the installer extracted the 92MB libnode.so) and passes node= through Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ument execv(libnode.so) and memfd execveat both failed on device (exit 42) — suspected XPM code-integrity / noexec on the bundle mount. New ladder step 6a: exec the system's own signed ld-musl loader with the node binary as its program argument; the loader maps the binary itself (PROT_EXEC mmap, same as dlopen which provably works for app .so files in this process). Also logs node stat() mode/size and the mountinfo options of the containing mount, so noexec/code-integrity is visible in node-boot.log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device test: execv errno=13 EACCES — even loader-exec. XPM only runs signed code. Build now embeds a code signature in the node ELF via the official binary-sign-tool (openharmony/developtools_hapsigner dist, sha256-pinned download): - cert mode with the APP signing identity when KEYSTORE_PASSWORD/ KEY_PASSWORD are set (CI), self-sign fallback (local) - packed-HAP verification step fails the build if the signature is lost during packaging - entry build-profile: nativeLib.debugSymbol.strip=false so the non-alloc signature section is never stripped - libnode.so is signed in place; restore locally with git checkout -- entry/libs/arm64-v8a/libnode.so Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third device failure with errno=13: direct execv, exec via the signed system loader, and memfd execveat are ALL refused for the app child — even with the binary cert-signed with the APP identity. XPM blocks the exec syscall for app processes, not the signature. New strategy 1 (nodejs-mobile / WineHua pattern): dlopen the bundled node binary and call its exported embedder entry int node::Start(int argc, char *argv[]) (_ZN4node5StartEiPPc) on a dedicated 32MB-stack thread. OHOS musl's load_library has no PT_INTERP/DF_1_PIE rejection, so the PIE executable loads as a shared object. dlopen is how app code legitimately gets mapped executable (nativespawn loaded this very launcher). Falls back to the exec ladder only if dlopen/dlsym cannot start. Also: chdir(dataDir) before running node; the failure overlay now shows the last 8 boot-log lines so the whole dlopen → dlsym → Start ladder is readable from the screen alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th lost its dir Device log (temp/2026-08-28 15_03_43.log) shows: - node stat mode=100644: hvigor installs libs without the execute bit, execve returns EACCES for that plain reason — the el1 mount is rw,nosuid,nodev,noatime, NOT noexec - loader execv errno=2: findLoader sliced the maps line at 'ld-musl', dropping /lib/ — exec'd a bare filename Fixes: - strategy 2: copy the signed binary into the app-owned el2 data dir (data/bin/node), chmod 0755, exec the copy — size-checked, copied once, kept across restarts - findLoader takes the full last path token of the maps line - chmod failures are logged everywhere now Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device log (temp/2026-08-28 16_00_16.log): dlopen succeeded, node::Start resolved and entered, then the node thread died of signal 31 (SIGSYS, si_code=1 SYS_SECCOMP) — the app sandbox seccomp filter kills syscalls V8 probes at startup (membarrier / pkey_mprotect / perf_event_open class). The signal was delivered to a handler (OHOS DfxSignalHandler), so it is catchable: install our own SIGSYS handler that logs the trapped syscall number (si_syscall), advances PC past the 4-byte svc instruction, and sets x0 = -ENOSYS. V8/uv treat those probes as optional and degrade gracefully on ENOSYS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device: SIGSYS shim worked — node ran deep into libuv and died on assert(fd > STDERR_FILENO) in uv__close. That assert fires when a uv handle ends up on fd 0/1/2, i.e. a stdio slot was closed and reused. - setupStdioForNode(): snapshot the fd table the child was born with into the boot log, then close 0/1/2 and rebuild deterministically (0=/dev/null, 1=2=boot log, log reopened on slot 1) — the nodejs-mobile pattern; no closed or aliased stdio slots can exist - installCrashMarkers(): SIGABRT/SEGV/BUS/ILL/FPE handlers log which signal killed the child before re-raising — an assert-abort now leaves a marker in the boot log instead of a silent end Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Numbers taken from the SDK's asm-generic/unistd.h (aarch64) — the log now reads 'syscall 283 (membarrier)' instead of a bare number. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The nativespawn child runs under a stricter seccomp filter than the app itself — node's libuv aborts there (SIGABRT; the fd>-2 assert in uv__close fires on uv_loop_init's error path when epoll-class syscalls come back ENOSYS from the SIGSYS shim). The MAIN app process runs libuv-class loops of its own (NETSTACK/curl), so node now runs there, the electron-harmony pattern: - libnode_ctl.so startBackend(entryParams): dlopen libnode.so + node::Start on a 32MB-stack pthread in the app process, SIGSYS shim + crash markers, same node-boot.log channel (tag electerm.embed) - Index.ets tries in-process first, falls back to startNativeChildProcess - BackendManager: skip kill for in-process mode (it IS the app) - node-boot.log hilog dump at failure: 800 → 2400 chars Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ilog The in-process run killed the whole app on node's first fatal signal (app closes instantly, no overlay, no dump — the capture window missed it entirely). Now: - fatal signals print the marker to hilog IMMEDIATELY (electerm.embed 'fatal: signal N on tid T') — survives the process - if the signal hit the node thread, park that thread forever instead of dying: the app stays on the loading screen and the 8-line overlay shows the boot-log tail - boot log truncated per attempt so the overlay reflects this run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cloud-debug hilog export delivers the OLDEST buffered lines and system noise (~1600 lines/s) flushes the ring long before the 90s boot deadline — every evidence dump so far landed outside the exported window. Now the tail (last 1100 chars) is re-dumped to hilog every ~2s while waiting, so a copy of the ladder + crash marker always sits inside whatever window survives. Failure overlay: 8 → 14 lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In the child we rebuilt stdio, so assert/abort text reached the log; in embed mode node's stderr was the app's fd 2 (/dev/null) — abort messages vanished, leaving only the crash marker. Redirect 1/2 onto the boot log (the app runtime logs via hilog, not stdio) so the next run shows the actual assertion text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g truncation Device evidence (17:23:49): in-main-process run got all the way to node::Start on tid 30146; the ONLY seccomp-trapped syscall is io_uring_setup (425) → ENOSYS, which libuv handles gracefully (uv__iou_init returns on ringfd<0). Signal 6 abort follows inside node/V8 startup — but its stderr text never reached us: hilog truncates messages at ~140 bytes and ArkWeb stdout junk buries the file tail. - pipe fd 1/2 to a reader thread that logs every line to the boot log AND hilog ([io] prefix, electerm.embed) — assert/abort text arrives live, un-truncated - UV_USE_IO_URING=0 (the one trapped syscall, now skipped up front) - tail dumps split into 110-char chunks, one hilog line each Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The libuv assert (fd > STDERR_FILENO, uv__close core.c:646) names the dying function but not its caller; static analysis of every startup uv__close path (loop init fail paths, io_uring init, signal pipes) came back clean, so make the next device run self-diagnosing: - crashMarkerHandler: backtrace() up to 24 frames, each annotated with dladdr symbol+offset (libnode.so is unstripped — real symbol names are resolvable) and written to the boot log AND hilog, plus full backtrace_symbols_fd dump to the boot log. - Before the stdio pipe: fstat fds 0/1/2 and open /dev/null onto any EBADF fd — a closed std fd at app spawn means pipe() returns fd 0/1, dup2(x,x) no-ops, close() re-closes it, and every later cleanup closes a std fd, tripping exactly this assert. Repair + log the fd layout. - node_ctl built with -fno-omit-frame-pointer so backtrace() walks our frames reliably. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e of the uv__close assert Device backtrace (b3ba359 run, symbolized against the unstripped libnode.so) finally named the abort caller: uv__close_nocheckstdio ← uv__close (assert fd > STDERR_FILENO) ← uv__iou_init fail path (linux.c:624) ← uv__platform_loop_init ← uv_loop_init (loop.c:79) ← node::tracing::LegacyTracingAgent ctor ← node::tracing::Agent::CreateDefault ← node::V8Platform::Initialize ← node::InitializeOncePerProcessInternal ← node::Start Chain of events: io_uring_setup is seccomp-trapped (SIGSYS), the shim resumed with x0 = -ENOSYS (-38); OHOS musl's syscall() passes raw x0 through WITHOUT upstream musl's __syscall_ret errno-translation, so uv__iou_init received ringfd = -38, passed its 'if (ringfd == -1) return;' guard, failed mmap/epoll_ctl on the bogus fd, and its cleanup called uv__close(-38) → libuv assert → SIGABRT on the node thread. Fix: return exactly -1 (the universal failure value every caller checks) and set errno = ENOSYS (TLS store, async-signal-safe) in BOTH shims — node_ctl.c (main process) and node_launcher.c (native child, which died of the same assert). The tracing-agent loop is the FIRST libuv loop node creates, so this fired before any script ran. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Device log 18_11_18 (521ce7a): the uv__close assert is GONE (the -1 shim fix worked) but the node thread now dies of SIGSEGV inside strlen called from printf machinery, with the interrupted frames being uv__io_uring_setup <- uv__iou_init — i.e. the crash is INSIDE our own SIGSYS handler: its first-sight logging used logWrite (vsnprintf + OH_LOG_Print), which take libc locks; the second seccomp trap fired while the node thread held one, so the handler's own formatting crashed. Classic async-signal-safety violation. - sigsysHandler (node_ctl.c + node_launcher.c): logging is now async-signal-safe — fixed strings + manual decimal into a stack buffer, write(2) only, to the boot log and to fd 2 (the main-process stdio pipe, where the reader thread relays it to hilog in normal context). Per-syscall dedup via a 512-bit bitmap. - stdioReaderThread: filter ArkWeb/chromium framework lines (nweb_, render_, updater, cloud-control, compositor, …) so node's console output, asserts and stack traces are not buried — the on-screen boot overlay shows the boot log tail and was 100% ArkWeb spam last run. - Generated backend entry (build/web/build.mjs): append [backend] milestones directly to node-boot.log (entry running / bundle imported / uncaughtException / unhandledRejection / exit code) — direct file writes bypass stdout entirely, immune to fd-level noise. - CI: setup-java v4 -> v5 (v4 is deprecated). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.