mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-29 11:06:29 +08:00
Patch series "mm: Unconditional per-VMA locks and cleanups", v7.
tl;dr: Make per-VMA locks available in all configs. Simplify some of the
per-VMA lock users now that they can rely on them being always available.
Binder and networking folks: Your code is the target of the cleanups. I'm
cc'ing you now on v2 because there's emerging consensus on the mm side
that the approach here is sane. I'm not quite sure how this pile would
get merged, but ack/review tags would be appreciated if this looks good to
you.
Longer version:
When working on some x86 shadow stack code, it was a real pain to avoid
causing recursive locking problems with mmap_lock. One way to avoid those
was to avoid mmap_lock and use per-VMA locks instead. They are great, but
they are not available in all configs which makes them unusable in generic
code, or if you want to completely avoid mmap_lock.
Make per-VMA locks available in all configs. Right now, they are only
available on select architectures when SMP and MMU are enabled. But all
of the primitives that per-VMA locks are built on (RCU, maple trees,
refcounts) work just fine without SMP or MMU.
The only real downside is that making VMAs a wee bit bigger on !MMU and
!SMP builds.
The upside is much cleaner code, lower complexity and less #ifdeffery.
Clean up a binder VMA locking site now that it can rely on per-VMA locks.
Building on top of universally-available per-VMA locks, introduce a new
helper. Since the new API does not require callers to have a fallback to
mmap_lock, it's much easier to use. Callers can potentially replace this
very common kernel idiom:
mmap_read_lock(mm);
vma = vma_lookup()
// fiddle with vma
mmap_read_unlock(mm);
with:
vma = vma_start_read_unlocked(mm, address);
// fiddle with vma
vma_end_read(vma);
Which avoids mmap_lock entirely in the fast path.
Use that new API for another binder site and one in the TCP code.
This patch (of 7):
The per-VMA locks have been around for several years. They've had some
bugs worked out of them and have seen quite wide use. However, they are
still only available when architectures explicitly enable them. Remove
the conditional compilation around the per-VMA locks, making them
available on all architectures and configs.
The approach up to now seemed to be to add ARCH_SUPPORTS_PER_VMA_LOCK when
the architecture started using per-VMA locks in the fault handler. But,
contrary to the naming, the Kconfig option does not really indicate
whether the architecture supports per-VMA locks or not. It is more of a
marker for whether the architecture is likely to benefit from per-VMA
locks.
To me, the most important thing side-effect of universal availability is
letting per-VMA locks be used in SMP=n configs. This lets us use
per-VMA locking in all x86 code without fallbacks.
Overall, this just generally makes the kernel simpler. Just look at the
diffstat. It also opens the door to users that want to use the per-VMA
locks in common code. Doing *that* brings additional simplifications.
The downside of this is adding some fields to vm_area_struct and
mm_struct. There are likely ways to optimize this, especially for things
like SMP=n configs. For now, do the simplest thing: use the same
implementation everywhere.
== Considerations for NOMMU config ==
NOMMU systems do not write-lock VMAs, therefore read-locking a VMA would
always succeed unless VMA is detached. Therefore for NOMMU config we make
vma_mark_attached() a NOOP, which keeps VMAs always in detached state.
This causes VMA read-locking to always fail and the caller falls back to
locking mmap_lock.
The following functions will have a different implementation in NOMMU
config:
- vma_mark_attached(), vma_mark_detached() are made NOOPs, keeping VMAs
always in a detached state and preventing assertions and refcount
underflows;
- vma_start_write(), vma_start_write_killable() are made NOOPs to avoid
warnings in __vma_start_write() due to VMAs being detached. These
functions are not used in NOMMU code but __vma_start_write() is an
exported function, therefore might be used by drivers.
- vma_assert_attached() is made NOOP because it's reachable from NOMMU
code via split_vma()->vma_iter_store_new()->vma_iter_store_overwrite();
- vma_assert_write_locked() is asserting vma->vm_mm is write-locked, as
was done before this change;
- vma_assert_locked() is asserting vma->vm_mm is locked, as was done
before this change;
The following functions work for both MMU and NOMMU configs:
- vma_lock_init() performs the same initialization as for MMU config;
- mm_lock_seqcount_init(), mm_lock_seqcount_begin(),
mm_lock_seqcount_end() are called from mmap_write_{lock|unlock} and
update mm_lock_seq correctly.
- mmap_lock_speculate_try_begin(), mmap_lock_speculate_retry() work as
is because mm_lock_seq is updated correctly;
- vma_start_read(), vma_start_read_locked() will always fail because
VMAs are always detached;
- vma_end_read() will never be called because vma_start_read() never
succeeds;
- vma_is_attached() always return false because VMAs are always
detached;
- vma_assert_detached() will never trigger because VMAs are never
attached;
- vma_start_read_locked() always return false because VMAs are always
detached;
- lock_vma_under_rcu() will be safe as the attempted read lock will bail;
Changes in the following files are not affecting NOMMU config:
task_mmu.c - not compiled when CONFIG_MMU=n;
pagewalk.c - not compiled when CONFIG_MMU=n;
userfaultfd.c - not compiled when CONFIG_MMU=n (CONFIG_USERFAULTFD depends
on CONFIG_MMU);
The following changes in the BPF code are made to keep NOMMU config
working like before:
stack_map_lock_vma() - keeps mmap_lock in NOMMU config;
bpf_iter_task_vma_new() - bails out in NOMMU config;
Link: https://lore.kernel.org/20260831203056.838265-1-surenb@google.com
Link: https://lore.kernel.org/20260831203056.838265-2-surenb@google.com
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Shakeel Butt <shakeel.butt@linux.dev>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Todd Kjos <tkjos@android.com>
Cc: Christian Brauner <christian@brauner.io>
Cc: Carlos Llamas <cmllamas@google.com>
Cc: Alice Ryhl <aliceryhl@google.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: David Ahern <dsahern@kernel.org>
Cc: Arve Hjønnevåg <arve@android.com>
1151 lines
31 KiB
C
1151 lines
31 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2016 Facebook
|
|
*/
|
|
#include <linux/bpf.h>
|
|
#include <linux/jhash.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/perf_event.h>
|
|
#include <linux/btf_ids.h>
|
|
#include <linux/buildid.h>
|
|
#include <linux/mmap_lock.h>
|
|
#include "percpu_freelist.h"
|
|
#include "mmap_unlock_work.h"
|
|
|
|
#define STACK_CREATE_FLAG_MASK \
|
|
(BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY | \
|
|
BPF_F_STACK_BUILD_ID)
|
|
|
|
struct stack_map_bucket {
|
|
struct pcpu_freelist_node fnode;
|
|
u32 hash;
|
|
u32 nr;
|
|
u64 data[];
|
|
};
|
|
|
|
struct bpf_stack_map {
|
|
struct bpf_map map;
|
|
void *elems;
|
|
struct pcpu_freelist freelist;
|
|
u32 n_buckets;
|
|
struct stack_map_bucket *buckets[] __counted_by(n_buckets);
|
|
};
|
|
|
|
static inline bool stack_map_use_build_id(struct bpf_map *map)
|
|
{
|
|
return (map->map_flags & BPF_F_STACK_BUILD_ID);
|
|
}
|
|
|
|
static inline int stack_map_data_size(struct bpf_map *map)
|
|
{
|
|
return stack_map_use_build_id(map) ?
|
|
sizeof(struct bpf_stack_build_id) : sizeof(u64);
|
|
}
|
|
|
|
/**
|
|
* stack_map_calculate_max_depth - Calculate maximum allowed stack trace depth
|
|
* @size: Size of the buffer/map value in bytes
|
|
* @elem_size: Size of each stack trace element
|
|
* @flags: BPF stack trace flags (BPF_F_USER_STACK, BPF_F_USER_BUILD_ID, ...)
|
|
*
|
|
* Return: Maximum number of stack trace entries that can be safely stored
|
|
*/
|
|
static u32 stack_map_calculate_max_depth(u32 size, u32 elem_size, u64 flags)
|
|
{
|
|
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
u32 max_depth;
|
|
u32 curr_sysctl_max_stack = READ_ONCE(sysctl_perf_event_max_stack);
|
|
|
|
max_depth = size / elem_size;
|
|
max_depth += skip;
|
|
if (max_depth > curr_sysctl_max_stack)
|
|
return curr_sysctl_max_stack;
|
|
|
|
return max_depth;
|
|
}
|
|
|
|
static int prealloc_elems_and_freelist(struct bpf_stack_map *smap)
|
|
{
|
|
u64 elem_size = sizeof(struct stack_map_bucket) +
|
|
(u64)smap->map.value_size;
|
|
int err;
|
|
|
|
smap->elems = bpf_map_area_alloc(elem_size * smap->map.max_entries,
|
|
smap->map.numa_node);
|
|
if (!smap->elems)
|
|
return -ENOMEM;
|
|
|
|
err = pcpu_freelist_init(&smap->freelist);
|
|
if (err)
|
|
goto free_elems;
|
|
|
|
pcpu_freelist_populate(&smap->freelist, smap->elems, elem_size,
|
|
smap->map.max_entries);
|
|
return 0;
|
|
|
|
free_elems:
|
|
bpf_map_area_free(smap->elems);
|
|
return err;
|
|
}
|
|
|
|
/* Called from syscall */
|
|
static struct bpf_map *stack_map_alloc(union bpf_attr *attr)
|
|
{
|
|
u32 value_size = attr->value_size;
|
|
struct bpf_stack_map *smap;
|
|
u64 cost, n_buckets;
|
|
int err;
|
|
|
|
if (attr->map_flags & ~STACK_CREATE_FLAG_MASK)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* check sanity of attributes */
|
|
if (attr->max_entries == 0 || attr->key_size != 4 ||
|
|
value_size < 8 || value_size % 8)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
BUILD_BUG_ON(sizeof(struct bpf_stack_build_id) % sizeof(u64));
|
|
if (attr->map_flags & BPF_F_STACK_BUILD_ID) {
|
|
if (value_size % sizeof(struct bpf_stack_build_id) ||
|
|
value_size / sizeof(struct bpf_stack_build_id)
|
|
> sysctl_perf_event_max_stack)
|
|
return ERR_PTR(-EINVAL);
|
|
} else if (value_size / 8 > sysctl_perf_event_max_stack)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* hash table size must be power of 2; roundup_pow_of_two() can overflow
|
|
* into UB on 32-bit arches, so check that first
|
|
*/
|
|
if (attr->max_entries > 1UL << 31)
|
|
return ERR_PTR(-E2BIG);
|
|
|
|
n_buckets = roundup_pow_of_two(attr->max_entries);
|
|
|
|
cost = n_buckets * sizeof(struct stack_map_bucket *) + sizeof(*smap);
|
|
smap = bpf_map_area_alloc(cost, bpf_map_attr_numa_node(attr));
|
|
if (!smap)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
bpf_map_init_from_attr(&smap->map, attr);
|
|
smap->n_buckets = n_buckets;
|
|
|
|
err = get_callchain_buffers(sysctl_perf_event_max_stack);
|
|
if (err)
|
|
goto free_smap;
|
|
|
|
err = prealloc_elems_and_freelist(smap);
|
|
if (err)
|
|
goto put_buffers;
|
|
|
|
return &smap->map;
|
|
|
|
put_buffers:
|
|
put_callchain_buffers();
|
|
free_smap:
|
|
bpf_map_area_free(smap);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static int fetch_build_id(struct vm_area_struct *vma, unsigned char *build_id, bool may_fault)
|
|
{
|
|
return may_fault ? build_id_parse(vma, build_id, NULL)
|
|
: build_id_parse_nofault(vma, build_id, NULL);
|
|
}
|
|
|
|
static inline void stack_map_build_id_set_ip(struct bpf_stack_build_id *id)
|
|
{
|
|
id->status = BPF_STACK_BUILD_ID_IP;
|
|
memset(id->build_id, 0, BUILD_ID_SIZE_MAX);
|
|
}
|
|
|
|
static inline u64 stack_map_build_id_offset(unsigned long vm_pgoff,
|
|
unsigned long vm_start, u64 ip)
|
|
{
|
|
return (vm_pgoff << PAGE_SHIFT) + ip - vm_start;
|
|
}
|
|
|
|
static inline void stack_map_build_id_set_valid(struct bpf_stack_build_id *id,
|
|
u64 offset,
|
|
const unsigned char *build_id)
|
|
{
|
|
id->status = BPF_STACK_BUILD_ID_VALID;
|
|
id->offset = offset;
|
|
if (id->build_id != build_id)
|
|
memcpy(id->build_id, build_id, BUILD_ID_SIZE_MAX);
|
|
}
|
|
|
|
/*
|
|
* A cached VMA lookup result. The range [vm_start, vm_end) is always set.
|
|
* vm_pgoff, file, build_id are set only when the build ID was resolved.
|
|
* Zero vm_end marks the slot empty. build_id aliases the id_offs[] entry.
|
|
*/
|
|
struct stack_map_cached_vma {
|
|
unsigned long vm_start;
|
|
unsigned long vm_end;
|
|
unsigned long vm_pgoff;
|
|
struct file *file; /* pinned in the sleepable path; NULL otherwise */
|
|
const unsigned char *build_id;
|
|
};
|
|
|
|
/*
|
|
* Per stack_map_get_build_id_offset() call cache of the last VMA with a build ID
|
|
* resolved and the last VMA with no usable build ID. Adjacent stack frames tend
|
|
* to land in the same VMA or the same backing file, so caching the last result
|
|
* of each kind lets us skip unnecessary VMA lookups and build ID parse calls.
|
|
* Keeping the two slots independent means a build-ID-less VMA doesn't evict the
|
|
* last resolved build ID.
|
|
*/
|
|
struct stack_map_build_id_cache {
|
|
struct stack_map_cached_vma resolved;
|
|
struct stack_map_cached_vma unresolved;
|
|
};
|
|
|
|
/*
|
|
* Fill @id from a cached range covering @ip. On a hit this writes @id (resolved
|
|
* range -> build ID + offset, unresolved range -> raw ip) and returns 0; on a
|
|
* miss it leaves @id untouched and returns -ENOENT.
|
|
*/
|
|
static int stack_map_build_id_set_from_cache(struct stack_map_build_id_cache *cache,
|
|
struct bpf_stack_build_id *id, u64 ip)
|
|
{
|
|
unsigned long vm_start, vm_end, vm_pgoff;
|
|
u64 offset;
|
|
|
|
vm_start = cache->resolved.vm_start;
|
|
vm_end = cache->resolved.vm_end;
|
|
if (vm_end && ip >= vm_start && ip < vm_end) {
|
|
vm_pgoff = cache->resolved.vm_pgoff;
|
|
offset = stack_map_build_id_offset(vm_pgoff, vm_start, ip);
|
|
stack_map_build_id_set_valid(id, offset, cache->resolved.build_id);
|
|
return 0;
|
|
}
|
|
|
|
vm_start = cache->unresolved.vm_start;
|
|
vm_end = cache->unresolved.vm_end;
|
|
if (vm_end && ip >= vm_start && ip < vm_end) {
|
|
stack_map_build_id_set_ip(id);
|
|
return 0;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*
|
|
* Record @vma's build ID as the last resolved one. @file is the pinned backing
|
|
* file in the sleepable path (released when evicted), or NULL otherwise.
|
|
*/
|
|
static void stack_map_build_id_cache_set_resolved(struct stack_map_build_id_cache *cache,
|
|
struct file *file,
|
|
const unsigned char *build_id,
|
|
unsigned long vm_start,
|
|
unsigned long vm_end,
|
|
unsigned long vm_pgoff)
|
|
{
|
|
if (cache->resolved.file)
|
|
fput(cache->resolved.file);
|
|
cache->resolved = (struct stack_map_cached_vma){
|
|
.vm_start = vm_start,
|
|
.vm_end = vm_end,
|
|
.vm_pgoff = vm_pgoff,
|
|
.file = file,
|
|
.build_id = build_id,
|
|
};
|
|
}
|
|
|
|
/* Record [vm_start, vm_end) as a range with no usable build ID. */
|
|
static void stack_map_build_id_cache_set_unresolved(struct stack_map_build_id_cache *cache,
|
|
unsigned long vm_start,
|
|
unsigned long vm_end)
|
|
{
|
|
cache->unresolved = (struct stack_map_cached_vma){
|
|
.vm_start = vm_start,
|
|
.vm_end = vm_end,
|
|
};
|
|
}
|
|
|
|
struct stack_map_vma_lock {
|
|
struct vm_area_struct *vma;
|
|
struct mm_struct *mm;
|
|
};
|
|
|
|
/*
|
|
* Acquire a stable read-side reference on the VMA covering @ip.
|
|
*
|
|
* On NOMMU configurations, returns with the mmap_lock held. If the MMU
|
|
* is enabled, the per-VMA lock will be held instead. The lock
|
|
* should be released with stack_map_unlock_vma() which will release the
|
|
* appropriate lock. Once the lock is released, the VMA may be freed.
|
|
*
|
|
* Returns NULL on failure, in which case no lock is held.
|
|
*/
|
|
static struct vm_area_struct *
|
|
stack_map_lock_vma(struct stack_map_vma_lock *lock, unsigned long ip)
|
|
{
|
|
struct mm_struct *mm = lock->mm;
|
|
struct vm_area_struct *vma;
|
|
|
|
vma = lock_vma_under_rcu(mm, ip);
|
|
if (vma) {
|
|
lock->vma = vma;
|
|
return vma;
|
|
}
|
|
|
|
/*
|
|
* Taking mmap_read_lock() is unsafe here, because the caller BPF
|
|
* program might already hold it, causing a deadlock.
|
|
*/
|
|
if (!mmap_read_trylock(mm))
|
|
return NULL;
|
|
|
|
vma = vma_lookup(mm, ip);
|
|
if (!vma) {
|
|
mmap_read_unlock(mm);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_MMU
|
|
if (!vma_start_read_locked(vma)) {
|
|
mmap_read_unlock(mm);
|
|
return NULL;
|
|
}
|
|
mmap_read_unlock(mm);
|
|
#endif
|
|
lock->vma = vma;
|
|
return vma;
|
|
}
|
|
|
|
static void stack_map_unlock_vma(struct stack_map_vma_lock *lock)
|
|
{
|
|
#ifdef CONFIG_MMU
|
|
vma_end_read(lock->vma);
|
|
#else
|
|
mmap_read_unlock(lock->mm);
|
|
#endif
|
|
lock->vma = NULL;
|
|
}
|
|
|
|
static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *id_offs,
|
|
u32 trace_nr)
|
|
{
|
|
struct stack_map_vma_lock lock = { .mm = current->mm };
|
|
struct stack_map_build_id_cache cache = {};
|
|
struct stack_map_cached_vma *res = &cache.resolved;
|
|
unsigned long vm_pgoff, vm_start, vm_end;
|
|
struct vm_area_struct *vma;
|
|
struct file *file;
|
|
u64 offset;
|
|
u64 ip;
|
|
|
|
for (u32 i = 0; i < trace_nr; i++) {
|
|
ip = READ_ONCE(id_offs[i].ip);
|
|
|
|
if (!stack_map_build_id_set_from_cache(&cache, &id_offs[i], ip))
|
|
continue;
|
|
|
|
vma = stack_map_lock_vma(&lock, ip);
|
|
if (!vma) {
|
|
stack_map_build_id_set_ip(&id_offs[i]);
|
|
continue;
|
|
}
|
|
|
|
vm_pgoff = vma->vm_pgoff;
|
|
vm_start = vma->vm_start;
|
|
vm_end = vma->vm_end;
|
|
|
|
if (vma_is_anonymous(vma) || !vma->vm_file) {
|
|
stack_map_unlock_vma(&lock);
|
|
stack_map_build_id_set_ip(&id_offs[i]);
|
|
stack_map_build_id_cache_set_unresolved(&cache, vm_start, vm_end);
|
|
continue;
|
|
}
|
|
|
|
file = vma->vm_file;
|
|
offset = stack_map_build_id_offset(vm_pgoff, vm_start, ip);
|
|
|
|
/*
|
|
* Same backing file as the last resolved VMA (another mapping
|
|
* of the same ELF binary): reuse its build_id without re-parsing.
|
|
*/
|
|
if (file == res->file) {
|
|
stack_map_unlock_vma(&lock);
|
|
stack_map_build_id_set_valid(&id_offs[i], offset, res->build_id);
|
|
res->vm_start = vm_start;
|
|
res->vm_end = vm_end;
|
|
res->vm_pgoff = vm_pgoff;
|
|
continue;
|
|
}
|
|
|
|
file = get_file(file);
|
|
stack_map_unlock_vma(&lock);
|
|
|
|
/* build_id_parse_file() may block on filesystem reads */
|
|
if (build_id_parse_file(file, id_offs[i].build_id, NULL)) {
|
|
stack_map_build_id_set_ip(&id_offs[i]);
|
|
fput(file);
|
|
stack_map_build_id_cache_set_unresolved(&cache, vm_start, vm_end);
|
|
continue;
|
|
}
|
|
|
|
stack_map_build_id_set_valid(&id_offs[i], offset, id_offs[i].build_id);
|
|
stack_map_build_id_cache_set_resolved(&cache, file, id_offs[i].build_id,
|
|
vm_start, vm_end, vm_pgoff);
|
|
}
|
|
|
|
if (res->file)
|
|
fput(res->file);
|
|
}
|
|
|
|
/*
|
|
* Expects all id_offs[i].ip values to be set to correct initial IPs.
|
|
* They will be subsequently:
|
|
* - either adjusted in place to a file offset, if build ID fetching
|
|
* succeeds; in this case id_offs[i].build_id is set to correct build ID,
|
|
* and id_offs[i].status is set to BPF_STACK_BUILD_ID_VALID;
|
|
* - or IP will be kept intact, if build ID fetching failed; in this case
|
|
* id_offs[i].build_id is zeroed out and id_offs[i].status is set to
|
|
* BPF_STACK_BUILD_ID_IP.
|
|
*/
|
|
static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
|
|
u32 trace_nr, bool user, bool may_fault)
|
|
{
|
|
struct mmap_unlock_irq_work *work;
|
|
bool has_user_ctx = user && current && current->mm;
|
|
struct stack_map_build_id_cache cache = {};
|
|
struct vm_area_struct *vma;
|
|
int i;
|
|
|
|
if (may_fault && has_user_ctx) {
|
|
stack_map_get_build_id_offset_sleepable(id_offs, trace_nr);
|
|
return;
|
|
}
|
|
|
|
if (!has_user_ctx)
|
|
goto fallback;
|
|
|
|
work = bpf_mmap_unlock_guard_get();
|
|
if (IS_ERR(work))
|
|
goto fallback;
|
|
|
|
if (!mmap_read_trylock(current->mm)) {
|
|
bpf_mmap_unlock_guard_put(work);
|
|
goto fallback;
|
|
}
|
|
|
|
for (i = 0; i < trace_nr; i++) {
|
|
u64 ip = READ_ONCE(id_offs[i].ip);
|
|
|
|
if (!stack_map_build_id_set_from_cache(&cache, &id_offs[i], ip))
|
|
continue;
|
|
|
|
vma = find_vma(current->mm, ip);
|
|
if (!vma || vma_is_anonymous(vma) ||
|
|
fetch_build_id(vma, id_offs[i].build_id, may_fault)) {
|
|
/* per entry fall back to ips; cache build-ID-less range */
|
|
stack_map_build_id_set_ip(&id_offs[i]);
|
|
if (vma)
|
|
stack_map_build_id_cache_set_unresolved(&cache,
|
|
vma->vm_start, vma->vm_end);
|
|
continue;
|
|
}
|
|
/*
|
|
* mmap_lock is held for the whole loop, so the cached VMA
|
|
* fields stay valid; no file pinning is needed here.
|
|
*/
|
|
stack_map_build_id_set_valid(&id_offs[i],
|
|
stack_map_build_id_offset(vma->vm_pgoff, vma->vm_start, ip),
|
|
id_offs[i].build_id);
|
|
stack_map_build_id_cache_set_resolved(&cache, NULL, id_offs[i].build_id,
|
|
vma->vm_start, vma->vm_end,
|
|
vma->vm_pgoff);
|
|
}
|
|
bpf_mmap_unlock_mm(work, current->mm);
|
|
return;
|
|
|
|
fallback:
|
|
/* cannot access current->mm, fall back to ips */
|
|
for (i = 0; i < trace_nr; i++)
|
|
stack_map_build_id_set_ip(&id_offs[i]);
|
|
}
|
|
|
|
static struct perf_callchain_entry *
|
|
get_callchain_entry_for_task(struct task_struct *task, u32 max_depth)
|
|
{
|
|
#ifdef CONFIG_STACKTRACE
|
|
struct perf_callchain_entry *entry;
|
|
int rctx;
|
|
|
|
entry = get_callchain_entry(&rctx);
|
|
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
entry->nr = stack_trace_save_tsk(task, (unsigned long *)entry->ip,
|
|
max_depth, 0);
|
|
|
|
/* stack_trace_save_tsk() works on unsigned long array, while
|
|
* perf_callchain_entry uses u64 array. For 32-bit systems, it is
|
|
* necessary to fix this mismatch.
|
|
*/
|
|
if (__BITS_PER_LONG != 64) {
|
|
unsigned long *from = (unsigned long *) entry->ip;
|
|
u64 *to = entry->ip;
|
|
int i;
|
|
|
|
/* copy data from the end to avoid using extra buffer */
|
|
for (i = entry->nr - 1; i >= 0; i--)
|
|
to[i] = (u64)(from[i]);
|
|
}
|
|
|
|
put_callchain_entry(rctx);
|
|
|
|
return entry;
|
|
#else /* CONFIG_STACKTRACE */
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
struct stackid {
|
|
struct stack_map_bucket *bucket;
|
|
const u64 *ips;
|
|
u32 nr;
|
|
u32 len;
|
|
u32 hash;
|
|
u32 id;
|
|
bool hash_matches;
|
|
};
|
|
|
|
static int stackid_init(struct stackid *stackid, struct bpf_map *map,
|
|
const struct perf_callchain_entry *trace, u32 trace_nr, u64 flags)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
u32 max_depth;
|
|
|
|
if (trace_nr <= skip)
|
|
/* skipping more than usable stack trace */
|
|
return -EFAULT;
|
|
|
|
max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags);
|
|
stackid->nr = min_t(u32, trace_nr - skip, max_depth - skip);
|
|
stackid->len = stackid->nr * sizeof(u64);
|
|
stackid->ips = trace->ip + skip;
|
|
stackid->hash = jhash2((const u32 *)stackid->ips, stackid->len / sizeof(u32), 0);
|
|
stackid->id = stackid->hash & (smap->n_buckets - 1);
|
|
stackid->bucket = READ_ONCE(smap->buckets[stackid->id]);
|
|
stackid->hash_matches = stackid->bucket && stackid->bucket->hash == stackid->hash;
|
|
return 0;
|
|
}
|
|
|
|
static int stackid_fastpath(struct stackid *stackid, struct bpf_map *map,
|
|
const struct perf_callchain_entry *trace, u32 trace_nr,
|
|
u64 flags)
|
|
{
|
|
int err;
|
|
|
|
err = stackid_init(stackid, map, trace, trace_nr, flags);
|
|
if (err)
|
|
return err;
|
|
|
|
/* fast cmp */
|
|
if (stackid->hash_matches && flags & BPF_F_FAST_STACK_CMP)
|
|
return stackid->id;
|
|
|
|
if (stack_map_use_build_id(map))
|
|
return -ENOENT;
|
|
if (stackid->hash_matches && stackid->bucket->nr == stackid->nr &&
|
|
memcmp(stackid->bucket->data, stackid->ips, stackid->len) == 0)
|
|
return stackid->id;
|
|
if (stackid->bucket && !(flags & BPF_F_REUSE_STACKID))
|
|
return -EEXIST;
|
|
return -ENOENT;
|
|
}
|
|
|
|
static struct stack_map_bucket *
|
|
stackid_new_bucket(struct stackid *stackid, struct bpf_map *map)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
struct bpf_stack_build_id *id_offs;
|
|
struct stack_map_bucket *bucket;
|
|
u32 i;
|
|
|
|
bucket = (struct stack_map_bucket *) pcpu_freelist_pop(&smap->freelist);
|
|
if (unlikely(!bucket))
|
|
return NULL;
|
|
|
|
if (stack_map_use_build_id(map)) {
|
|
id_offs = (struct bpf_stack_build_id *)bucket->data;
|
|
for (i = 0; i < stackid->nr; i++)
|
|
id_offs[i].ip = stackid->ips[i];
|
|
} else {
|
|
memcpy(bucket->data, stackid->ips, stackid->len);
|
|
}
|
|
|
|
bucket->hash = stackid->hash;
|
|
bucket->nr = stackid->nr;
|
|
return bucket;
|
|
}
|
|
|
|
static long stackid_install(struct stackid *stackid, struct bpf_map *map,
|
|
struct stack_map_bucket *new_bucket, u64 flags)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
bool user = flags & BPF_F_USER_STACK;
|
|
struct stack_map_bucket *old_bucket;
|
|
u32 trace_len;
|
|
|
|
if (stack_map_use_build_id(map)) {
|
|
struct bpf_stack_build_id *id_offs;
|
|
|
|
id_offs = (struct bpf_stack_build_id *)new_bucket->data;
|
|
stack_map_get_build_id_offset(id_offs, stackid->nr, user, false /* !may_fault */);
|
|
trace_len = stackid->nr * sizeof(struct bpf_stack_build_id);
|
|
if (stackid->hash_matches && stackid->bucket->nr == stackid->nr &&
|
|
memcmp(stackid->bucket->data, new_bucket->data, trace_len) == 0) {
|
|
pcpu_freelist_push(&smap->freelist, &new_bucket->fnode);
|
|
return stackid->id;
|
|
}
|
|
if (stackid->bucket && !(flags & BPF_F_REUSE_STACKID)) {
|
|
pcpu_freelist_push(&smap->freelist, &new_bucket->fnode);
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
old_bucket = xchg(&smap->buckets[stackid->id], new_bucket);
|
|
if (old_bucket)
|
|
pcpu_freelist_push(&smap->freelist, &old_bucket->fnode);
|
|
return stackid->id;
|
|
}
|
|
|
|
BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,
|
|
u64, flags)
|
|
{
|
|
u32 elem_size = stack_map_data_size(map);
|
|
bool user = flags & BPF_F_USER_STACK;
|
|
struct stack_map_bucket *new_bucket;
|
|
struct perf_callchain_entry *trace;
|
|
struct stackid stackid;
|
|
bool kernel = !user;
|
|
u32 max_depth;
|
|
int err;
|
|
|
|
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
|
|
BPF_F_FAST_STACK_CMP | BPF_F_REUSE_STACKID)))
|
|
return -EINVAL;
|
|
|
|
max_depth = stack_map_calculate_max_depth(map->value_size, elem_size, flags);
|
|
|
|
scoped_guard(preempt) {
|
|
trace = get_perf_callchain(regs, kernel, user, max_depth,
|
|
false, false, 0);
|
|
if (unlikely(!trace))
|
|
/* couldn't fetch the stack trace */
|
|
return -EFAULT;
|
|
|
|
err = stackid_fastpath(&stackid, map, trace, trace->nr, flags);
|
|
if (err != -ENOENT)
|
|
return err;
|
|
|
|
new_bucket = stackid_new_bucket(&stackid, map);
|
|
if (!new_bucket)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return stackid_install(&stackid, map, new_bucket, flags);
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_stackid_proto = {
|
|
.func = bpf_get_stackid,
|
|
.gpl_only = true,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg2_type = ARG_CONST_MAP_PTR,
|
|
.arg3_type = ARG_ANYTHING,
|
|
};
|
|
|
|
static __u64 count_kernel_ip(const struct perf_callchain_entry *trace)
|
|
{
|
|
__u64 nr_kernel = 0;
|
|
|
|
while (nr_kernel < trace->nr) {
|
|
if (trace->ip[nr_kernel] == PERF_CONTEXT_USER)
|
|
break;
|
|
nr_kernel++;
|
|
}
|
|
return nr_kernel;
|
|
}
|
|
|
|
BPF_CALL_3(bpf_get_stackid_pe, struct bpf_perf_event_data_kern *, ctx,
|
|
struct bpf_map *, map, u64, flags)
|
|
{
|
|
const struct perf_callchain_entry *trace;
|
|
struct perf_event *event = ctx->event;
|
|
struct stack_map_bucket *new_bucket;
|
|
struct stackid stackid;
|
|
bool kernel, user;
|
|
__u64 nr_kernel;
|
|
u32 trace_nr;
|
|
int ret;
|
|
|
|
/* perf_sample_data doesn't have callchain, use bpf_get_stackid */
|
|
if (!(event->attr.sample_type & PERF_SAMPLE_CALLCHAIN))
|
|
return bpf_get_stackid((unsigned long)(ctx->regs),
|
|
(unsigned long) map, flags, 0, 0);
|
|
|
|
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
|
|
BPF_F_FAST_STACK_CMP | BPF_F_REUSE_STACKID)))
|
|
return -EINVAL;
|
|
|
|
user = flags & BPF_F_USER_STACK;
|
|
kernel = !user;
|
|
|
|
trace = ctx->data->callchain;
|
|
if (unlikely(!trace))
|
|
return -EFAULT;
|
|
|
|
nr_kernel = count_kernel_ip(trace);
|
|
|
|
if (kernel) {
|
|
trace_nr = nr_kernel;
|
|
} else { /* user */
|
|
u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
|
|
trace_nr = trace->nr;
|
|
skip += nr_kernel;
|
|
if (skip > BPF_F_SKIP_FIELD_MASK)
|
|
return -EFAULT;
|
|
|
|
flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip;
|
|
}
|
|
|
|
ret = stackid_fastpath(&stackid, map, trace, trace_nr, flags);
|
|
if (ret != -ENOENT)
|
|
return ret;
|
|
|
|
new_bucket = stackid_new_bucket(&stackid, map);
|
|
if (new_bucket)
|
|
return stackid_install(&stackid, map, new_bucket, flags);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_stackid_proto_pe = {
|
|
.func = bpf_get_stackid_pe,
|
|
.gpl_only = false,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg2_type = ARG_CONST_MAP_PTR,
|
|
.arg3_type = ARG_ANYTHING,
|
|
};
|
|
|
|
static u32 callchain_store(const struct perf_callchain_entry *trace, u32 trace_nr,
|
|
void *buf, u32 elem_size, u64 flags)
|
|
{
|
|
bool user_build_id = flags & BPF_F_USER_BUILD_ID;
|
|
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
const u64 *ips;
|
|
u32 copy_len;
|
|
|
|
trace_nr = trace_nr - skip;
|
|
copy_len = trace_nr * elem_size;
|
|
|
|
ips = trace->ip + skip;
|
|
if (user_build_id) {
|
|
struct bpf_stack_build_id *id_offs = buf;
|
|
|
|
for (u32 i = 0; i < trace_nr; i++)
|
|
id_offs[i].ip = ips[i];
|
|
} else {
|
|
memcpy(buf, ips, copy_len);
|
|
}
|
|
return trace_nr;
|
|
}
|
|
|
|
static long callchain_finalize(void *buf, u32 size, u32 trace_nr, u32 elem_size,
|
|
u64 flags, bool may_fault)
|
|
{
|
|
bool user_build_id = flags & BPF_F_USER_BUILD_ID;
|
|
bool user = flags & BPF_F_USER_STACK;
|
|
u32 copy_len = trace_nr * elem_size;
|
|
|
|
if (user_build_id)
|
|
stack_map_get_build_id_offset(buf, trace_nr, user, may_fault);
|
|
|
|
if (size > copy_len)
|
|
memset(buf + copy_len, 0, size - copy_len);
|
|
return copy_len;
|
|
}
|
|
|
|
static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
|
|
void *buf, u32 size, u64 flags, bool may_fault)
|
|
{
|
|
bool user_build_id = flags & BPF_F_USER_BUILD_ID;
|
|
bool crosstask = task && task != current;
|
|
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
bool user = flags & BPF_F_USER_STACK;
|
|
struct perf_callchain_entry *trace;
|
|
u32 trace_nr, elem_size, max_depth;
|
|
bool kernel = !user;
|
|
int err = -EINVAL;
|
|
|
|
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
|
|
BPF_F_USER_BUILD_ID)))
|
|
goto clear;
|
|
if (kernel && user_build_id)
|
|
goto clear;
|
|
|
|
elem_size = user_build_id ? sizeof(struct bpf_stack_build_id) : sizeof(u64);
|
|
if (unlikely(size % elem_size))
|
|
goto clear;
|
|
|
|
/* cannot get valid user stack for task without user_mode regs */
|
|
if (task && user && !user_mode(regs))
|
|
goto err_fault;
|
|
|
|
/* get_perf_callchain does not support crosstask user stack walking
|
|
* but returns an empty stack instead of NULL.
|
|
*/
|
|
if (crosstask && user) {
|
|
err = -EOPNOTSUPP;
|
|
goto clear;
|
|
}
|
|
|
|
max_depth = stack_map_calculate_max_depth(size, elem_size, flags);
|
|
|
|
preempt_disable();
|
|
if (may_fault)
|
|
rcu_read_lock(); /* need RCU for perf's callchain below */
|
|
|
|
if (kernel && task) {
|
|
trace = get_callchain_entry_for_task(task, max_depth);
|
|
} else {
|
|
trace = get_perf_callchain(regs, kernel, user, max_depth,
|
|
crosstask, false, 0);
|
|
}
|
|
|
|
if (unlikely(!trace) || trace->nr < skip) {
|
|
if (may_fault)
|
|
rcu_read_unlock();
|
|
preempt_enable();
|
|
goto err_fault;
|
|
}
|
|
|
|
trace_nr = callchain_store(trace, trace->nr, buf, elem_size, flags);
|
|
|
|
/* trace should not be dereferenced after this point */
|
|
if (may_fault)
|
|
rcu_read_unlock();
|
|
preempt_enable();
|
|
|
|
return callchain_finalize(buf, size, trace_nr, elem_size, flags, may_fault);
|
|
|
|
err_fault:
|
|
err = -EFAULT;
|
|
clear:
|
|
memset(buf, 0, size);
|
|
return err;
|
|
}
|
|
|
|
BPF_CALL_4(bpf_get_stack, struct pt_regs *, regs, void *, buf, u32, size,
|
|
u64, flags)
|
|
{
|
|
return __bpf_get_stack(regs, NULL, buf, size, flags, false /* !may_fault */);
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_stack_proto = {
|
|
.func = bpf_get_stack,
|
|
.gpl_only = true,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
|
|
.arg3_type = ARG_MEM_SIZE_OR_ZERO,
|
|
.arg4_type = ARG_ANYTHING,
|
|
};
|
|
|
|
BPF_CALL_4(bpf_get_stack_sleepable, struct pt_regs *, regs, void *, buf, u32, size,
|
|
u64, flags)
|
|
{
|
|
return __bpf_get_stack(regs, NULL, buf, size, flags, true /* may_fault */);
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_stack_sleepable_proto = {
|
|
.func = bpf_get_stack_sleepable,
|
|
.gpl_only = true,
|
|
.might_sleep = true,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
|
|
.arg3_type = ARG_MEM_SIZE_OR_ZERO,
|
|
.arg4_type = ARG_ANYTHING,
|
|
};
|
|
|
|
static long __bpf_get_task_stack(struct task_struct *task, void *buf, u32 size,
|
|
u64 flags, bool may_fault)
|
|
{
|
|
struct pt_regs *regs;
|
|
long res = -EINVAL;
|
|
|
|
if (!try_get_task_stack(task)) {
|
|
memset(buf, 0, size);
|
|
return -EFAULT;
|
|
}
|
|
|
|
regs = task_pt_regs(task);
|
|
if (regs)
|
|
res = __bpf_get_stack(regs, task, buf, size, flags, may_fault);
|
|
else
|
|
memset(buf, 0, size);
|
|
put_task_stack(task);
|
|
return res;
|
|
}
|
|
|
|
BPF_CALL_4(bpf_get_task_stack, struct task_struct *, task, void *, buf,
|
|
u32, size, u64, flags)
|
|
{
|
|
return __bpf_get_task_stack(task, buf, size, flags, false /* !may_fault */);
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_task_stack_proto = {
|
|
.func = bpf_get_task_stack,
|
|
.gpl_only = false,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_BTF_ID,
|
|
.arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
|
|
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
|
|
.arg3_type = ARG_MEM_SIZE_OR_ZERO,
|
|
.arg4_type = ARG_ANYTHING,
|
|
};
|
|
|
|
BPF_CALL_4(bpf_get_task_stack_sleepable, struct task_struct *, task, void *, buf,
|
|
u32, size, u64, flags)
|
|
{
|
|
return __bpf_get_task_stack(task, buf, size, flags, true /* !may_fault */);
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_task_stack_sleepable_proto = {
|
|
.func = bpf_get_task_stack_sleepable,
|
|
.gpl_only = false,
|
|
.might_sleep = true,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_BTF_ID,
|
|
.arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK],
|
|
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
|
|
.arg3_type = ARG_MEM_SIZE_OR_ZERO,
|
|
.arg4_type = ARG_ANYTHING,
|
|
};
|
|
|
|
static int __bpf_get_stack_pe(const struct perf_callchain_entry *trace, u32 trace_nr,
|
|
void *buf, u32 size, u64 flags)
|
|
{
|
|
bool user_build_id = flags & BPF_F_USER_BUILD_ID;
|
|
u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
bool user = flags & BPF_F_USER_STACK;
|
|
u32 elem_size, max_depth, nr_trace;
|
|
bool kernel = !user;
|
|
|
|
if (kernel && user_build_id)
|
|
return -EINVAL;
|
|
|
|
elem_size = user_build_id ? sizeof(struct bpf_stack_build_id) : sizeof(u64);
|
|
if (unlikely(size % elem_size))
|
|
return -EINVAL;
|
|
|
|
max_depth = stack_map_calculate_max_depth(size, elem_size, flags);
|
|
trace_nr = min_t(u32, trace_nr, max_depth);
|
|
|
|
if (trace_nr < skip)
|
|
return -EFAULT;
|
|
|
|
nr_trace = callchain_store(trace, trace_nr, buf, elem_size, flags);
|
|
return callchain_finalize(buf, size, nr_trace, elem_size, flags, false /* !may_fault */);
|
|
}
|
|
|
|
BPF_CALL_4(bpf_get_stack_pe, struct bpf_perf_event_data_kern *, ctx,
|
|
void *, buf, u32, size, u64, flags)
|
|
{
|
|
struct pt_regs *regs = (struct pt_regs *)(ctx->regs);
|
|
const struct perf_callchain_entry *trace;
|
|
struct perf_event *event = ctx->event;
|
|
bool kernel, user;
|
|
int err = -EINVAL;
|
|
__u64 nr_kernel;
|
|
|
|
if (!(event->attr.sample_type & PERF_SAMPLE_CALLCHAIN))
|
|
return __bpf_get_stack(regs, NULL, buf, size, flags, false /* !may_fault */);
|
|
|
|
if (unlikely(flags & ~(BPF_F_SKIP_FIELD_MASK | BPF_F_USER_STACK |
|
|
BPF_F_USER_BUILD_ID)))
|
|
goto clear;
|
|
|
|
user = flags & BPF_F_USER_STACK;
|
|
kernel = !user;
|
|
|
|
err = -EFAULT;
|
|
trace = ctx->data->callchain;
|
|
if (unlikely(!trace))
|
|
goto clear;
|
|
|
|
nr_kernel = count_kernel_ip(trace);
|
|
|
|
if (kernel) {
|
|
err = __bpf_get_stack_pe(trace, nr_kernel, buf, size, flags);
|
|
} else { /* user */
|
|
u64 skip = flags & BPF_F_SKIP_FIELD_MASK;
|
|
|
|
skip += nr_kernel;
|
|
if (skip > BPF_F_SKIP_FIELD_MASK)
|
|
goto clear;
|
|
flags = (flags & ~BPF_F_SKIP_FIELD_MASK) | skip;
|
|
err = __bpf_get_stack_pe(trace, trace->nr, buf, size, flags);
|
|
}
|
|
|
|
clear:
|
|
if (err < 0)
|
|
memset(buf, 0, size);
|
|
return err;
|
|
|
|
}
|
|
|
|
const struct bpf_func_proto bpf_get_stack_proto_pe = {
|
|
.func = bpf_get_stack_pe,
|
|
.gpl_only = true,
|
|
.ret_type = RET_INTEGER,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg2_type = ARG_PTR_TO_UNINIT_MEM,
|
|
.arg3_type = ARG_MEM_SIZE_OR_ZERO,
|
|
.arg4_type = ARG_ANYTHING,
|
|
};
|
|
|
|
/* Called from eBPF program */
|
|
static void *stack_map_lookup_elem(struct bpf_map *map, void *key)
|
|
{
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
}
|
|
|
|
/* Called from syscall */
|
|
static int stack_map_lookup_and_delete_elem(struct bpf_map *map, void *key,
|
|
void *value, u64 flags)
|
|
{
|
|
return bpf_stackmap_extract(map, key, value, true);
|
|
}
|
|
|
|
/* Called from syscall */
|
|
int bpf_stackmap_extract(struct bpf_map *map, void *key, void *value,
|
|
bool delete)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
struct stack_map_bucket *bucket, *old_bucket;
|
|
u32 id = *(u32 *)key, trace_len;
|
|
|
|
if (unlikely(id >= smap->n_buckets))
|
|
return -ENOENT;
|
|
|
|
bucket = xchg(&smap->buckets[id], NULL);
|
|
if (!bucket)
|
|
return -ENOENT;
|
|
|
|
trace_len = bucket->nr * stack_map_data_size(map);
|
|
memcpy(value, bucket->data, trace_len);
|
|
memset(value + trace_len, 0, map->value_size - trace_len);
|
|
|
|
if (delete)
|
|
old_bucket = bucket;
|
|
else
|
|
old_bucket = xchg(&smap->buckets[id], bucket);
|
|
if (old_bucket)
|
|
pcpu_freelist_push(&smap->freelist, &old_bucket->fnode);
|
|
return 0;
|
|
}
|
|
|
|
static int stack_map_get_next_key(struct bpf_map *map, void *key,
|
|
void *next_key)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map,
|
|
struct bpf_stack_map, map);
|
|
u32 id;
|
|
|
|
WARN_ON_ONCE(!rcu_read_lock_held());
|
|
|
|
if (!key) {
|
|
id = 0;
|
|
} else {
|
|
id = *(u32 *)key;
|
|
if (id >= smap->n_buckets || !smap->buckets[id])
|
|
id = 0;
|
|
else
|
|
id++;
|
|
}
|
|
|
|
while (id < smap->n_buckets && !smap->buckets[id])
|
|
id++;
|
|
|
|
if (id >= smap->n_buckets)
|
|
return -ENOENT;
|
|
|
|
*(u32 *)next_key = id;
|
|
return 0;
|
|
}
|
|
|
|
static long stack_map_update_elem(struct bpf_map *map, void *key, void *value,
|
|
u64 map_flags)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Called from syscall or from eBPF program */
|
|
static long stack_map_delete_elem(struct bpf_map *map, void *key)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
struct stack_map_bucket *old_bucket;
|
|
u32 id = *(u32 *)key;
|
|
|
|
if (unlikely(id >= smap->n_buckets))
|
|
return -E2BIG;
|
|
|
|
old_bucket = xchg(&smap->buckets[id], NULL);
|
|
if (old_bucket) {
|
|
pcpu_freelist_push(&smap->freelist, &old_bucket->fnode);
|
|
return 0;
|
|
} else {
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
/* Called when map->refcnt goes to zero, either from workqueue or from syscall */
|
|
static void stack_map_free(struct bpf_map *map)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
|
|
bpf_map_area_free(smap->elems);
|
|
pcpu_freelist_destroy(&smap->freelist);
|
|
bpf_map_area_free(smap);
|
|
put_callchain_buffers();
|
|
}
|
|
|
|
static u64 stack_map_mem_usage(const struct bpf_map *map)
|
|
{
|
|
struct bpf_stack_map *smap = container_of(map, struct bpf_stack_map, map);
|
|
u64 value_size = map->value_size;
|
|
u64 n_buckets = smap->n_buckets;
|
|
u64 enties = map->max_entries;
|
|
u64 usage = sizeof(*smap);
|
|
|
|
usage += n_buckets * sizeof(struct stack_map_bucket *);
|
|
usage += enties * (sizeof(struct stack_map_bucket) + value_size);
|
|
return usage;
|
|
}
|
|
|
|
BTF_ID_LIST_SINGLE(stack_trace_map_btf_ids, struct, bpf_stack_map)
|
|
const struct bpf_map_ops stack_trace_map_ops = {
|
|
.map_meta_equal = bpf_map_meta_equal,
|
|
.map_alloc = stack_map_alloc,
|
|
.map_free = stack_map_free,
|
|
.map_get_next_key = stack_map_get_next_key,
|
|
.map_lookup_elem = stack_map_lookup_elem,
|
|
.map_lookup_and_delete_elem = stack_map_lookup_and_delete_elem,
|
|
.map_update_elem = stack_map_update_elem,
|
|
.map_delete_elem = stack_map_delete_elem,
|
|
.map_check_btf = map_check_no_btf,
|
|
.map_mem_usage = stack_map_mem_usage,
|
|
.map_btf_id = &stack_trace_map_btf_ids[0],
|
|
};
|