mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 00:35:01 +00:00
e269b5d291
vm_module_tags_populate() calculation of the populated area assumes that
area starts at a page boundary and therefore when new pages are allocation,
the end of the area is page-aligned as well. If the start of the area is
not page-aligned then allocating a page and incrementing the end of the
area by PAGE_SIZE leads to an area at the end but within the area boundary
which is not populated. Accessing this are will lead to a kernel panic.
Fix the calculation by down-aligning the start of the area and using that
as the location allocated pages are mapped to.
[gehao@kylinos.cn: fix vm_module_tags_populate's KASAN poisoning logic]
Link: https://lkml.kernel.org/r/20241205170528.81000-1-hao.ge@linux.dev
[gehao@kylinos.cn: fix panic when CONFIG_KASAN enabled and CONFIG_KASAN_VMALLOC not enabled]
Link: https://lkml.kernel.org/r/20241212072126.134572-1-hao.ge@linux.dev
Link: https://lkml.kernel.org/r/20241130001423.1114965-1-surenb@google.com
Fixes: 0f9b685626
("alloc_tag: populate memory for module tags as needed")
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
Reported-by: kernel test robot <oliver.sang@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202411132111.6a221562-lkp@intel.com
Acked-by: Yu Zhao <yuzhao@google.com>
Tested-by: Adrian Huang <ahuang12@lenovo.com>
Cc: David Wang <00107082@163.com>
Cc: Kent Overstreet <kent.overstreet@linux.dev>
Cc: Mike Rapoport (Microsoft) <rppt@kernel.org>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: Sourav Panda <souravpanda@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
767 lines
19 KiB
C
767 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#include <linux/alloc_tag.h>
|
|
#include <linux/execmem.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/module.h>
|
|
#include <linux/page_ext.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_buf.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#define ALLOCINFO_FILE_NAME "allocinfo"
|
|
#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag))
|
|
#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME)
|
|
#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME)
|
|
|
|
#ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT
|
|
static bool mem_profiling_support = true;
|
|
#else
|
|
static bool mem_profiling_support;
|
|
#endif
|
|
|
|
static struct codetag_type *alloc_tag_cttype;
|
|
|
|
DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag);
|
|
EXPORT_SYMBOL(_shared_alloc_tag);
|
|
|
|
DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT,
|
|
mem_alloc_profiling_key);
|
|
DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed);
|
|
|
|
struct alloc_tag_kernel_section kernel_tags = { NULL, 0 };
|
|
unsigned long alloc_tag_ref_mask;
|
|
int alloc_tag_ref_offs;
|
|
|
|
struct allocinfo_private {
|
|
struct codetag_iterator iter;
|
|
bool print_header;
|
|
};
|
|
|
|
static void *allocinfo_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
struct allocinfo_private *priv;
|
|
struct codetag *ct;
|
|
loff_t node = *pos;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
m->private = priv;
|
|
if (!priv)
|
|
return NULL;
|
|
|
|
priv->print_header = (node == 0);
|
|
codetag_lock_module_list(alloc_tag_cttype, true);
|
|
priv->iter = codetag_get_ct_iter(alloc_tag_cttype);
|
|
while ((ct = codetag_next_ct(&priv->iter)) != NULL && node)
|
|
node--;
|
|
|
|
return ct ? priv : NULL;
|
|
}
|
|
|
|
static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos)
|
|
{
|
|
struct allocinfo_private *priv = (struct allocinfo_private *)arg;
|
|
struct codetag *ct = codetag_next_ct(&priv->iter);
|
|
|
|
(*pos)++;
|
|
if (!ct)
|
|
return NULL;
|
|
|
|
return priv;
|
|
}
|
|
|
|
static void allocinfo_stop(struct seq_file *m, void *arg)
|
|
{
|
|
struct allocinfo_private *priv = (struct allocinfo_private *)m->private;
|
|
|
|
if (priv) {
|
|
codetag_lock_module_list(alloc_tag_cttype, false);
|
|
kfree(priv);
|
|
}
|
|
}
|
|
|
|
static void print_allocinfo_header(struct seq_buf *buf)
|
|
{
|
|
/* Output format version, so we can change it. */
|
|
seq_buf_printf(buf, "allocinfo - version: 1.0\n");
|
|
seq_buf_printf(buf, "# <size> <calls> <tag info>\n");
|
|
}
|
|
|
|
static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct)
|
|
{
|
|
struct alloc_tag *tag = ct_to_alloc_tag(ct);
|
|
struct alloc_tag_counters counter = alloc_tag_read(tag);
|
|
s64 bytes = counter.bytes;
|
|
|
|
seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls);
|
|
codetag_to_text(out, ct);
|
|
seq_buf_putc(out, ' ');
|
|
seq_buf_putc(out, '\n');
|
|
}
|
|
|
|
static int allocinfo_show(struct seq_file *m, void *arg)
|
|
{
|
|
struct allocinfo_private *priv = (struct allocinfo_private *)arg;
|
|
char *bufp;
|
|
size_t n = seq_get_buf(m, &bufp);
|
|
struct seq_buf buf;
|
|
|
|
seq_buf_init(&buf, bufp, n);
|
|
if (priv->print_header) {
|
|
print_allocinfo_header(&buf);
|
|
priv->print_header = false;
|
|
}
|
|
alloc_tag_to_text(&buf, priv->iter.ct);
|
|
seq_commit(m, seq_buf_used(&buf));
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations allocinfo_seq_op = {
|
|
.start = allocinfo_start,
|
|
.next = allocinfo_next,
|
|
.stop = allocinfo_stop,
|
|
.show = allocinfo_show,
|
|
};
|
|
|
|
size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
|
|
{
|
|
struct codetag_iterator iter;
|
|
struct codetag *ct;
|
|
struct codetag_bytes n;
|
|
unsigned int i, nr = 0;
|
|
|
|
if (can_sleep)
|
|
codetag_lock_module_list(alloc_tag_cttype, true);
|
|
else if (!codetag_trylock_module_list(alloc_tag_cttype))
|
|
return 0;
|
|
|
|
iter = codetag_get_ct_iter(alloc_tag_cttype);
|
|
while ((ct = codetag_next_ct(&iter))) {
|
|
struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct));
|
|
|
|
n.ct = ct;
|
|
n.bytes = counter.bytes;
|
|
|
|
for (i = 0; i < nr; i++)
|
|
if (n.bytes > tags[i].bytes)
|
|
break;
|
|
|
|
if (i < count) {
|
|
nr -= nr == count;
|
|
memmove(&tags[i + 1],
|
|
&tags[i],
|
|
sizeof(tags[0]) * (nr - i));
|
|
nr++;
|
|
tags[i] = n;
|
|
}
|
|
}
|
|
|
|
codetag_lock_module_list(alloc_tag_cttype, false);
|
|
|
|
return nr;
|
|
}
|
|
|
|
void pgalloc_tag_split(struct folio *folio, int old_order, int new_order)
|
|
{
|
|
int i;
|
|
struct alloc_tag *tag;
|
|
unsigned int nr_pages = 1 << new_order;
|
|
|
|
if (!mem_alloc_profiling_enabled())
|
|
return;
|
|
|
|
tag = pgalloc_tag_get(&folio->page);
|
|
if (!tag)
|
|
return;
|
|
|
|
for (i = nr_pages; i < (1 << old_order); i += nr_pages) {
|
|
union pgtag_ref_handle handle;
|
|
union codetag_ref ref;
|
|
|
|
if (get_page_tag_ref(folio_page(folio, i), &ref, &handle)) {
|
|
/* Set new reference to point to the original tag */
|
|
alloc_tag_ref_set(&ref, tag);
|
|
update_page_tag_ref(handle, &ref);
|
|
put_page_tag_ref(handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pgalloc_tag_swap(struct folio *new, struct folio *old)
|
|
{
|
|
union pgtag_ref_handle handle_old, handle_new;
|
|
union codetag_ref ref_old, ref_new;
|
|
struct alloc_tag *tag_old, *tag_new;
|
|
|
|
tag_old = pgalloc_tag_get(&old->page);
|
|
if (!tag_old)
|
|
return;
|
|
tag_new = pgalloc_tag_get(&new->page);
|
|
if (!tag_new)
|
|
return;
|
|
|
|
if (!get_page_tag_ref(&old->page, &ref_old, &handle_old))
|
|
return;
|
|
if (!get_page_tag_ref(&new->page, &ref_new, &handle_new)) {
|
|
put_page_tag_ref(handle_old);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Clear tag references to avoid debug warning when using
|
|
* __alloc_tag_ref_set() with non-empty reference.
|
|
*/
|
|
set_codetag_empty(&ref_old);
|
|
set_codetag_empty(&ref_new);
|
|
|
|
/* swap tags */
|
|
__alloc_tag_ref_set(&ref_old, tag_new);
|
|
update_page_tag_ref(handle_old, &ref_old);
|
|
__alloc_tag_ref_set(&ref_new, tag_old);
|
|
update_page_tag_ref(handle_new, &ref_new);
|
|
|
|
put_page_tag_ref(handle_old);
|
|
put_page_tag_ref(handle_new);
|
|
}
|
|
|
|
static void shutdown_mem_profiling(bool remove_file)
|
|
{
|
|
if (mem_alloc_profiling_enabled())
|
|
static_branch_disable(&mem_alloc_profiling_key);
|
|
|
|
if (!mem_profiling_support)
|
|
return;
|
|
|
|
if (remove_file)
|
|
remove_proc_entry(ALLOCINFO_FILE_NAME, NULL);
|
|
mem_profiling_support = false;
|
|
}
|
|
|
|
static void __init procfs_init(void)
|
|
{
|
|
if (!mem_profiling_support)
|
|
return;
|
|
|
|
if (!proc_create_seq(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op)) {
|
|
pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
|
|
shutdown_mem_profiling(false);
|
|
}
|
|
}
|
|
|
|
void __init alloc_tag_sec_init(void)
|
|
{
|
|
struct alloc_tag *last_codetag;
|
|
|
|
if (!mem_profiling_support)
|
|
return;
|
|
|
|
if (!static_key_enabled(&mem_profiling_compressed))
|
|
return;
|
|
|
|
kernel_tags.first_tag = (struct alloc_tag *)kallsyms_lookup_name(
|
|
SECTION_START(ALLOC_TAG_SECTION_NAME));
|
|
last_codetag = (struct alloc_tag *)kallsyms_lookup_name(
|
|
SECTION_STOP(ALLOC_TAG_SECTION_NAME));
|
|
kernel_tags.count = last_codetag - kernel_tags.first_tag;
|
|
|
|
/* Check if kernel tags fit into page flags */
|
|
if (kernel_tags.count > (1UL << NR_UNUSED_PAGEFLAG_BITS)) {
|
|
shutdown_mem_profiling(false); /* allocinfo file does not exist yet */
|
|
pr_err("%lu allocation tags cannot be references using %d available page flag bits. Memory allocation profiling is disabled!\n",
|
|
kernel_tags.count, NR_UNUSED_PAGEFLAG_BITS);
|
|
return;
|
|
}
|
|
|
|
alloc_tag_ref_offs = (LRU_REFS_PGOFF - NR_UNUSED_PAGEFLAG_BITS);
|
|
alloc_tag_ref_mask = ((1UL << NR_UNUSED_PAGEFLAG_BITS) - 1);
|
|
pr_debug("Memory allocation profiling compression is using %d page flag bits!\n",
|
|
NR_UNUSED_PAGEFLAG_BITS);
|
|
}
|
|
|
|
#ifdef CONFIG_MODULES
|
|
|
|
static struct maple_tree mod_area_mt = MTREE_INIT(mod_area_mt, MT_FLAGS_ALLOC_RANGE);
|
|
static struct vm_struct *vm_module_tags;
|
|
/* A dummy object used to indicate an unloaded module */
|
|
static struct module unloaded_mod;
|
|
/* A dummy object used to indicate a module prepended area */
|
|
static struct module prepend_mod;
|
|
|
|
struct alloc_tag_module_section module_tags;
|
|
|
|
static inline unsigned long alloc_tag_align(unsigned long val)
|
|
{
|
|
if (!static_key_enabled(&mem_profiling_compressed)) {
|
|
/* No alignment requirements when we are not indexing the tags */
|
|
return val;
|
|
}
|
|
|
|
if (val % sizeof(struct alloc_tag) == 0)
|
|
return val;
|
|
return ((val / sizeof(struct alloc_tag)) + 1) * sizeof(struct alloc_tag);
|
|
}
|
|
|
|
static bool ensure_alignment(unsigned long align, unsigned int *prepend)
|
|
{
|
|
if (!static_key_enabled(&mem_profiling_compressed)) {
|
|
/* No alignment requirements when we are not indexing the tags */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* If alloc_tag size is not a multiple of required alignment, tag
|
|
* indexing does not work.
|
|
*/
|
|
if (!IS_ALIGNED(sizeof(struct alloc_tag), align))
|
|
return false;
|
|
|
|
/* Ensure prepend consumes multiple of alloc_tag-sized blocks */
|
|
if (*prepend)
|
|
*prepend = alloc_tag_align(*prepend);
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool tags_addressable(void)
|
|
{
|
|
unsigned long tag_idx_count;
|
|
|
|
if (!static_key_enabled(&mem_profiling_compressed))
|
|
return true; /* with page_ext tags are always addressable */
|
|
|
|
tag_idx_count = CODETAG_ID_FIRST + kernel_tags.count +
|
|
module_tags.size / sizeof(struct alloc_tag);
|
|
|
|
return tag_idx_count < (1UL << NR_UNUSED_PAGEFLAG_BITS);
|
|
}
|
|
|
|
static bool needs_section_mem(struct module *mod, unsigned long size)
|
|
{
|
|
if (!mem_profiling_support)
|
|
return false;
|
|
|
|
return size >= sizeof(struct alloc_tag);
|
|
}
|
|
|
|
static struct alloc_tag *find_used_tag(struct alloc_tag *from, struct alloc_tag *to)
|
|
{
|
|
while (from <= to) {
|
|
struct alloc_tag_counters counter;
|
|
|
|
counter = alloc_tag_read(from);
|
|
if (counter.bytes)
|
|
return from;
|
|
from++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Called with mod_area_mt locked */
|
|
static void clean_unused_module_areas_locked(void)
|
|
{
|
|
MA_STATE(mas, &mod_area_mt, 0, module_tags.size);
|
|
struct module *val;
|
|
|
|
mas_for_each(&mas, val, module_tags.size) {
|
|
if (val != &unloaded_mod)
|
|
continue;
|
|
|
|
/* Release area if all tags are unused */
|
|
if (!find_used_tag((struct alloc_tag *)(module_tags.start_addr + mas.index),
|
|
(struct alloc_tag *)(module_tags.start_addr + mas.last)))
|
|
mas_erase(&mas);
|
|
}
|
|
}
|
|
|
|
/* Called with mod_area_mt locked */
|
|
static bool find_aligned_area(struct ma_state *mas, unsigned long section_size,
|
|
unsigned long size, unsigned int prepend, unsigned long align)
|
|
{
|
|
bool cleanup_done = false;
|
|
|
|
repeat:
|
|
/* Try finding exact size and hope the start is aligned */
|
|
if (!mas_empty_area(mas, 0, section_size - 1, prepend + size)) {
|
|
if (IS_ALIGNED(mas->index + prepend, align))
|
|
return true;
|
|
|
|
/* Try finding larger area to align later */
|
|
mas_reset(mas);
|
|
if (!mas_empty_area(mas, 0, section_size - 1,
|
|
size + prepend + align - 1))
|
|
return true;
|
|
}
|
|
|
|
/* No free area, try cleanup stale data and repeat the search once */
|
|
if (!cleanup_done) {
|
|
clean_unused_module_areas_locked();
|
|
cleanup_done = true;
|
|
mas_reset(mas);
|
|
goto repeat;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int vm_module_tags_populate(void)
|
|
{
|
|
unsigned long phys_end = ALIGN_DOWN(module_tags.start_addr, PAGE_SIZE) +
|
|
(vm_module_tags->nr_pages << PAGE_SHIFT);
|
|
unsigned long new_end = module_tags.start_addr + module_tags.size;
|
|
|
|
if (phys_end < new_end) {
|
|
struct page **next_page = vm_module_tags->pages + vm_module_tags->nr_pages;
|
|
unsigned long old_shadow_end = ALIGN(phys_end, MODULE_ALIGN);
|
|
unsigned long new_shadow_end = ALIGN(new_end, MODULE_ALIGN);
|
|
unsigned long more_pages;
|
|
unsigned long nr;
|
|
|
|
more_pages = ALIGN(new_end - phys_end, PAGE_SIZE) >> PAGE_SHIFT;
|
|
nr = alloc_pages_bulk_array_node(GFP_KERNEL | __GFP_NOWARN,
|
|
NUMA_NO_NODE, more_pages, next_page);
|
|
if (nr < more_pages ||
|
|
vmap_pages_range(phys_end, phys_end + (nr << PAGE_SHIFT), PAGE_KERNEL,
|
|
next_page, PAGE_SHIFT) < 0) {
|
|
/* Clean up and error out */
|
|
for (int i = 0; i < nr; i++)
|
|
__free_page(next_page[i]);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
vm_module_tags->nr_pages += nr;
|
|
|
|
/*
|
|
* Kasan allocates 1 byte of shadow for every 8 bytes of data.
|
|
* When kasan_alloc_module_shadow allocates shadow memory,
|
|
* its unit of allocation is a page.
|
|
* Therefore, here we need to align to MODULE_ALIGN.
|
|
*/
|
|
if (old_shadow_end < new_shadow_end)
|
|
kasan_alloc_module_shadow((void *)old_shadow_end,
|
|
new_shadow_end - old_shadow_end,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
/*
|
|
* Mark the pages as accessible, now that they are mapped.
|
|
* With hardware tag-based KASAN, marking is skipped for
|
|
* non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
|
|
*/
|
|
kasan_unpoison_vmalloc((void *)module_tags.start_addr,
|
|
new_end - module_tags.start_addr,
|
|
KASAN_VMALLOC_PROT_NORMAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *reserve_module_tags(struct module *mod, unsigned long size,
|
|
unsigned int prepend, unsigned long align)
|
|
{
|
|
unsigned long section_size = module_tags.end_addr - module_tags.start_addr;
|
|
MA_STATE(mas, &mod_area_mt, 0, section_size - 1);
|
|
unsigned long offset;
|
|
void *ret = NULL;
|
|
|
|
/* If no tags return error */
|
|
if (size < sizeof(struct alloc_tag))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/*
|
|
* align is always power of 2, so we can use IS_ALIGNED and ALIGN.
|
|
* align 0 or 1 means no alignment, to simplify set to 1.
|
|
*/
|
|
if (!align)
|
|
align = 1;
|
|
|
|
if (!ensure_alignment(align, &prepend)) {
|
|
shutdown_mem_profiling(true);
|
|
pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n",
|
|
mod->name, align);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
mas_lock(&mas);
|
|
if (!find_aligned_area(&mas, section_size, size, prepend, align)) {
|
|
ret = ERR_PTR(-ENOMEM);
|
|
goto unlock;
|
|
}
|
|
|
|
/* Mark found area as reserved */
|
|
offset = mas.index;
|
|
offset += prepend;
|
|
offset = ALIGN(offset, align);
|
|
if (offset != mas.index) {
|
|
unsigned long pad_start = mas.index;
|
|
|
|
mas.last = offset - 1;
|
|
mas_store(&mas, &prepend_mod);
|
|
if (mas_is_err(&mas)) {
|
|
ret = ERR_PTR(xa_err(mas.node));
|
|
goto unlock;
|
|
}
|
|
mas.index = offset;
|
|
mas.last = offset + size - 1;
|
|
mas_store(&mas, mod);
|
|
if (mas_is_err(&mas)) {
|
|
mas.index = pad_start;
|
|
mas_erase(&mas);
|
|
ret = ERR_PTR(xa_err(mas.node));
|
|
}
|
|
} else {
|
|
mas.last = offset + size - 1;
|
|
mas_store(&mas, mod);
|
|
if (mas_is_err(&mas))
|
|
ret = ERR_PTR(xa_err(mas.node));
|
|
}
|
|
unlock:
|
|
mas_unlock(&mas);
|
|
|
|
if (IS_ERR(ret))
|
|
return ret;
|
|
|
|
if (module_tags.size < offset + size) {
|
|
int grow_res;
|
|
|
|
module_tags.size = offset + size;
|
|
if (mem_alloc_profiling_enabled() && !tags_addressable()) {
|
|
shutdown_mem_profiling(true);
|
|
pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n",
|
|
mod->name, NR_UNUSED_PAGEFLAG_BITS);
|
|
}
|
|
|
|
grow_res = vm_module_tags_populate();
|
|
if (grow_res) {
|
|
shutdown_mem_profiling(true);
|
|
pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n",
|
|
mod->name);
|
|
return ERR_PTR(grow_res);
|
|
}
|
|
}
|
|
|
|
return (struct alloc_tag *)(module_tags.start_addr + offset);
|
|
}
|
|
|
|
static void release_module_tags(struct module *mod, bool used)
|
|
{
|
|
MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size);
|
|
struct alloc_tag *tag;
|
|
struct module *val;
|
|
|
|
mas_lock(&mas);
|
|
mas_for_each_rev(&mas, val, 0)
|
|
if (val == mod)
|
|
break;
|
|
|
|
if (!val) /* module not found */
|
|
goto out;
|
|
|
|
if (!used)
|
|
goto release_area;
|
|
|
|
/* Find out if the area is used */
|
|
tag = find_used_tag((struct alloc_tag *)(module_tags.start_addr + mas.index),
|
|
(struct alloc_tag *)(module_tags.start_addr + mas.last));
|
|
if (tag) {
|
|
struct alloc_tag_counters counter = alloc_tag_read(tag);
|
|
|
|
pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n",
|
|
tag->ct.filename, tag->ct.lineno, tag->ct.modname,
|
|
tag->ct.function, counter.bytes);
|
|
} else {
|
|
used = false;
|
|
}
|
|
release_area:
|
|
mas_store(&mas, used ? &unloaded_mod : NULL);
|
|
val = mas_prev_range(&mas, 0);
|
|
if (val == &prepend_mod)
|
|
mas_store(&mas, NULL);
|
|
out:
|
|
mas_unlock(&mas);
|
|
}
|
|
|
|
static void replace_module(struct module *mod, struct module *new_mod)
|
|
{
|
|
MA_STATE(mas, &mod_area_mt, 0, module_tags.size);
|
|
struct module *val;
|
|
|
|
mas_lock(&mas);
|
|
mas_for_each(&mas, val, module_tags.size) {
|
|
if (val != mod)
|
|
continue;
|
|
|
|
mas_store_gfp(&mas, new_mod, GFP_KERNEL);
|
|
break;
|
|
}
|
|
mas_unlock(&mas);
|
|
}
|
|
|
|
static int __init alloc_mod_tags_mem(void)
|
|
{
|
|
/* Map space to copy allocation tags */
|
|
vm_module_tags = execmem_vmap(MODULE_ALLOC_TAG_VMAP_SIZE);
|
|
if (!vm_module_tags) {
|
|
pr_err("Failed to map %lu bytes for module allocation tags\n",
|
|
MODULE_ALLOC_TAG_VMAP_SIZE);
|
|
module_tags.start_addr = 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
vm_module_tags->pages = kmalloc_array(get_vm_area_size(vm_module_tags) >> PAGE_SHIFT,
|
|
sizeof(struct page *), GFP_KERNEL | __GFP_ZERO);
|
|
if (!vm_module_tags->pages) {
|
|
free_vm_area(vm_module_tags);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
module_tags.start_addr = (unsigned long)vm_module_tags->addr;
|
|
module_tags.end_addr = module_tags.start_addr + MODULE_ALLOC_TAG_VMAP_SIZE;
|
|
/* Ensure the base is alloc_tag aligned when required for indexing */
|
|
module_tags.start_addr = alloc_tag_align(module_tags.start_addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __init free_mod_tags_mem(void)
|
|
{
|
|
int i;
|
|
|
|
module_tags.start_addr = 0;
|
|
for (i = 0; i < vm_module_tags->nr_pages; i++)
|
|
__free_page(vm_module_tags->pages[i]);
|
|
kfree(vm_module_tags->pages);
|
|
free_vm_area(vm_module_tags);
|
|
}
|
|
|
|
#else /* CONFIG_MODULES */
|
|
|
|
static inline int alloc_mod_tags_mem(void) { return 0; }
|
|
static inline void free_mod_tags_mem(void) {}
|
|
|
|
#endif /* CONFIG_MODULES */
|
|
|
|
/* See: Documentation/mm/allocation-profiling.rst */
|
|
static int __init setup_early_mem_profiling(char *str)
|
|
{
|
|
bool compressed = false;
|
|
bool enable;
|
|
|
|
if (!str || !str[0])
|
|
return -EINVAL;
|
|
|
|
if (!strncmp(str, "never", 5)) {
|
|
enable = false;
|
|
mem_profiling_support = false;
|
|
pr_info("Memory allocation profiling is disabled!\n");
|
|
} else {
|
|
char *token = strsep(&str, ",");
|
|
|
|
if (kstrtobool(token, &enable))
|
|
return -EINVAL;
|
|
|
|
if (str) {
|
|
|
|
if (strcmp(str, "compressed"))
|
|
return -EINVAL;
|
|
|
|
compressed = true;
|
|
}
|
|
mem_profiling_support = true;
|
|
pr_info("Memory allocation profiling is enabled %s compression and is turned %s!\n",
|
|
compressed ? "with" : "without", enable ? "on" : "off");
|
|
}
|
|
|
|
if (enable != mem_alloc_profiling_enabled()) {
|
|
if (enable)
|
|
static_branch_enable(&mem_alloc_profiling_key);
|
|
else
|
|
static_branch_disable(&mem_alloc_profiling_key);
|
|
}
|
|
if (compressed != static_key_enabled(&mem_profiling_compressed)) {
|
|
if (compressed)
|
|
static_branch_enable(&mem_profiling_compressed);
|
|
else
|
|
static_branch_disable(&mem_profiling_compressed);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling);
|
|
|
|
static __init bool need_page_alloc_tagging(void)
|
|
{
|
|
if (static_key_enabled(&mem_profiling_compressed))
|
|
return false;
|
|
|
|
return mem_profiling_support;
|
|
}
|
|
|
|
static __init void init_page_alloc_tagging(void)
|
|
{
|
|
}
|
|
|
|
struct page_ext_operations page_alloc_tagging_ops = {
|
|
.size = sizeof(union codetag_ref),
|
|
.need = need_page_alloc_tagging,
|
|
.init = init_page_alloc_tagging,
|
|
};
|
|
EXPORT_SYMBOL(page_alloc_tagging_ops);
|
|
|
|
#ifdef CONFIG_SYSCTL
|
|
static struct ctl_table memory_allocation_profiling_sysctls[] = {
|
|
{
|
|
.procname = "mem_profiling",
|
|
.data = &mem_alloc_profiling_key,
|
|
#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG
|
|
.mode = 0444,
|
|
#else
|
|
.mode = 0644,
|
|
#endif
|
|
.proc_handler = proc_do_static_key,
|
|
},
|
|
};
|
|
|
|
static void __init sysctl_init(void)
|
|
{
|
|
if (!mem_profiling_support)
|
|
memory_allocation_profiling_sysctls[0].mode = 0444;
|
|
|
|
register_sysctl_init("vm", memory_allocation_profiling_sysctls);
|
|
}
|
|
#else /* CONFIG_SYSCTL */
|
|
static inline void sysctl_init(void) {}
|
|
#endif /* CONFIG_SYSCTL */
|
|
|
|
static int __init alloc_tag_init(void)
|
|
{
|
|
const struct codetag_type_desc desc = {
|
|
.section = ALLOC_TAG_SECTION_NAME,
|
|
.tag_size = sizeof(struct alloc_tag),
|
|
#ifdef CONFIG_MODULES
|
|
.needs_section_mem = needs_section_mem,
|
|
.alloc_section_mem = reserve_module_tags,
|
|
.free_section_mem = release_module_tags,
|
|
.module_replaced = replace_module,
|
|
#endif
|
|
};
|
|
int res;
|
|
|
|
res = alloc_mod_tags_mem();
|
|
if (res)
|
|
return res;
|
|
|
|
alloc_tag_cttype = codetag_register_type(&desc);
|
|
if (IS_ERR(alloc_tag_cttype)) {
|
|
free_mod_tags_mem();
|
|
return PTR_ERR(alloc_tag_cttype);
|
|
}
|
|
|
|
sysctl_init();
|
|
procfs_init();
|
|
|
|
return 0;
|
|
}
|
|
module_init(alloc_tag_init);
|