10 comments

[ 2.9 ms ] story [ 31.0 ms ] thread
This is a perfect example of "because I can". Never ever do this for real.
In a dev/test environment, do whatever works best and fastest. If you have the skillset and toolset to jump right to the ABI to solve a problem, it's pretty silly to stay in the confines of MRI just out of orthodoxy.
I'm probably not thinking this through all the way, but wouldn't a memory profiler be more straight-forward, and less dangerous in Smalltalk?

I haven't built one myself, but I know that the Seaside framework comes with a built-in memory profiler.

Doing intercession of messages to objects in Smalltalk isn't trivial, but it isn't voodoo either. I whipped up a quick method replacement class that swaps out a database 'save' method's behavior for testing. However, I would imagine that trying to replace VM primitive calls would lead to some not-so-nice side effects (maybe... or would it just make the image slower?).

I like Ruby, and I like Smalltalk, but I'm liking Smalltalk much better lately because the "turtles all the way down" aspect which makes things like a memory profiler much less "voodoo-y".

Replace "memory profiler" with any other piece of runtime manipulation, and the point is just as valid with Smalltalk. It is not, in fact, "turtles all the way down"; eventually you hit the ABI, and that's what this article is talking about.
I love posts like this, but I think the authors would benefit from a close read of the Microsoft Detours paper, which runtime patchers have been cribbing from for the past 5 years or so.

The general Detours approach:

* Disassemble the first N bytes of a target location

* Scoop N bytes worth of opcodes out of the target, and re-host them somewhere heap-allocated

* Replace the N bytes with an absolute jump to a heap-allocated trampoline

* Bounce to your code

* Execute the scooped-up N bytes worth of opcodes

* Jump back to the target

This approach (what the post calls "caller-side trampolines") works well when your targets are function prologues, and less well when it's an arbitrary bblock.

We have an implementation in pure Ruby, complete with a pure-Ruby ia32 assembler (which is one of the most useful little pieces of code I've written) at Timur's Ragweed repository --- google "Ruby ragweed github".

I am surprised this works. I was under the impression that more modern implementations write protected the pages that libraries in the initial link are loaded to. There is really no good reason for them to be writable save for extremely strange uses such as in this article.

Is this assumption false?

Userland code can't usually modify program text. The kernel can. The kernel will modify program text on your program's behalf if you own the process you want to modify. Depending on the OS, that happens via WriteProcessMemory, ptrace, Mach, or procfs.
Well in the code Joe posted, he takes the marked pages and calls mprotect on them so they can be modified. I am surprised he's allowed to do this at all. Is there ever a legitimate reason to do this in most modern systems?

I mean besides this crazy-like-a-fox twin trampoline system, or for compromising binaries at runtime maliciously. The days of overlays are long gone. :)

   int
   main(int argc, char **argv) {
        void *faddr = strxfrm;
        void *paddr = (void*) ((u_int32_t)faddr & 0xfffff000);

        if(mprotect(paddr, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) >= 0) {
                ((u_char *)faddr)[0] = 0xcc;
                strxfrm("foo", "", 0);
        } else
                perror("mprotect");

        exit(0);
   }

   $ ./mp
   Trace/BPT trap
The simplest answer to your question that jumps into my head is "relocations", but I'm always surprised by the everyday craziness of the C runtime and the Unix ABI, so I'm sure there's a simpler answer.