diff mbox series

[RFC,4/6] module, modpost: introduce support for MODULE_SYSCTL_TABLE

Message ID 20220722022416.137548-5-mfo@canonical.com (mailing list archive)
State New, archived
Headers show
Series Introduce "sysctl:" module aliases | expand

Commit Message

Mauricio Faria de Oliveira July 22, 2022, 2:24 a.m. UTC
Now that 'struct ctl_table' is exposed to modpost/file2alias,
introduce MODULE_SYSCTL_TABLE to register some sysctl tables,
just like MODULE_DEVICE_TABLE for, eg, drivers PCI ID tables.

This adds a '__mod_sysctl__<name>_device_table' symbol in the
module that points to the array of 'struct ctl_table' entries.

This is identified and handled by file2alias, as other tables,
that emits MODULE_ALIAS("sysctl:<struct ctl_table.procname>")
statements for each entry.

That would be relatively simple for 'procname' as a char array
(eg, do_{rpmsg,i2c,spi}_entry()) but since it's a char pointer
an ELF relocation is used (it sets that pointer value to where
the string it should point to actually ends up).

...

It's probably not ideal to convert 'struct ctl_table.procname'
to a char array, as the max length it uses in some modules is
long, and all other users would pay the memory price of a one-
size-fits-all array size.

Also, some places set it to NULL, which requires special care
and changes (i.e., set/check the first byte is '\0').

Anyway, the resulting disadvantages in the general case aren't
worth the simplicity gain in this particular case.

...

So, add ELF relocation handling code, borrowing that function
we factored-out in modpost.c earlier.

The logic and details are commented in the source, but briefly:

0) modpost.c calls file2alias.c's handle_moddevtable()

1) handle_moddevtable()
1.1) matches a '__mod__sysctl__..._device_table' symbol
1.2) calls do_sysctl_table() for it (array of struct ctl_table)

2) do_sysctl_table()
2.1) finds the relocation section that _references_ the section
     that defines that symbol (ie, that holds the actual values
     of 'procname' char pointers to be replaced in that symbol).
2.2) calls do_sysctl_entry() for each entry in that table/array

3) do_sysctl_entry()
3.1) calls do_sysctl_section_relx() to scan a relocation section
     for the relocation entry that targets a particular procname
     pointer of this entry (struct ctl_table).
3.2) do_sysctl_section_relx() returns its source string pointer.
3.3) do_sysctl_entry() stores 'sysctl:<procname>' in the buffer.

4) do_sysctl_table() emits a 'MODULE_ALIAS()' statement with it.

...

This algorithm is likely iterating over the earlier relocation
entries many times as each 'struct ctl_table' entry starts the
loop over the relocation section again, but keep it simple now.

We could keep the relocation entry cursor pointer across calls,
and start over only when we can't find a relocation, I guess.

Signed-off-by: Mauricio Faria de Oliveira <mfo@canonical.com>
---
 include/linux/module.h   |   7 +++
 scripts/mod/file2alias.c | 111 +++++++++++++++++++++++++++++++++++++++
 scripts/mod/modpost.c    |   4 +-
 scripts/mod/modpost.h    |   3 ++
 4 files changed, 123 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/include/linux/module.h b/include/linux/module.h
index 518296ea7f73..3010f687df19 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -247,6 +247,13 @@  extern typeof(name) __mod_##type##__##name##_device_table		\
 #define MODULE_DEVICE_TABLE(type, name)
 #endif
 
