KZT, refactor: split loader object tracking and patch planning#3
Draft
LaurenIsACoder wants to merge 23 commits into
Draft
KZT, refactor: split loader object tracking and patch planning#3LaurenIsACoder wants to merge 23 commits into
LaurenIsACoder wants to merge 23 commits into
Conversation
4060d91 to
45e8f9d
Compare
Signed-off-by: Sun Haiyong <sunhaiyong@zdbr.net>
99f8a9a to
81ab7db
Compare
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
Signed-off-by: zqz <2264460073@qq.com>
Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
a24da1a to
bb8e12e
Compare
Document the KZT loader refactor as a staged design rather than a progress report. The plan explains why loader synchronization needs clearer object identity, memory-based metadata parsing, recorded patch decisions, and a stable event boundary before the private glibc hook can be reduced. The series starts with registry and parser boundaries, then moves GOT patching, dependency loading, lazy binding, dlopen/dlsym/dlclose, and loader notifications onto guest-owned state.
Stage 1 creates a registry for guest ELF object identity. The loader callback records object name, load range, base address, and dynamic table address before forwarding to the legacy path. Later stages can use this as the address-to-object boundary without changing current glibc hook behavior.
Stage 2 adds a Dynamic Table parser that reads metadata from guest memory. The parser materializes strings, symbols, relocations, and version data from runtime dynamic information, with range validation before derived tables are walked. The legacy file parser remains active for comparison, so differences are visible before the section-header dependency is removed.
Stage 3 turns GOT updates into explicit patch decisions. Each decision records the guest object, relocation, symbol/version, old target, old owner, selected bridge, target source, and reason before a slot is patched. The selected target is unchanged in this patch; the value is the observable boundary needed for later resolver changes.
Stage 4 starts observing whether the current GOT value identifies the guest object that owns the resolved target. The shadow path classifies guest-owner lookup results and compares them with the existing maplib-selected bridge, without changing the selected target. This makes guest-owner resolution reviewable before it can replace the global maplib lookup.
Complete Stage 4 by allowing successful guest-owner lookup to select the native wrapper bridge directly. When the current GOT target belongs to a known guest object and resolves to a wrapper, the planner records a guest-owner decision and skips the maplib reparse for that slot. Failed probes keep the existing maplib fallback. Lazy binding is left to the next stage.
Stage 5 lets the guest loader own ordinary guest dependencies. KZT now registers wrappers only for objects already reported by the guest loader. It stops expanding guest RPATH/RUNPATH, recursively loading guest DT_NEEDED entries, and side-loading guest libraries through LoadNeededLibs(). AddNeededLib() remains for native wrapper registration and wrapper host dependencies.
Stage 6 moves lazy JUMP_SLOT policy out of the generic relocation path. Lazy slot classification, resolver metadata, deferred patch decisions, and first-call resolver actions now live behind guestlazy helpers. Behavior is intended to stay unchanged. The new boundary prepares the later model where guest ld.so binds first and KZT replaces the slot after binding.
Prepare Stage 7 by splitting common guest dl helper calls out of the wrapper entry points. The patch names the handle lookup, RunFunctionWithState() forwarding, and guest dlclose forwarding paths without changing behavior. The following patches use this boundary to make guest loader results authoritative.
Stage 7 starts making dlopen handles follow guest loader ownership. Wrapper state is keyed by handles returned by the guest loader, including reused handles, reopen-after-close, self handles, and closed-handle deactivation. The compatibility path no longer reloads guest objects behind the guest loader, and fixed-buffer inputs are bounded before copying.
Stage 7 moves dlsym and dlvsym authority to guest-owned lookup results. RTLD_DEFAULT, RTLD_NEXT, wrapped link_map lookups, versioned requests, and error reporting are routed through named helpers before local wrapper metadata is used as a compatibility fallback. Local symbols only replace successful guest results with native bridge addresses.
Complete Stage 7 by making close and dl information queries follow guest loader state. dlclose releases guest loader references, while dladdr, dladdr1, dlinfo, and dlvsym prefer guest link_map and lookup results when available. Local metadata remains a compatibility fallback and failure paths avoid stale handle-table state or duplicate close accounting.
Start Stage 8 by routing loader notifications through a registry event dispatcher. The glibc callback now builds an immutable link_map event snapshot and submits it to shared object registration and legacy compatibility logic. This separates event capture from object synchronization, so future event sources can reuse the same registry path.
Stage 8 makes r_debug the normal loader notification path and keeps the private glibc hook as a version-gated fallback source. Both sources feed the same registry event dispatcher. The r_debug path validates loader state and link_map input before registration, while fallback failures stay local to the fallback source.
bb8e12e to
99791eb
Compare
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.
Summary
This draft PR carries the staged KZT loader refactor through stages 1 to 8.
The series documents the overall design, introduces guest object identity,
adds in-memory Dynamic Table parsing, records GOT patch decisions, prefers
guest-owner wrapper targets where possible, stops side-loading ordinary guest
dependencies, isolates lazy binding policy, makes guest dl APIs authoritative,
and narrows the private glibc hook into a fallback loader-event source.
The current local head has passed a debug build and the focused KZT loader
regression script. A full
dEQP-EGL.*A/B run from the earlier convergencepoint matched the pre-refactor baseline over 4111 EGL CTS cases; the latest
head should still go through a final full CTS A/B comparison before review is
considered complete.
中文详细说明
中文详细说明
设计意图
这组补丁的目标不是一次性重写整个 loader,而是把原来耦合在一起的 KZT
loader 路径拆成可验证、可替换的边界。
旧方案把 guest loader 通知、ELF 文件解析、maplib 重解析、wrapper 查找、
GOT 修改、lazy binding、
dlopen/dlsym/dlclose状态,以及 glibc 私有hook 同步流程混在一起。核心问题是:guest loader 已经知道对象和绑定结果,
但 KZT 仍经常通过文件、全局符号范围或 maplib 重新推导一次,导致对象身份、
目标来源和引用生命周期都不清晰。
本次重构按阶段解决这些问题:
link_map/l_ld解析运行时 ELF 元数据。dlopen/dlsym/dlclose等 API 返回结果成为权威。当前补丁组织
当前系列按 review 粒度整理为 19 个提交:
是否符合设计预期
总体符合预期。
这组补丁已经把 KZT 从“自己重做 guest loader 决策”推进到“观察 guest loader
结果,并在明确边界后做 wrapper 替换”的方向。对象身份、Dynamic Table 解析、
GOT patch 决策、guest-owner wrapper 选择、dependency loading、lazy binding、
dl API 语义和 loader event source 都已经拆出明确边界。
Stage 8 仍是过渡形态。glibc 私有 hook 还没有完全移除,但它已经不再承担整个
装载同步流程,只作为 fallback event source 上报 link_map 事件。后续可以在
这个边界后替换成
r_debug、mprotect、QEMU loader/mmap event,或更小的版本隔离 fallback hook。
已解决的原有缺陷
已解决主要结构缺陷:
l_ld的内存 parser。symbol、version、old target、old owner、new bridge 和 reason。
GOT 当前值所属 guest object。
DT_NEEDED会被 KZT 旁路加载;现在交回 guest loader。dlopen/dlsym/dlclose有合成句柄、重复引用计数和旁路 reload;现在 guest loader 返回结果成为权威。
最近收敛的修复
最新本地提交补充了 Stage 7/8 收敛后的健壮性修复:
RTLD_DEFAULT先询问 guestdlsym,只有 guest miss 后才走兼容 fallback。dlsym/dlvsym先由 guest link_map 决定符号是否存在,成功后才允许 local native bridge 替换返回值。
dlvsym保持版本化 guest lookup 路径,缺失 metadata 时不再退回普通非版本
dlsym。dladdr1(RTLD_DL_LINKMAP)fallback、关闭后的 handle等路径返回普通 dl error,而不是 assert 或写入无效结果。
dlopen会持有 guest loader 引用,使后续dlclose与 guest 侧引用计数配对。
dlprivatehandle 表扩容时初始化所有并行数组,避免未使用槽位残留状态。异常 env/hook/reg 不再读取坏寄存器。
新风险点
本次修改也引入或暴露了需要继续关注的风险:
RTLD_NEXT、RTLD_DEFAULT、RTLD_NOLOAD、dlmopennamespace、versioned symbol、dladdr1等边角语义仍需要更多真实应用覆盖。
方案更清晰,但仍是高风险区域。
pattern validation 和 missing-source fallback,但不同 glibc 版本仍可能需要
额外适配。
dEQP-EGL.*,但最新 HEAD 还需要最终整体 CTS 对比;GLX、Vulkan、Wine 和真实应用路径仍需要后续覆盖。
已完成验证
最新本地 HEAD 已完成:
此前收敛过程中的完整 EGL CTS A/B 对比:
结果:
后续工作
后续工作主要有三类:
r_debug通知、RELROmprotect、QEMU mmap/loaderevent 等方案,逐步减少 glibc 私有 hook 依赖。
English Detailed Description
English Detailed Description
Design Intent
This series does not try to rewrite the whole loader in one step. It splits
the existing KZT loader path into explicit, testable, and replaceable
boundaries.
The old path mixed guest loader notification, ELF file parsing, maplib
re-resolution, wrapper lookup, GOT patching, lazy binding, dl API bookkeeping,
and the private glibc hook synchronization path. The central problem was that
the guest loader already knew the object and binding result, while KZT often
derived the target again through files, global symbol ranges, or maplib. That
made object identity, target provenance, and handle lifetime difficult to
reason about.
The staged refactor addresses this by:
link_map/l_ld;value;
dlopen/dlsym/dlcloseresults authoritative;Current Patch Organization
The current series is organized into 19 reviewable patches:
Design Conformance
The current implementation matches the refactor direction.
The code now has explicit boundaries for guest object identity, runtime dynamic
metadata parsing, GOT patch decisions, guest-owner wrapper lookup, dependency
loading, lazy binding, guest dl API authority, and loader event sources. This
moves KZT away from redoing guest loader decisions and toward observing guest
loader results before applying wrapper replacement.
Stage 8 is still transitional. The private glibc hook has not been fully
removed, but it has been narrowed to a fallback event source. It reports
link_mapevents and no longer owns the whole synchronization flow. Thatboundary is the point where future
r_debug, mprotect, QEMU loader/mmap event,or smaller version-isolated fallback sources can be plugged in.
Defects Addressed
The series addresses the main structural defects of the old scheme:
Headers.
instead of redoing global maplib resolution first.
DT_NEEDEDdependencies are no longer side-loaded by KZT.duplicated reference counting, and side reload logic.
whole loader sync path.
Recent Hardening
The latest local commit adds final stage 7/8 hardening:
RTLD_DEFAULTasks guestdlsymfirst and uses compatibility fallback onlyafter a guest miss.
dlsym/dlvsymask the guest link_map to decide symbolexistence before replacing a successful result with a local native bridge.
dlvsymstays on the versioned guest lookup path and no longer falls back toa plain non-versioned
dlsymwhen local metadata is missing.dladdr1(RTLD_DL_LINKMAP)fallback, and closed-handlepaths report ordinary dl errors instead of asserting or writing invalid data.
dlopenretains the guest loader reference so laterdlclosecalls are paired on the guest side.dlprivatehandle-table growth initializes all parallel arrays.failure disables KZT, and invalid env/hook/reg state no longer reads a bad
guest register.
New Risks
The refactor also introduces or exposes risks that still need attention:
as
RTLD_NEXT,RTLD_DEFAULT,RTLD_NOLOAD,dlmopennamespaces,versioned symbols, and
dladdr1still need more real-application coverage.This is clearer than the old model but remains a sensitive area.
pattern validation, and missing-source fallback reduce the risk, but new glibc
layouts may still require additional adaptation.
dEQP-EGL.*A/B run has matched during convergence, but the latesthead still needs a final full CTS comparison. GLX, Vulkan, Wine, and real
application paths also need follow-up coverage.
Validation Completed
Latest local head:
Earlier full EGL CTS A/B comparison during convergence:
Result:
Follow-up Work
Follow-up work is focused on three areas:
r_debug, RELROmprotect, QEMUmmap/loader events, or a smaller version-isolated fallback hook.