Skip to content

Commit 8b401f9

Browse files
yonghong-songborkmann
authored andcommitted
bpf: implement bpf_send_signal() helper
This patch tries to solve the following specific use case. Currently, bpf program can already collect stack traces through kernel function get_perf_callchain() when certain events happens (e.g., cache miss counter or cpu clock counter overflows). But such stack traces are not enough for jitted programs, e.g., hhvm (jited php). To get real stack trace, jit engine internal data structures need to be traversed in order to get the real user functions. bpf program itself may not be the best place to traverse the jit engine as the traversing logic could be complex and it is not a stable interface either. Instead, hhvm implements a signal handler, e.g. for SIGALARM, and a set of program locations which it can dump stack traces. When it receives a signal, it will dump the stack in next such program location. Such a mechanism can be implemented in the following way: . a perf ring buffer is created between bpf program and tracing app. . once a particular event happens, bpf program writes to the ring buffer and the tracing app gets notified. . the tracing app sends a signal SIGALARM to the hhvm. But this method could have large delays and causing profiling results skewed. This patch implements bpf_send_signal() helper to send a signal to hhvm in real time, resulting in intended stack traces. Acked-by: Andrii Nakryiko <[email protected]> Signed-off-by: Yonghong Song <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]>
1 parent 5420f32 commit 8b401f9

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

Diff for: include/uapi/linux/bpf.h

+16-1
Original file line numberDiff line numberDiff line change
@@ -2672,6 +2672,20 @@ union bpf_attr {
26722672
* 0 on success.
26732673
*
26742674
* **-ENOENT** if the bpf-local-storage cannot be found.
2675+
*
2676+
* int bpf_send_signal(u32 sig)
2677+
* Description
2678+
* Send signal *sig* to the current task.
2679+
* Return
2680+
* 0 on success or successfully queued.
2681+
*
2682+
* **-EBUSY** if work queue under nmi is full.
2683+
*
2684+
* **-EINVAL** if *sig* is invalid.
2685+
*
2686+
* **-EPERM** if no permission to send the *sig*.
2687+
*
2688+
* **-EAGAIN** if bpf program can try again.
26752689
*/
26762690
#define __BPF_FUNC_MAPPER(FN) \
26772691
FN(unspec), \
@@ -2782,7 +2796,8 @@ union bpf_attr {
27822796
FN(strtol), \
27832797
FN(strtoul), \
27842798
FN(sk_storage_get), \
2785-
FN(sk_storage_delete),
2799+
FN(sk_storage_delete), \
2800+
FN(send_signal),
27862801

27872802
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
27882803
* function eBPF program intends to call

Diff for: kernel/trace/bpf_trace.c

+72
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,63 @@ static const struct bpf_func_proto bpf_probe_read_str_proto = {
567567
.arg3_type = ARG_ANYTHING,
568568
};
569569

570+
struct send_signal_irq_work {
571+
struct irq_work irq_work;
572+
struct task_struct *task;
573+
u32 sig;
574+
};
575+
576+
static DEFINE_PER_CPU(struct send_signal_irq_work, send_signal_work);
577+
578+
static void do_bpf_send_signal(struct irq_work *entry)
579+
{
580+
struct send_signal_irq_work *work;
581+
582+
work = container_of(entry, struct send_signal_irq_work, irq_work);
583+
group_send_sig_info(work->sig, SEND_SIG_PRIV, work->task, PIDTYPE_TGID);
584+
}
585+
586+
BPF_CALL_1(bpf_send_signal, u32, sig)
587+
{
588+
struct send_signal_irq_work *work = NULL;
589+
590+
/* Similar to bpf_probe_write_user, task needs to be
591+
* in a sound condition and kernel memory access be
592+
* permitted in order to send signal to the current
593+
* task.
594+
*/
595+
if (unlikely(current->flags & (PF_KTHREAD | PF_EXITING)))
596+
return -EPERM;
597+
if (unlikely(uaccess_kernel()))
598+
return -EPERM;
599+
if (unlikely(!nmi_uaccess_okay()))
600+
return -EPERM;
601+
602+
if (in_nmi()) {
603+
work = this_cpu_ptr(&send_signal_work);
604+
if (work->irq_work.flags & IRQ_WORK_BUSY)
605+
return -EBUSY;
606+
607+
/* Add the current task, which is the target of sending signal,
608+
* to the irq_work. The current task may change when queued
609+
* irq works get executed.
610+
*/
611+
work->task = current;
612+
work->sig = sig;
613+
irq_work_queue(&work->irq_work);
614+
return 0;
615+
}
616+
617+
return group_send_sig_info(sig, SEND_SIG_PRIV, current, PIDTYPE_TGID);
618+
}
619+
620+
static const struct bpf_func_proto bpf_send_signal_proto = {
621+
.func = bpf_send_signal,
622+
.gpl_only = false,
623+
.ret_type = RET_INTEGER,
624+
.arg1_type = ARG_ANYTHING,
625+
};
626+
570627
static const struct bpf_func_proto *
571628
tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
572629
{
@@ -617,6 +674,8 @@ tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
617674
case BPF_FUNC_get_current_cgroup_id:
618675
return &bpf_get_current_cgroup_id_proto;
619676
#endif
677+
case BPF_FUNC_send_signal:
678+
return &bpf_send_signal_proto;
620679
default:
621680
return NULL;
622681
}
@@ -1343,5 +1402,18 @@ static int __init bpf_event_init(void)
13431402
return 0;
13441403
}
13451404

1405+
static int __init send_signal_irq_work_init(void)
1406+
{
1407+
int cpu;
1408+
struct send_signal_irq_work *work;
1409+
1410+
for_each_possible_cpu(cpu) {
1411+
work = per_cpu_ptr(&send_signal_work, cpu);
1412+
init_irq_work(&work->irq_work, do_bpf_send_signal);
1413+
}
1414+
return 0;
1415+
}
1416+
13461417
fs_initcall(bpf_event_init);
1418+
subsys_initcall(send_signal_irq_work_init);
13471419
#endif /* CONFIG_MODULES */

0 commit comments

Comments
 (0)