Back in the 1980s, text editors had configuration files. The configuration file would be read every time the editor was loaded. This was very slow on a floppy disk system.
I realized that, instead of a configuration file, I could configure the executable instead! So, any changes in configuration meant the editor would patch its own exe file!
This marvelous technique came to an end when attempts to stop malware got folded into the operating system.
Some early systems, like TeX and I believe some Lisps, took this to the extreme. Instead of patching, they loaded their config once and then dumped the configured process image to a file, which was used in subsequent invocations.
TeX was developed on DECsystem-10/20 machines (36-bit words, your choice of byte size). The various operating systems (Tops20, Sail/Waits, ITS) were superior to Unix in a few ways, one of which was that when a process was suspended (think control-Z, or even control-C) you could issue the built-in shell SAVE command that would save the entire state of the suspended process into a new executable, data segment as well as code. So, on these systems, you could run TeX, have it load a bunch of macros and such, and then SAVE the result as a real, pre-configured executable for the world to run. Easy-peasy.
This was true for the original TeX78 written in Sail, as well as the ultimate TeX82 written in Knuth's WEB macro language on top of Pascal (that nowadays typically gets transpiled to C). Other programs did similar stuff; the feature was in the OS way before TeX started.
(Gory detail: Actually, TeX went a little further, to free up all possible address space for the final executable: The version that could initialize various hash tables and hyphenation trie tables could dump (nee serialize) a binary file of the resulting data structures; then a slimmed-down version that didn't have that code would read the binary info back in to recreate the initial data structure state, and that's what you'd SAVE the production executable from.)
For the unix-y versions of TeX, there was effort made to mimic this sort of thing in user-land with "undump", but admirable as it was, it was a hack, I'm told. These days, everything is so fast, it's not clear that this feature would be worth it anyway.
Emacs does this to create its image with all the added functionality above the minimum required to run elisp, then has a mechanism to pull in text files because elisp is just text anyway.
They just chucked the old system for a portable version of it, but until this last release, they still had to option of doing it the old school way.
Don’t think TeX ever did that, that was a “simple” Pascal program.
Lisp Machines though.. updating the operating system was by loading bunch of compiled files that replaced currently loaded functions in memory.
Then again, Lisp Machines where very proud of self modification — the CADR had a fun feature where it could modify the next instruction depending on things…
IIRC early Turbo Pascal versions worked like that too, there was some "setup" program that let you configure colors, etc, by modifying the COM/EXE file itself.
I still run Borland's Turbo Pascal on CP/M systems, and many programs had setup/configuration programs which would rewrite binaries for specific input/output devices.
Choosing between ADM-3A or ANSI terminals by running "WINSTALL" would rewrite the main Wordstart executable WS.COM appropriately for example.
Was the advantage that the exe file's sectors were likely to be contiguous on disk, so the config data could likely be read in a single pass through sectors on the same track, as compared to probably having to wait for a full revolution and a track seek for the separate config file approach? Or was it that the config file was verbose but compressed to a much smaller image in memory, thus fewer bytes to read?
yes but it's easy enough to issue a cache flush when you modify the code.
The overhead of cache flushing means some old school techniques are no longer viable, like modifying a constant in the next instruction. However it is still interesting to write machine code snippets once and execute them many times, like the nested function trampolines. I had a case where I had RGB masks like R=0x00ff0000 etc (loaded at startup once) and wanted to convert 0x00rrggbb to match the mask (so no-op in the common case but not always) which could have involved setting the shift amounts in a series of shift instructions.
The Linux kernel uses self-modification to change branches depending on whether certain features are on. For example when a user-mode process starts tracing a certain function, it adds code to the beginning of that function to trace the call, otherwise it pads that space with a no-op. JIT compilers also make good use of knowing whether a class has any subclasses, which is statically unknowable in Java but dynamically knowable.
The Microsoft Detours[1] library does this for arbitrary Windows API calls. I've used it production to fix simple bugs in third-party software no longer supported by vendors.
For example, I have a program that opens Adobe Acrobat Reader as an out-of-process COM server, but tends to leave phantom Acrobat processes hanging around after it quits. To fix this, I wrap CreateProcess in a function that adds any Acrobat processes created to a job object[2] set up to make Windows automatically kill them when the application closes.
Yep. Even outside of tracing, there are several different ways that the Linux kernel patches itself:
- Static calls: like a call to a global function pointer, except instead of loading a function pointer and doing an indirect call, the code is patched to do a direct call to the destination
- Static keys: like an if statement testing a global boolean, except instead of loading a boolean and doing a conditional branch, the code is patched to do either an unconditional branch or a nop
- Runtime constants: like a load of a global variable, except instead of loading, the value is patched directly into the code
- Alternatives: selects one of multiple possible instruction sequences depending on (usually) whether the CPU supports specific instructions
It's really fascinating to see the kind of fun efficient stuff you can do when you have that level of low-level control. Not just code patching but things like RCU as well.
> However it is still interesting to write machine code snippets once and execute them many times, like the nested function trampolines.
I slightly disagree on this though. In my experience writing code with Clang blocks (which don't use trampolines), they're often useful for code organization even if the callback will only be called once. Therefore, even ignoring security issues, I think GCC choosing a design that required cache flushing was a mistake - certainly in retrospect (as cache flushing has become more expensive over the years), but perhaps even at the time. I did some research, and trampolines were introduced in GCC 2.0, which already included mprotect calls and/or cache flushes on some of the architectures it supported, such as MIPS. However, this was a relatively new development, and on most of the supported architectures it didn't do either of those things. But on MIPS it would do an mprotect every single time a trampoline was created, which can't have been fast.
It's not the cache flush that's expensive, it's loading the new instructions that aren't cached. If you are flushing the very next instruction and then immediately executing it, that's expensive because of the serial dependency, but if you're generating new code, flushing it shouldn't be more expensive than if you were simply accessing new code for the first time. But on old systems you could modify the very next instruction with no penalty because there wasn't a cache. You can still do that and probably faster than those old systems could (they were slower because of not having a cache, everything was an uncached access), it's just a waste of most of the new system's performance.
BTW you can do all of this cool stuff in user mode on Linux too (but not on OpenBSD) - you just have to opt in to executable stack and/or writable .text. I could have written the dynamic shift instruction generator I mentioned, but I didn't want to spend the effort, but I imagined having a language with actual support for something like that (like static keys for variables).
No, it might make sense because it saves precious memory bandwidth as well as uop count. This code wasn't called that often, generally once per drawing operation, but I don't like being uselessly wasteful - that mindset is how software got so bloated.
Your program is either front-end stalled by uop count or instruction cache latency, or back-end bound by memory bandwidth or ALU throughput or a serial dependency chain. It doesn't matter which is the bottleneck for this case, because inlining constants improves most of the above!
Back in the 1980s, text editors had configuration files. The configuration file would be read every time the editor was loaded. This was very slow on a floppy disk system.
I realized that, instead of a configuration file, I could configure the executable instead! So, any changes in configuration meant the editor would patch its own exe file!
This marvelous technique came to an end when attempts to stop malware got folded into the operating system.
Some early systems, like TeX and I believe some Lisps, took this to the extreme. Instead of patching, they loaded their config once and then dumped the configured process image to a file, which was used in subsequent invocations.
TeX was developed on DECsystem-10/20 machines (36-bit words, your choice of byte size). The various operating systems (Tops20, Sail/Waits, ITS) were superior to Unix in a few ways, one of which was that when a process was suspended (think control-Z, or even control-C) you could issue the built-in shell SAVE command that would save the entire state of the suspended process into a new executable, data segment as well as code. So, on these systems, you could run TeX, have it load a bunch of macros and such, and then SAVE the result as a real, pre-configured executable for the world to run. Easy-peasy.
This was true for the original TeX78 written in Sail, as well as the ultimate TeX82 written in Knuth's WEB macro language on top of Pascal (that nowadays typically gets transpiled to C). Other programs did similar stuff; the feature was in the OS way before TeX started.
(Gory detail: Actually, TeX went a little further, to free up all possible address space for the final executable: The version that could initialize various hash tables and hyphenation trie tables could dump (nee serialize) a binary file of the resulting data structures; then a slimmed-down version that didn't have that code would read the binary info back in to recreate the initial data structure state, and that's what you'd SAVE the production executable from.)
For the unix-y versions of TeX, there was effort made to mimic this sort of thing in user-land with "undump", but admirable as it was, it was a hack, I'm told. These days, everything is so fast, it's not clear that this feature would be worth it anyway.
Source: me; I was there.
1 reply →
Emacs does this to create its image with all the added functionality above the minimum required to run elisp, then has a mechanism to pull in text files because elisp is just text anyway.
They just chucked the old system for a portable version of it, but until this last release, they still had to option of doing it the old school way.
Don’t think TeX ever did that, that was a “simple” Pascal program.
Lisp Machines though.. updating the operating system was by loading bunch of compiled files that replaced currently loaded functions in memory.
Then again, Lisp Machines where very proud of self modification — the CADR had a fun feature where it could modify the next instruction depending on things…
How the world has changed.
2 replies →
On a floppy system, it's much faster to just patch it on disk than rewrite it!
Pharo [1] is a modern take.
[1] https://pharo.org/
emacs did this too.
2 replies →
IIRC early Turbo Pascal versions worked like that too, there was some "setup" program that let you configure colors, etc, by modifying the COM/EXE file itself.
I still run Borland's Turbo Pascal on CP/M systems, and many programs had setup/configuration programs which would rewrite binaries for specific input/output devices.
Choosing between ADM-3A or ANSI terminals by running "WINSTALL" would rewrite the main Wordstart executable WS.COM appropriately for example.
Was the advantage that the exe file's sectors were likely to be contiguous on disk, so the config data could likely be read in a single pass through sectors on the same track, as compared to probably having to wait for a full revolution and a track seek for the separate config file approach? Or was it that the config file was verbose but compressed to a much smaller image in memory, thus fewer bytes to read?
yes but it's easy enough to issue a cache flush when you modify the code.
The overhead of cache flushing means some old school techniques are no longer viable, like modifying a constant in the next instruction. However it is still interesting to write machine code snippets once and execute them many times, like the nested function trampolines. I had a case where I had RGB masks like R=0x00ff0000 etc (loaded at startup once) and wanted to convert 0x00rrggbb to match the mask (so no-op in the common case but not always) which could have involved setting the shift amounts in a series of shift instructions.
The Linux kernel uses self-modification to change branches depending on whether certain features are on. For example when a user-mode process starts tracing a certain function, it adds code to the beginning of that function to trace the call, otherwise it pads that space with a no-op. JIT compilers also make good use of knowing whether a class has any subclasses, which is statically unknowable in Java but dynamically knowable.
The Microsoft Detours[1] library does this for arbitrary Windows API calls. I've used it production to fix simple bugs in third-party software no longer supported by vendors.
For example, I have a program that opens Adobe Acrobat Reader as an out-of-process COM server, but tends to leave phantom Acrobat processes hanging around after it quits. To fix this, I wrap CreateProcess in a function that adds any Acrobat processes created to a job object[2] set up to make Windows automatically kill them when the application closes.
[1] https://github.com/microsoft/detours
[2] https://learn.microsoft.com/en-us/windows/win32/procthread/j...
Yep. Even outside of tracing, there are several different ways that the Linux kernel patches itself:
- Static calls: like a call to a global function pointer, except instead of loading a function pointer and doing an indirect call, the code is patched to do a direct call to the destination
- Static keys: like an if statement testing a global boolean, except instead of loading a boolean and doing a conditional branch, the code is patched to do either an unconditional branch or a nop
- Runtime constants: like a load of a global variable, except instead of loading, the value is patched directly into the code
- Alternatives: selects one of multiple possible instruction sequences depending on (usually) whether the CPU supports specific instructions
It's really fascinating to see the kind of fun efficient stuff you can do when you have that level of low-level control. Not just code patching but things like RCU as well.
> However it is still interesting to write machine code snippets once and execute them many times, like the nested function trampolines.
I slightly disagree on this though. In my experience writing code with Clang blocks (which don't use trampolines), they're often useful for code organization even if the callback will only be called once. Therefore, even ignoring security issues, I think GCC choosing a design that required cache flushing was a mistake - certainly in retrospect (as cache flushing has become more expensive over the years), but perhaps even at the time. I did some research, and trampolines were introduced in GCC 2.0, which already included mprotect calls and/or cache flushes on some of the architectures it supported, such as MIPS. However, this was a relatively new development, and on most of the supported architectures it didn't do either of those things. But on MIPS it would do an mprotect every single time a trampoline was created, which can't have been fast.
It's not the cache flush that's expensive, it's loading the new instructions that aren't cached. If you are flushing the very next instruction and then immediately executing it, that's expensive because of the serial dependency, but if you're generating new code, flushing it shouldn't be more expensive than if you were simply accessing new code for the first time. But on old systems you could modify the very next instruction with no penalty because there wasn't a cache. You can still do that and probably faster than those old systems could (they were slower because of not having a cache, everything was an uncached access), it's just a waste of most of the new system's performance.
BTW you can do all of this cool stuff in user mode on Linux too (but not on OpenBSD) - you just have to opt in to executable stack and/or writable .text. I could have written the dynamic shift instruction generator I mentioned, but I didn't want to spend the effort, but I imagined having a language with actual support for something like that (like static keys for variables).
Modifying a constant in code might make sense because it saves several precious bytes of variable storage.
No, it might make sense because it saves precious memory bandwidth as well as uop count. This code wasn't called that often, generally once per drawing operation, but I don't like being uselessly wasteful - that mindset is how software got so bloated.
Your program is either front-end stalled by uop count or instruction cache latency, or back-end bound by memory bandwidth or ALU throughput or a serial dependency chain. It doesn't matter which is the bottleneck for this case, because inlining constants improves most of the above!
1 reply →