2 unstable releases
0.2.0 | Jun 16, 2023 |
---|---|
0.1.0 | Apr 13, 2023 |
#872 in Procedural macros
21KB
380 lines
mutself
Create self-mutating executables.
Purpose
// TODO
Usage
mutself::mutself! {
DATA = "alice"
}
fn main() {
if let Some(arg) = std::env::args().nth(1) {
println!("Hello {}", &arg);
mutself("new.exe", Some(&arg)).unwrap();
} else {
println!("Hello {}", &*DATA);
}
}
Running our executable gives :
$ cargo run
Hello alice
Now with an argument :
$ cargo run -- bob
Hello bob
# This creates another executable named 'new.exe'
$ new
Hello bob
Yes, data size can be changed.
$ cargo run -- someverylongstringthatwouldntfitinsidethespace
How ?
The library parses the executable it's running in and changes values before dumping a new executable.
In depth
Simply put : an abuse of the #[link_section = ".custom"]
attribute. There is no assembly patching or anything. The
data entries in the mutself!
macro are stored in a custom section in the executable. The code that run only has a
pointer to the top of the section (that pointer always stays valid because the custom section will always be at the
same place in virtual memory). All entries in the section are referenced relatively to this pointer.
The layout in memory is like so (factoring out alignment) :
#ENTRY0 size (usize) <-- hardcoded pointer
#ENTRY0
#ENTRY1 size (usize)
#ENTRY1
// And so on
Since every entry is defined relatively to the base, they can grow or shrink in size freely without fear the runtime code can't address them.
Limitations
The initializer expression must be of a type that can be stored inline (e.g. not a slice). A slice will only store a ptr
and a size in the custom executable section referencing .data
, which means we can't modify it.
Dependencies
~3.5MB
~71K SLoC