logoalt Hacker News

inigyouyesterday at 5:27 PM3 repliesview on HN

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.


Replies

jasomilltoday at 3:53 AM

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...

comexyesterday at 9:13 PM

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.

show 1 reply
codedokodeyesterday at 6:21 PM

Modifying a constant in code might make sense because it saves several precious bytes of variable storage.

show 1 reply