Search code examples
frida

Why does this simple Frida trace of C code act erratically?


I'm experimenting with Frida, and some of the simplest examples I've found do not work as expected on macOS. Here is an example.

Consider this C code:

#include <stdio.h>
#include <unistd.h>

void print_hello(int n, char a, float f) {
    printf("hello %d %c %f\n", n, a, f);
}

int main(int argc, char *argv[]) {
    while (1) {
        print_hello(10, 'a', 3.141f);
        sleep(1);
    }
    return 0;
}

Obvious what it does.

Now here's a Frida python launcher:

#!/usr/bin/env python3

import frida

def on_message(message, data):
    print(message)

pid = frida.spawn('./a.out')
session = frida.attach(pid)
script = session.create_script("""
    Interceptor.attach(Module.findExportByName(null, 'print_hello'), {
        onEnter(args) {
            send('enter')
        },
        onLeave(retval) {
            send('leave')
        }
    })
""")
script.on('message', on_message)
script.load()
frida.resume(pid)

One would expect this to print enter/leave every second. Instead, here are some of the results I get:


% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
^C

% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
^C

% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
hello 10 a 3.141000
^C

% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
{'type': 'send', 'payload': 'leave'}

% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
hello 10 a 3.141000
hello 10 a 3.141000
^C

% ./trace.py 
hello 10 a 3.141000
{'type': 'send', 'payload': 'enter'}
hello 10 a 3.141000
hello 10 a 3.141000
hello 10 a 3.141000
^C

On that third invocation it exited w/o ^C.


Solution

  • There's nothing keeping trace.py alive, so the instrumentation is reverted once it reaches the end of the script – as part of the Python interpreter shutting down. You could add a call to sys.stdin.read() at the end of the script to avoid this.