0

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?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
WKali
  • 17
  • 4
  • Why would you expect to catch a bash built-in? I don't believe bash needs to interact with the kernel at all to run the `echo` built-in. – Shane Bishop Nov 03 '22 at 00:52
  • 2
    @ShaneBishop `echo` writes to the terminal... so surely a kernel interaction is needed (i.e. a `write` to the TTY). In any case, OP is trying to use a *user* probe, which is meant to trace usermode code ([BCC doc](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#4-attach_uprobe)), so whether `echo` interacts with the kernel or not shouldn't matter. – Marco Bonelli Nov 03 '22 at 01:14
  • Thanks. Uprobe is used to trace user-mode process, and BCC provides an interface to make us easily write a BPF program and attach on user-mode program through Uprobe mechanism. @MarcoBonelli Do you have any ideas to address this issue? – WKali Nov 03 '22 at 05:07

1 Answers1

1

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
larsks
  • 277,717
  • 41
  • 399
  • 399
  • I've updated this answer with an example that does what you want, although there are probably more graceful solutions. This is the first time I've tried working with bcc so I'm not entirely sure what I'm doing. – larsks Nov 04 '22 at 01:29
  • I should trace bash functions before probe on. I directly assume the argument of echo_builtin is char* but it's wrong. This is a helpful and great solution, thank you. – WKali Nov 04 '22 at 02:58
  • I've added a `bpftrace` version of the solution. – larsks Nov 05 '22 at 02:44