blob: 3a113ffea29cae7179c1aff64349cb2a4f5540dd [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* C++ exceptions. llvm lowers unwinding to two insns:
* invoke foo unwind pad call foo; unwind_goto pad
* resume r0 = 0; unwind_exit
* so 'throw' is a jump or an exit, 'catch (...)' and the destructors that run on
* the way are ordinary blocks.
*
* vmlinux.h and bpf_helpers.h are not C++.
*/
#define SEC(name) __attribute__((section(name), used))
#define __ksym __attribute__((section(".ksyms")))
#define __noinline __attribute__((noinline))
#define __always_inline inline __attribute__((always_inline))
#ifdef __BPF_FEATURE_UNWIND
struct __sk_buff {
unsigned int len;
};
extern "C" {
void bpf_rcu_read_lock(void) noexcept __ksym;
void bpf_rcu_read_unlock(void) noexcept __ksym;
void bpf_preempt_disable(void) noexcept __ksym;
void bpf_preempt_enable(void) noexcept __ksym;
/* Starts an unwind. Not a kfunc: llvm lowers the call and it doesn't come back. */
int _Unwind_RaiseException(void *exc);
/* All that is left of the C++ runtime. Nobody looks at the exception object. */
static char exc_obj[16];
__always_inline void *__cxa_allocate_exception(unsigned long size) noexcept
{
return exc_obj;
}
__always_inline void __cxa_throw(void *exc, void *tinfo, void (*dtor)(void *))
{
_Unwind_RaiseException(exc);
}
__always_inline void *__cxa_begin_catch(void *exc) noexcept
{
return exc;
}
__always_inline void __cxa_end_catch(void) noexcept
{
}
}
/* One decimal digit per destructor, in the order they ran */
volatile int dtors SEC(".data.dtors");
/* The verifier insists that both are undone on every path out of the prog */
struct rcu_guard {
__always_inline rcu_guard()
{
bpf_rcu_read_lock();
}
__always_inline ~rcu_guard()
{
bpf_rcu_read_unlock();
dtors = dtors * 10 + 1;
}
};
struct plain_rcu_guard {
__always_inline plain_rcu_guard()
{
bpf_rcu_read_lock();
}
__always_inline ~plain_rcu_guard()
{
bpf_rcu_read_unlock();
}
};
struct preempt_guard {
__always_inline preempt_guard()
{
bpf_preempt_disable();
}
__always_inline ~preempt_guard()
{
bpf_preempt_enable();
dtors = dtors * 10 + 2;
}
};
static __noinline int inner(int len)
{
rcu_guard guard;
if (len > 100)
throw 1;
return len;
}
static __noinline int middle(int len)
{
preempt_guard guard;
return inner(len) + 1;
}
using u128 = unsigned __int128;
/* Global subprogs returning 16 bytes use R0:R2, including unwind exits. */
extern "C" __noinline u128 aggregate_return(int len)
{
plain_rcu_guard guard;
if (len > 100)
throw 1;
return (static_cast<u128>(len) << 64) | 7;
}
extern "C" SEC("tc") int catches(struct __sk_buff *skb)
{
try {
return middle(skb->len);
} catch (...) {
return 42;
}
}
/* The exception leaves the prog: it returns 0 */
extern "C" SEC("tc") int does_not_catch(struct __sk_buff *skb)
{
return middle(skb->len);
}
extern "C" SEC("tc") int catches_aggregate(struct __sk_buff *skb)
{
try {
u128 value = aggregate_return(skb->len);
return static_cast<unsigned long>(value) +
static_cast<unsigned long>(value >> 64);
} catch (...) {
return 42;
}
}
#endif
char _license[] SEC("license") = "GPL";