Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | // SPDX-License-Identifier: GPL-2.0-or-later /* * Module proc support * * Copyright (C) 2008 Alexey Dobriyan */ #include <linux/module.h> #include <linux/kallsyms.h> #include <linux/mutex.h> #include <linux/seq_file.h> #include <linux/proc_fs.h> #include "internal.h" #ifdef CONFIG_MODULE_UNLOAD static inline void print_unload_info(struct seq_file *m, struct module *mod) { struct module_use *use; int printed_something = 0; seq_printf(m, " %i ", module_refcount(mod)); /* * Always include a trailing , so userspace can differentiate * between this and the old multi-field proc format. */ list_for_each_entry(use, &mod->source_list, source_list) { printed_something = 1; seq_printf(m, "%s,", use->source->name); } if (mod->init && !mod->exit) { printed_something = 1; seq_puts(m, "[permanent],"); } if (!printed_something) seq_puts(m, "-"); } #else /* !CONFIG_MODULE_UNLOAD */ static inline void print_unload_info(struct seq_file *m, struct module *mod) { /* We don't know the usage count, or what modules are using. */ seq_puts(m, " - -"); } #endif /* CONFIG_MODULE_UNLOAD */ /* Called by the /proc file system to return a list of modules. */ static void *m_start(struct seq_file *m, loff_t *pos) { mutex_lock(&module_mutex); return seq_list_start(&modules, *pos); } static void *m_next(struct seq_file *m, void *p, loff_t *pos) { return seq_list_next(p, &modules, pos); } static void m_stop(struct seq_file *m, void *p) { mutex_unlock(&module_mutex); } static unsigned int module_total_size(struct module *mod) { int size = 0; for_each_mod_mem_type(type) size += mod->mem[type].size; return size; } static int m_show(struct seq_file *m, void *p) { struct module *mod = list_entry(p, struct module, list); char buf[MODULE_FLAGS_BUF_SIZE]; void *value; unsigned int size; /* We always ignore unformed modules. */ if (mod->state == MODULE_STATE_UNFORMED) return 0; size = module_total_size(mod); seq_printf(m, "%s %u", mod->name, size); print_unload_info(m, mod); /* Informative for users. */ seq_printf(m, " %s", mod->state == MODULE_STATE_GOING ? "Unloading" : mod->state == MODULE_STATE_COMING ? "Loading" : "Live"); /* Used by oprofile and other similar tools. */ value = m->private ? NULL : mod->mem[MOD_TEXT].base; seq_printf(m, " 0x%px", value); /* Taints info */ if (mod->taints) seq_printf(m, " %s", module_flags(mod, buf, true)); seq_puts(m, "\n"); return 0; } /* * Format: modulename size refcount deps address * * Where refcount is a number or -, and deps is a comma-separated list * of depends or -. */ static const struct seq_operations modules_op = { .start = m_start, .next = m_next, .stop = m_stop, .show = m_show }; /* * This also sets the "private" pointer to non-NULL if the * kernel pointers should be hidden (so you can just test * "m->private" to see if you should keep the values private). * * We use the same logic as for /proc/kallsyms. */ static int modules_open(struct inode *inode, struct file *file) { int err = seq_open(file, &modules_op); if (!err) { struct seq_file *m = file->private_data; m->private = kallsyms_show_value(file->f_cred) ? NULL : (void *)8ul; } return err; } static const struct proc_ops modules_proc_ops = { .proc_flags = PROC_ENTRY_PERMANENT, .proc_open = modules_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = seq_release, }; static int __init proc_modules_init(void) { proc_create("modules", 0, NULL, &modules_proc_ops); return 0; } module_init(proc_modules_init); |