Issue
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
Answered By - larsks Answer Checked By - Katrina (WPSolving Volunteer)