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.
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.
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.
I was re-reading Ian Lance Taylor's series of articles on Linkers [0], and one thing I didn't realise is that using dynamically linked libraries almost requires self-modifying code unless you want to resolve all function calls are start-time (which would make startup slower).
I'm still working my way through it so it's possible that I've misunderstood this section, though, and one question I haven't answered is how they get around the typical restriction on w+x pages.
It was maybe cool 50 years ago or so. Nowadays it's no longer needed. Possible performance gains of such code are marginal and modern programming languages allow generating many specialized and optimized code pieces using the same template, so that self-modification is no longer needed.
There is also JIT (like in regexp engines), but it's different story.
It's still beneficial on some x86-64 implementations to rewrite indirect jumps (as used in PLT stubs) to direct jumps when feasible. For example, AMD says this about the Zen 4 architecture:
> Only a limited number of indirect targets that cross a 64MB aligned boundary relative to the branch address can be tracked in the indirect target predictor. Software should limit the number of indirect branch targets that cross such a boundary.
And one way doing this is to replace the indirect branch with a direct branch, which supports a 32-bit signed displacement.
It's pretty much always beneficial to do something more directly. Doing less work is always better than doing more work. The slowness of modern software is the result of a stack of abstractions acting like a stack of interpreters. You write something in React, that manipulates a React object tree and shadows it to a DOM, which gets shadowed to an internal object tree which gets laid out and shadowed to a stack of GPU layers which gets written out as drawing commands... When you want to scroll up there's so much work to do. To make it fast, cut through layers and minimize work. In the 1990s, scrolling up meant calculating how many pixels to scroll, blitting that many pixels in the main framebuffer (usually GPU accelerated) and then rendering the new pixels at the bottom. There wasn't even a double buffer. Very little abstraction there, just the shortest path to achieve the desired result. The GPU driver did abstract the blit and rendering operations, of course, and the mouse driver abstracted the scrollwheel event, and you may have a wrapper component in your GUI tree that manages a viewport over a larger virtual component, but it's all kept as direct as practical. I can't imagine any electron app using the blit-pixels-up approach.
I remember once learning of a runtime environment that would inline class functions. For example they wrote an OS, and if you had an object of a SATA hard drive class, it would copy the function code and inline the drive ID. I don't remember how well it worked for them.
A related idea is the "tracing JIT". You know how you expect a JIT to translate one function at a time? A tracing JIT doesn't - it follows the program logic wherever it goes, through whatever control flow, and compiles all of it until it decides to stop. The most well known implementation is probably LuaJIT.
Not just security. Instruction caches must also be aware of self-modifying code.
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.
11 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.
1 reply →
When I was learning Python I wrote a program that stored data in its own .py file. I felt pretty smart.
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.
1 reply →
Modifying a constant in code might make sense because it saves several precious bytes of variable storage.
2 replies →
I was re-reading Ian Lance Taylor's series of articles on Linkers [0], and one thing I didn't realise is that using dynamically linked libraries almost requires self-modifying code unless you want to resolve all function calls are start-time (which would make startup slower).
I'm still working my way through it so it's possible that I've misunderstood this section, though, and one question I haven't answered is how they get around the typical restriction on w+x pages.
[0] Particularly this one https://www.airs.com/blog/archives/41
It was maybe cool 50 years ago or so. Nowadays it's no longer needed. Possible performance gains of such code are marginal and modern programming languages allow generating many specialized and optimized code pieces using the same template, so that self-modification is no longer needed.
There is also JIT (like in regexp engines), but it's different story.
It's still beneficial on some x86-64 implementations to rewrite indirect jumps (as used in PLT stubs) to direct jumps when feasible. For example, AMD says this about the Zen 4 architecture:
> Only a limited number of indirect targets that cross a 64MB aligned boundary relative to the branch address can be tracked in the indirect target predictor. Software should limit the number of indirect branch targets that cross such a boundary.
And one way doing this is to replace the indirect branch with a direct branch, which supports a 32-bit signed displacement.
It's pretty much always beneficial to do something more directly. Doing less work is always better than doing more work. The slowness of modern software is the result of a stack of abstractions acting like a stack of interpreters. You write something in React, that manipulates a React object tree and shadows it to a DOM, which gets shadowed to an internal object tree which gets laid out and shadowed to a stack of GPU layers which gets written out as drawing commands... When you want to scroll up there's so much work to do. To make it fast, cut through layers and minimize work. In the 1990s, scrolling up meant calculating how many pixels to scroll, blitting that many pixels in the main framebuffer (usually GPU accelerated) and then rendering the new pixels at the bottom. There wasn't even a double buffer. Very little abstraction there, just the shortest path to achieve the desired result. The GPU driver did abstract the blit and rendering operations, of course, and the mouse driver abstracted the scrollwheel event, and you may have a wrapper component in your GUI tree that manages a viewport over a larger virtual component, but it's all kept as direct as practical. I can't imagine any electron app using the blit-pixels-up approach.
I remember once learning of a runtime environment that would inline class functions. For example they wrote an OS, and if you had an object of a SATA hard drive class, it would copy the function code and inline the drive ID. I don't remember how well it worked for them.
A related idea is the "tracing JIT". You know how you expect a JIT to translate one function at a time? A tracing JIT doesn't - it follows the program logic wherever it goes, through whatever control flow, and compiles all of it until it decides to stop. The most well known implementation is probably LuaJIT.