Search code examples
linux-kernelebpfbcc-bpf

Couldn't get the arguments of "echo" bash builtin function by uprobe


Sample code as follows. I use python BCC library and write a simple BPF function and try to attach uprobe on echo bash builtin function.

from bcc import BPF
prog = """
#include<linux/sched.h>
int echo_catch(struct pt_regs *ctx){
    char command[64]={};
    bpf_probe_read_user_str(command, sizeof(command), (char *) PT_REGS_PARM1(ctx));
    bpf_trace_printk("%s", command);
    return 0;
}
"""
b = BPF(text=prog)
b.attach_uprobe(name="/bin/bash", sym="echo_builtin", fn_name="echo_catch")
b.trace_print()

But it always print out nothing:

b'            bash   [001] 51239.033139: bpf_trace_printk:'

Do I do anything wrong? How could I get the params of user mode program?


Solution

  • I think part of the problem here is that you're making assumptions about the arguments to the echo_builtin function. If you look at the bash source, echo_builtin is defined like this:

    int
    echo_builtin (list)
         WORD_LIST *list;
    

    That is, the arguments to echo_builtin are not the words to print; there is a single argument and it is a pointer to a WORD_LIST structure (you can explore this in some detail if run bash with gdb and set a breakpoint on echo_bulitin).


    Update 1

    This is a terrible hack, because I have no idea what I am doing, but it demonstrates the idea I was trying to get across:

    # Inspired partially by https://github.com/iovisor/bcc/blob/master/tools/bashreadline.py
    from bcc import BPF
    
    prog = """
    #include<linux/sched.h>
    
    
    typedef  struct word_desc {
        char *word;
        int flags;
    } WORD_DESC;
    
    typedef struct word_list {
        struct word_list *next;
        WORD_DESC *word;
    } WORD_LIST;
    
    struct event_item {
        char str[80];
    };
    
    BPF_PERF_OUTPUT(events);
    
    int echo_catch(struct pt_regs *ctx){
        WORD_LIST head, *cur;
        WORD_DESC data;
        int i;
    
        cur = (void *)PT_REGS_PARM1(ctx);
    
        for (i=0; i<10; i++) {
            char *word;
            struct event_item item = {"hello"};
            if (! cur) break;
    
            bpf_probe_read_user(&head, sizeof(head),
                (void *)cur);
            bpf_probe_read_user(&data, sizeof(data),
                (void *)head.word);
            bpf_probe_read_user_str(&item.str, sizeof(item.str), (void *)data.word);
    
            events.perf_submit(ctx,&item,sizeof(item));
    
            cur = head.next;
        }
    
        return 0;
    }
    """
    b = BPF(text=prog)
    b.attach_uprobe(name="/bin/bash", sym="echo_builtin", fn_name="echo_catch")
    
    def print_event(cpu, data, size):
        event = b["events"].event(data)
        print(event.str.decode('utf-8', 'replace'))
    
    b["events"].open_perf_buffer(print_event)
    while 1:
        try:
            b.perf_buffer_poll()
        except KeyboardInterrupt:
            break
    

    If you run this and in a bash terminal enter:

    echo hello world this is a test
    

    Then you will see displayed where the bcc program is running:

    hello
    world
    this
    is
    a
    test
    

    Update 2

    I used this question as an excuse to learn a little about bpftrace. We can implement the same solution using the following bpftrace script, which I think is a little cleaner than the earlier solution:

    #!/usr/bin/bptrace
    
    struct word_desc {
        char *word;
        int flags;
    };
    
    struct word_list {
        struct word_list *next;
        struct word_desc *word;
    };
    
    uprobe:/bin/bash:echo_builtin
    {
            $head = (struct word_list *)arg0;
            $marker = $head;
            $count = 10;
    
            printf("%d: ", pid);
            while ($count) {
                    printf("%s ", str($marker->word->word));
                    $marker = $marker->next;
                    if ($marker == 0) {
                            break;
                    }
                    $count--;
            }
            printf("\n");
    }
    

    The output of the above program will look <pid>: <arguments to echo command>, for example:

    76534: hello world
    76534: this is a test