You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every #[address(...)] lowers to an absolute literal, so the generated code assumes the target module loaded at its preferred image base. That's fine for a 32-bit binary with a fixed base. It's wrong for any x64 target built with /DYNAMICBASE, which is the default — the loader relocates the module and every generated call site, singleton accessor, and extern-value read points somewhere else.
The language can't express what an address is relative to. pyxis.toml carries a name and a pointer size, and nothing else, so the compiler can't tell an absolute VA from an RVA against a module that happened to load where it wanted to.
The Just Cause 3 definitions in pyxis-defs are x64, and their addresses are preferred-base VAs — 0x140_091_C10 is the default 0x140000000 image base plus the RVA. Those bindings only work against an unrelocated module.
Module/symbol externs #36 (module/symbol externs) is the other half. Once an address is relative to something named, resolving by module and exported symbol becomes another strategy for filling the same slot rather than a separate feature.
Expected vs actual
Expected: generated bindings work against the module as loaded. Actual: they work only if the loader left the module at its preferred base.
Proposed approach
Speculative, and recorded to open the discussion rather than close it.
Add image_base to pyxis.toml. Keep addresses written exactly as the disassembler shows them. Have the backends emit base() + (addr - image_base), where base() is a hook the consumer supplies. Default that hook to the declared image_base and current output stays byte-identical, so this can land without breaking anyone.
Open: where the hook lives. It could be an extern the generated code declares and the consumer defines, a BuildOptions flag next to public_addresses, or something else. Whether the manifest should name the module itself is also open, and that's where this starts to overlap #36.
Two alternatives worth weighing. Leave the manifest alone and make relocation entirely a consumer problem — document that public_addresses constants are preferred-base VAs and let consumers rebase them. Or write RVAs in the source directly and treat today's absolute form as a legacy spelling.
Problem
Every
#[address(...)]lowers to an absolute literal, so the generated code assumes the target module loaded at its preferred image base. That's fine for a 32-bit binary with a fixed base. It's wrong for any x64 target built with/DYNAMICBASE, which is the default — the loader relocates the module and every generated call site, singleton accessor, and extern-value read points somewhere else.The language can't express what an address is relative to.
pyxis.tomlcarries a name and a pointer size, and nothing else, so the compiler can't tell an absolute VA from an RVA against a module that happened to load where it wanted to.Context
Function bodies transmute the literal directly:
pyxis/src/backends/rust/values.rs
Lines 299 to 317 in f95ea87
which produces:
pyxis/codegen_tests/output/rust/freestanding_functions.rs
Lines 13 to 19 in f95ea87
The project manifest has nowhere to declare a base:
pyxis/src/config.rs
Lines 47 to 51 in f95ea87
The Just Cause 3 definitions in pyxis-defs are x64, and their addresses are preferred-base VAs —
0x140_091_C10is the default0x140000000image base plus the RVA. Those bindings only work against an unrelocated module.Related:
public_addresses) established that addresses are something consumers reach for, and put the option on the build script rather than the project. The same split applies here.externs #36 (module/symbol externs) is the other half. Once an address is relative to something named, resolving by module and exported symbol becomes another strategy for filling the same slot rather than a separate feature.Expected vs actual
Expected: generated bindings work against the module as loaded.
Actual: they work only if the loader left the module at its preferred base.
Proposed approach
Speculative, and recorded to open the discussion rather than close it.
Add
image_basetopyxis.toml. Keep addresses written exactly as the disassembler shows them. Have the backends emitbase() + (addr - image_base), wherebase()is a hook the consumer supplies. Default that hook to the declaredimage_baseand current output stays byte-identical, so this can land without breaking anyone.Open: where the hook lives. It could be an extern the generated code declares and the consumer defines, a
BuildOptionsflag next topublic_addresses, or something else. Whether the manifest should name the module itself is also open, and that's where this starts to overlap #36.Two alternatives worth weighing. Leave the manifest alone and make relocation entirely a consumer problem — document that
public_addressesconstants are preferred-base VAs and let consumers rebase them. Or write RVAs in the source directly and treat today's absolute form as a legacy spelling.