+#if defined(MODULE) && defined(CONFIG_PROC_SYSCTL)
+/* Creates an alias so file2alias.c can find sysctl "device" table. */
+#define MODULE_SYSCTL_TABLE(name) MODULE_DEVICE_TABLE(sysctl, name)
+#else /* !MODULE || !CONFIG_PROC_SYSCTL */
+#define MODULE_SYSCTL_TABLE(name)
+#endif
+
 /* Version of form [<epoch>:]<version>[-<extra-version>].
  * Or for CVS/RCS ID version, everything but the number is stripped.
  * <epoch>: A (small) unsigned integer which allows you to start versions
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index cbd6b0f48b4e..3bf9cb84c548 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1452,6 +1452,115 @@  static int do_dfl_entry(const char *filename, void *symval, char *alias)
 	return 1;
 }
 
+/*
+ * Scan the relocation section with section header index 'relx_shndx'
+ * for the relocation entry for target 'offset' and return a pointer
+ * to its source value (from another section/offset).
+ *
+ * The caller must ensure sechdr->sh_type == SHT_RELA or SHT_REL.
+ */
+static void *do_sysctl_section_relx(struct elf_info *info,
+				    unsigned int relx_shndx, int offset)
+{
+	/* Relocation section's header, and start/stop addresses. */
+	Elf_Shdr *sechdr = &info->sechdrs[relx_shndx];
+	Elf_Rela *start = (void *)info->hdr + sechdr->sh_offset;
+	Elf_Rela *stop  = (void *)start + sechdr->sh_size;
+
+	/* Relocation entry cursor and size in SHT_RELA or SHT_REL. */
+	Elf_Rela *relx; /* access .r_addend in SHT_RELA _only_! */
+	size_t relx_size;
+
+	if (sechdr->sh_type == SHT_RELA)
+		relx_size = sizeof(Elf_Rela);
+	else if (sechdr->sh_type == SHT_REL)
+		relx_size = sizeof(Elf_Rel);
+	else
+		return NULL;
+
+	for (relx = start; relx < stop; relx = (void *)relx + relx_size) {
+		/*
+		 * 'r' is the relocation entry, applied to the 'target offset'
+		 * in the 'target section' of this (relocation) section, with
+		 * the value from symbol 'sym' (in/at 'source section/offset').
+		 */
+		Elf_Rela r;
+		Elf_Sym *sym;
+		unsigned int sym_shndx;
+		int sym_offset;
+
+		if (get_relx_sym(info, sechdr, relx, &r, &sym))
+			continue;
+
+		/* Looking for this target offset. */
+		if (r.r_offset != offset)
+			continue;
+
+		/* Pointer to source section/offset (note: addend is needed). */
+		sym_shndx = get_secindex(info, sym);
+		sym_offset = sym->st_value;
+		return (void *)info->hdr + info->sechdrs[sym_shndx].sh_offset
+			+ sym_offset + r.r_addend;
+	}
+
+	return NULL;
+}
+
+/* Looks like: sysctl:S */
+static int do_sysctl_entry(const char *modname, int sym_entry_offset,
+			   char *alias, const char *symname,
+			   unsigned int relx_shndx, struct elf_info *info)
+{
+	/* Find the relocation entry for procname's offset and use its string */
+	int offset = sym_entry_offset + OFF_sysctl_device_id_procname;
+	const char *procname = do_sysctl_section_relx(info, relx_shndx, offset);
+
+	if (procname) {
+		sprintf(alias, "sysctl:%s", procname);
+		return 1;
+	}
+
+	error("%s: [%s.ko] cannot find relocation string.\n", symname, modname);
+	return 0;
+}
+
+static void do_sysctl_table(void *symval, unsigned long size,
+			    struct module *mod, const char *symname,
+			    Elf_Sym *sym, struct elf_info *info)
+{
+	unsigned long id_size = SIZE_sysctl_device_id;
+	unsigned int i, secindex, shndx;
+	char alias[ALIAS_SIZE];
+
+	device_id_check(mod->name, "sysctl", size, id_size, symval);
+	/* Leave last one: it's the terminator. */
+	size -= id_size;
+
+	/* Find relocation section that references the section w/ the symbol. */
+	shndx = sym->st_shndx;
+	for (secindex = 0; secindex < info->num_sections; secindex++) {
+		Elf_Shdr *shdr = &info->sechdrs[secindex];
+
+		if ((shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) &&
+		    (shdr->sh_flags & SHF_INFO_LINK) && shdr->sh_info == shndx)
+			break;
+	}
+
+	if (secindex == info->num_sections) {
+		error("%s: [%s.ko] cannot find relocation section.\n",
+		      symname, mod->name);
+		return;
+	}
+
+	/* The symbol is an array of struct ctl_table elements at offset 'i'. */
+	for (i = 0; i < size; i += id_size) {
+		if (do_sysctl_entry(mod->name, sym->st_value+i, alias, symname, secindex, info)) {
+			buf_printf(&mod->dev_table_buf,
+				   "MODULE_ALIAS(\"%s\");\n", alias);
+		}
+	}
+}
+
 /* Does namelen bytes of name exactly match the symbol? */
 static bool sym_is(const char *name, unsigned namelen, const char *symbol)
 {
@@ -1585,6 +1694,8 @@  void handle_moddevtable(struct module *mod, struct elf_info *info,
 		do_pnp_device_entry(symval, sym->st_size, mod);
 	else if (sym_is(name, namelen, "pnp_card"))
 		do_pnp_card_entries(symval, sym->st_size, mod);
+	if (sym_is(name, namelen, "sysctl"))
+		do_sysctl_table(symval, sym->st_size, mod, symname, sym, info);
 	else {
 		int i;
 
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index d1ed67fa290b..e2df2fbb0909 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -1735,8 +1735,8 @@  static int addend_mips_rel(struct elf_info *elf, Elf_Shdr *sechdr, Elf_Rela *r)
  * w/ possible relocation addend 'r.r_addend').
  * - 'sym' is that symbol (a pointer to the symbol table + symbol table index).
  */
-static int get_relx_sym(struct elf_info *elf, Elf_Shdr *sechdr, Elf_Rela *rela,
-			Elf_Rela *out_r, Elf_Sym **out_sym)
+int get_relx_sym(struct elf_info *elf, Elf_Shdr *sechdr, Elf_Rela *rela,
+		 Elf_Rela *out_r, Elf_Sym **out_sym)
 {
 	Elf_Sym *sym;
 	Elf_Rela r;
diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
index 044bdfb894b7..cdb95c7e03a9 100644
--- a/scripts/mod/modpost.h
+++ b/scripts/mod/modpost.h
@@ -212,3 +212,6 @@  void modpost_log(enum loglevel loglevel, const char *fmt, ...);
 #define warn(fmt, args...)	modpost_log(LOG_WARN, fmt, ##args)
 #define error(fmt, args...)	modpost_log(LOG_ERROR, fmt, ##args)
 #define fatal(fmt, args...)	modpost_log(LOG_FATAL, fmt, ##args)
+
+int get_relx_sym(struct elf_info *elf, Elf_Shdr *sechdr, Elf_Rela *rela,
+			Elf_Rela *out_r, Elf_Sym **out_sym);