< March 2006 >
    1 2 3 4
5 6 7 8 91011
Tue, 14 Mar 2006:

the problem: If you are a C programmer and I mean a really serious C programmer, you must've had to debug at least one memory leak bug in your life. Unless you built something like a CGI script which got torn down after seconds of existence, your patience must've cracked trying to figure out what is exactly slowly eating into your memory space. Even worse, after you found the exact chunk size that was leaking periodically, you might have no way of actually debugging where it leaked. Sooner or later, you learnt about electricfence and more importantly dmalloc. And of course, of valgrind - the holy grail of memory overwrite debugging.

less data, more info: These tools show a big picture view of the process, which meant that you got to see all the leaks everywhere. If you are debugging something huge like evolution, thunderbird or firefox - you don't want to see the entire leak listing because the data structures needed to keep that info outside the process itself is huge. Plan for a couple of GBs if you want to properly run valgrind on such a codebase. The other solution is to put macros everywhere to relay your file name and line numbers to your data structure classes so that you can figure out that the leak apparently in hash.c or string.c is really in prepare_for_cache_premature_optimisation.c.

recompile all strategies: This strategy also fails when you start passing around function pointers. Unless your function pointer prototype changes along with the debug mode, you cannot use that. If you are building a small extension to a big project like php, you do not have the luxury of testing out some two thousand lines of your code on production with a debug mode. That is when you try to figure out how to pass data without adding extra parameters to your function signatures. Using global variables like FFCall trampolines do is not quite the thread safe way.

gcc local functions: There are functions which are local to another function. Try compiling the following code in your gcc.

typedef int (*intfun)(int i);

intfun make_adder(int i) 
    int add(int k) 
        return (i+k);
    return add;

int main() 
    intfun foo;
    foo = make_adder(2);
    printf("%d\n", foo(3));
    return 0;

no, not that way: Even though it might look like a closure to the inexperienced eye, a quick run of the code will tell you that it doesn't work the way you'd have expected it to work. So I decided to quickly hack up a simple closure wrapper for a function by dynamically generating code.

With the help of x86_codegen.h and a rough understanding of x86 call frames, I started to hack this out. This is how the first cut looked like.

malloc_fun_t make_closure(malloc_fun_t original, 
                            const char * filename, int line)
    byte * code = malloc(4096);
    byte * method = code;
    x86_push_reg(code, X86_EBP);
    x86_mov_reg_reg(code, X86_EBP, X86_ESP, 4);

    /* add 8 (two words) to size */
    x86_alu_membase_imm(code, X86_ADD, X86_EBP, 8, 8);
    /* frame for next function */
    x86_alu_reg_imm(code, X86_SUB, X86_ESP, 12);

    /* push size */    
    x86_push_membase(code, X86_EBP, 0x8);
    x86_call_code(code, original);
    /* pop frame */
    x86_alu_reg_imm(code, X86_ADD, X86_ESP, 12);

    /* return value in eax, push in data */
    x86_mov_membase_imm(code, X86_EAX, 0, (int )filename, 4);
    x86_mov_membase_imm(code, X86_EAX, 4, (int)line, 4);
    x86_alu_reg_imm(code, X86_ADD, X86_EAX, 8);

    return method;

But very quickly, I realized that this code segvs on some libc versions while running perfectly fine on all the others. Turns out that with security patches turned on, you cannot run code off memory you allocated using malloc. But it proved to have a simple work around - mmap();

void *allocate_executable_mem(size_t size)
    static int zero_fd = -1;    
    void * addr;
    if(zero_fd == -1)
        /* thread safety is for another day */
        zero_fd = open("/dev/zero", O_RDWR, 0);
    addr = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_SHARED | MAP_ANON, zero_fd, 0);
    return addr;

That PROT_EXEC did the trick and the memory was now writeable and executable from userland. Then I added a couple more lines of code to ensure that I can distinguish a closure wrapped call from a standard gcc compiled code with a signature magic embedded in the binary code. And wrote some code to jump over it while executing the method.

target = code;
x86_jump8(code, 0);
/* magic to identify closures */
*(code++) = 0x42;
*(code++) = 0x13;
*(code++) = 0x37;
*(code++) = 0x42;

x86_patch(target, code); 

Now, I had a function which looked like malloc for anyone calling it, but could relay information about its source line and file in -1 and -2 offsets from start of the returned block. Look at asm-cl.c.

73              malloc_fun_t f = make_closure(malloc, __FILE__, __LINE__);
(gdb) x/8i f

0xf1a000:       push   %ebp
0xf1a001:       mov    %esp,%ebp
0xf1a003:       jmp    0xf1a009
0xf1a009:       addl   $0x8,0x8(%ebp)
0xf1a00d:       sub    $0xc,%esp
0xf1a010:       pushl  0x8(%ebp)

(gdb) c
0x8a9f010 traced to asm-cl.c:73
0x8a9f028 traced to asm-cl.c:74

People under-estimate what can be done with C when they say that " I know C ". I'm sad to say that there are whole cookie cutter assembly lines of colleges churning out students who won't appreciate such hacks which subvert the entire meaning of function pointers and take them a step closer to understanding what really happens. The urge to take things apart and see what they are made of is a basic enough human trait. Too little of it bubbles through all the exams and internals into real appreciation for the inner wheels of such otherwise useless magic tricks.

There's only one word to describe the above code - CRAZY. I can't believe I sat down and wrote this.

You may be right, I may be crazy,
But it just may be a lunatic you're looking for!

posted at: 19:44 | path: /hacks | permalink | Tags: ,