// SPDX-License-Identifier: GPL-2.0-only
/*
* linux / arch / arm / kernel / module . c
*
* Copyright ( C ) 2002 Russell King .
* Modified for nommu by Hyok S . Choi
*
* Module allocation method suggested by Andi Kleen .
*/
#include <linux/module.h>
#include <linux/moduleloader.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/elf.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <asm /sections.h>
#include <asm /smp_plat.h>
#include <asm /unwind.h>
#include <asm /opcodes.h>
bool module_init_section(const char *name)
{
return strstarts(name, ".init" ) ||
strstarts(name, ".ARM.extab.init" ) ||
strstarts(name, ".ARM.exidx.init" );
}
bool module_exit_section(const char *name)
{
return strstarts(name, ".exit" ) ||
strstarts(name, ".ARM.extab.exit" ) ||
strstarts(name, ".ARM.exidx.exit" );
}
#ifdef CONFIG_ARM_HAS_GROUP_RELOCS
/*
* This implements the partitioning algorithm for group relocations as
* documented in the ARM AArch32 ELF psABI ( IHI 0044 ) .
*
* A single PC - relative symbol reference is divided in up to 3 add or subtract
* operations , where the final one could be incorporated into a load / store
* instruction with immediate offset . E . g . ,
*
* ADD Rd , PC , # . . . or ADD Rd , PC , # . . .
* ADD Rd , Rd , # . . . ADD Rd , Rd , # . . .
* LDR Rd , [ Rd , # . . . ] ADD Rd , Rd , # . . .
*
* The latter has a guaranteed range of only 16 MiB ( 3 x8 = = 24 bits ) , so it is
* of limited use in the kernel . However , the ADD / ADD / LDR combo has a range of
* - / + 256 MiB , ( 2 x8 + 12 = = 28 bits ) , which means it has sufficient range for
* any in - kernel symbol reference ( unless module PLTs are being used ) .
*
* The main advantage of this approach over the typical pattern using a literal
* load is that literal loads may miss in the D - cache , and generally lead to
* lower cache efficiency for variables that are referenced often from many
* different places in the code .
*/
static u32 get_group_rem(u32 group, u32 *offset)
{
u32 val = *offset;
u32 shift;
do {
shift = val ? (31 - __fls(val)) & ~1 : 32 ;
*offset = val;
if (!val)
break ;
val &= 0 xffffff >> shift;
} while (group--);
return shift;
}
#endif
int
apply_relocate(Elf32_Shdr *sechdrs, const char *strtab, unsigned int symindex,
unsigned int relindex, struct module *module)
{
Elf32_Shdr *symsec = sechdrs + symindex;
Elf32_Shdr *relsec = sechdrs + relindex;
Elf32_Shdr *dstsec = sechdrs + relsec->sh_info;
Elf32_Rel *rel = (void *)relsec->sh_addr;
unsigned int i;
for (i = 0 ; i < relsec->sh_size / sizeof (Elf32_Rel); i++, rel++) {
unsigned long loc;
Elf32_Sym *sym;
const char *symname;
#ifdef CONFIG_ARM_HAS_GROUP_RELOCS
u32 shift, group = 1 ;
#endif
s32 offset;
u32 tmp;
#ifdef CONFIG_THUMB2_KERNEL
u32 upper, lower, sign, j1, j2;
#endif
offset = ELF32_R_SYM(rel->r_info);
if (offset < 0 || offset > (symsec->sh_size / sizeof (Elf32_Sym))) {
pr_err("%s: section %u reloc %u: bad relocation sym offset\n" ,
module->name, relindex, i);
return -ENOEXEC;
}
sym = ((Elf32_Sym *)symsec->sh_addr) + offset;
symname = strtab + sym->st_name;
if (rel->r_offset < 0 || rel->r_offset > dstsec->sh_size - sizeof (u32)) {
pr_err("%s: section %u reloc %u sym '%s': out of bounds relocation, offset %d size %u\n" ,
module->name, relindex, i, symname,
rel->r_offset, dstsec->sh_size);
return -ENOEXEC;
}
loc = dstsec->sh_addr + rel->r_offset;
switch (ELF32_R_TYPE(rel->r_info)) {
case R_ARM_NONE:
/* ignore */
break ;
case R_ARM_ABS32:
case R_ARM_TARGET1:
*(u32 *)loc += sym->st_value;
break ;
case R_ARM_PC24:
case R_ARM_CALL:
case R_ARM_JUMP24:
if (sym->st_value & 3 ) {
pr_err("%s: section %u reloc %u sym '%s': unsupported interworking call (ARM -> Thumb)\n" ,
module->name, relindex, i, symname);
return -ENOEXEC;
}
offset = __mem_to_opcode_arm(*(u32 *)loc);
offset = (offset & 0 x00ffffff) << 2 ;
offset = sign_extend32(offset, 25 );
offset += sym->st_value - loc;
/*
* Route through a PLT entry if ' offset ' exceeds the
* supported range . Note that ' offset + loc + 8 '
* contains the absolute jump target , i . e . ,
* @ sym + addend , corrected for the + 8 PC bias .
*/
if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) &&
(offset <= (s32)0 xfe000000 ||
offset >= (s32)0 x02000000))
offset = get_module_plt(module, loc,
offset + loc + 8 )
- loc - 8 ;
if (offset <= (s32)0 xfe000000 ||
offset >= (s32)0 x02000000) {
pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n" ,
module->name, relindex, i, symname,
ELF32_R_TYPE(rel->r_info), loc,
sym->st_value);
return -ENOEXEC;
}
offset >>= 2 ;
offset &= 0 x00ffffff;
*(u32 *)loc &= __opcode_to_mem_arm(0 xff000000);
*(u32 *)loc |= __opcode_to_mem_arm(offset);
break ;
case R_ARM_V4BX:
/* Preserve Rm and the condition code. Alter
* other bits to re - code instruction as
* MOV PC , Rm .
*/
*(u32 *)loc &= __opcode_to_mem_arm(0 xf000000f);
*(u32 *)loc |= __opcode_to_mem_arm(0 x01a0f000);
break ;
case R_ARM_PREL31:
offset = (*(s32 *)loc << 1 ) >> 1 ; /* sign extend */
offset += sym->st_value - loc;
if (offset >= 0 x40000000 || offset < -0 x40000000) {
pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n" ,
module->name, relindex, i, symname,
ELF32_R_TYPE(rel->r_info), loc,
sym->st_value);
return -ENOEXEC;
}
*(u32 *)loc &= 0 x80000000;
*(u32 *)loc |= offset & 0 x7fffffff;
break ;
case R_ARM_REL32:
*(u32 *)loc += sym->st_value - loc;
break ;
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_ABS:
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVT_PREL:
offset = tmp = __mem_to_opcode_arm(*(u32 *)loc);
offset = ((offset & 0 xf0000) >> 4 ) | (offset & 0 xfff);
offset = sign_extend32(offset, 15 );
offset += sym->st_value;
if (ELF32_R_TYPE(rel->r_info) == R_ARM_MOVT_PREL ||
ELF32_R_TYPE(rel->r_info) == R_ARM_MOVW_PREL_NC)
offset -= loc;
if (ELF32_R_TYPE(rel->r_info) == R_ARM_MOVT_ABS ||
ELF32_R_TYPE(rel->r_info) == R_ARM_MOVT_PREL)
offset >>= 16 ;
tmp &= 0 xfff0f000;
tmp |= ((offset & 0 xf000) << 4 ) |
(offset & 0 x0fff);
*(u32 *)loc = __opcode_to_mem_arm(tmp);
break ;
#ifdef CONFIG_ARM_HAS_GROUP_RELOCS
case R_ARM_ALU_PC_G0_NC:
group = 0 ;
fallthrough;
case R_ARM_ALU_PC_G1_NC:
tmp = __mem_to_opcode_arm(*(u32 *)loc);
offset = ror32(tmp & 0 xff, (tmp & 0 xf00) >> 7 );
if (tmp & BIT(22 ))
offset = -offset;
offset += sym->st_value - loc;
if (offset < 0 ) {
offset = -offset;
tmp = (tmp & ~BIT(23 )) | BIT(22 ); // SUB opcode
} else {
tmp = (tmp & ~BIT(22 )) | BIT(23 ); // ADD opcode
}
shift = get_group_rem(group, &offset);
if (shift < 24 ) {
offset >>= 24 - shift;
offset |= (shift + 8 ) << 7 ;
}
*(u32 *)loc = __opcode_to_mem_arm((tmp & ~0 xfff) | offset);
break ;
case R_ARM_LDR_PC_G2:
tmp = __mem_to_opcode_arm(*(u32 *)loc);
offset = tmp & 0 xfff;
if (~tmp & BIT(23 )) // U bit cleared?
offset = -offset;
offset += sym->st_value - loc;
if (offset < 0 ) {
offset = -offset;
tmp &= ~BIT(23 ); // clear U bit
} else {
tmp |= BIT(23 ); // set U bit
}
get_group_rem(2 , &offset);
if (offset > 0 xfff) {
pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n" ,
module->name, relindex, i, symname,
ELF32_R_TYPE(rel->r_info), loc,
sym->st_value);
return -ENOEXEC;
}
*(u32 *)loc = __opcode_to_mem_arm((tmp & ~0 xfff) | offset);
break ;
#endif
#ifdef CONFIG_THUMB2_KERNEL
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
/*
* For function symbols , only Thumb addresses are
* allowed ( no interworking ) .
*
* For non - function symbols , the destination
* has no specific ARM / Thumb disposition , so
* the branch is resolved under the assumption
* that interworking is not required .
*/
if (ELF32_ST_TYPE(sym->st_info) == STT_FUNC &&
!(sym->st_value & 1 )) {
pr_err("%s: section %u reloc %u sym '%s': unsupported interworking call (Thumb -> ARM)\n" ,
module->name, relindex, i, symname);
return -ENOEXEC;
}
upper = __mem_to_opcode_thumb16(*(u16 *)loc);
lower = __mem_to_opcode_thumb16(*(u16 *)(loc + 2 ));
/*
* 25 bit signed address range ( Thumb - 2 BL and B . W
* instructions ) :
* S : I1 : I2 : imm10 : imm11 : 0
* where :
* S = upper [ 10 ] = offset [ 24 ]
* I1 = ~ ( J1 ^ S ) = offset [ 23 ]
* I2 = ~ ( J2 ^ S ) = offset [ 22 ]
* imm10 = upper [ 9 : 0 ] = offset [ 21 : 12 ]
* imm11 = lower [ 10 : 0 ] = offset [ 11 : 1 ]
* J1 = lower [ 13 ]
* J2 = lower [ 11 ]
*/
sign = (upper >> 10 ) & 1 ;
j1 = (lower >> 13 ) & 1 ;
j2 = (lower >> 11 ) & 1 ;
offset = (sign << 24 ) | ((~(j1 ^ sign) & 1 ) << 23 ) |
((~(j2 ^ sign) & 1 ) << 22 ) |
((upper & 0 x03ff) << 12 ) |
((lower & 0 x07ff) << 1 );
offset = sign_extend32(offset, 24 );
offset += sym->st_value - loc;
/*
* Route through a PLT entry if ' offset ' exceeds the
* supported range .
*/
if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) &&
(offset <= (s32)0 xff000000 ||
offset >= (s32)0 x01000000))
offset = get_module_plt(module, loc,
offset + loc + 4 )
- loc - 4 ;
if (offset <= (s32)0 xff000000 ||
offset >= (s32)0 x01000000) {
pr_err("%s: section %u reloc %u sym '%s': relocation %u out of range (%#lx -> %#x)\n" ,
module->name, relindex, i, symname,
ELF32_R_TYPE(rel->r_info), loc,
sym->st_value);
return -ENOEXEC;
}
sign = (offset >> 24 ) & 1 ;
j1 = sign ^ (~(offset >> 23 ) & 1 );
j2 = sign ^ (~(offset >> 22 ) & 1 );
upper = (u16)((upper & 0 xf800) | (sign << 10 ) |
((offset >> 12 ) & 0 x03ff));
lower = (u16)((lower & 0 xd000) |
(j1 << 13 ) | (j2 << 11 ) |
((offset >> 1 ) & 0 x07ff));
*(u16 *)loc = __opcode_to_mem_thumb16(upper);
*(u16 *)(loc + 2 ) = __opcode_to_mem_thumb16(lower);
break ;
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_ABS:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVT_PREL:
upper = __mem_to_opcode_thumb16(*(u16 *)loc);
lower = __mem_to_opcode_thumb16(*(u16 *)(loc + 2 ));
/*
* MOVT / MOVW instructions encoding in Thumb - 2 :
*
* i = upper [ 10 ]
* imm4 = upper [ 3 : 0 ]
* imm3 = lower [ 14 : 12 ]
* imm8 = lower [ 7 : 0 ]
*
* imm16 = imm4 : i : imm3 : imm8
*/
offset = ((upper & 0 x000f) << 12 ) |
((upper & 0 x0400) << 1 ) |
((lower & 0 x7000) >> 4 ) | (lower & 0 x00ff);
offset = sign_extend32(offset, 15 );
offset += sym->st_value;
if (ELF32_R_TYPE(rel->r_info) == R_ARM_THM_MOVT_PREL ||
ELF32_R_TYPE(rel->r_info) == R_ARM_THM_MOVW_PREL_NC)
offset -= loc;
if (ELF32_R_TYPE(rel->r_info) == R_ARM_THM_MOVT_ABS ||
ELF32_R_TYPE(rel->r_info) == R_ARM_THM_MOVT_PREL)
offset >>= 16 ;
upper = (u16)((upper & 0 xfbf0) |
((offset & 0 xf000) >> 12 ) |
((offset & 0 x0800) >> 1 ));
lower = (u16)((lower & 0 x8f00) |
((offset & 0 x0700) << 4 ) |
(offset & 0 x00ff));
*(u16 *)loc = __opcode_to_mem_thumb16(upper);
*(u16 *)(loc + 2 ) = __opcode_to_mem_thumb16(lower);
break ;
#endif
default :
pr_err("%s: unknown relocation: %u\n" ,
module->name, ELF32_R_TYPE(rel->r_info));
return -ENOEXEC;
}
}
return 0 ;
}
static const Elf_Shdr *find_mod_section(const Elf32_Ehdr *hdr,
const Elf_Shdr *sechdrs, const char *name)
{
const Elf_Shdr *s, *se;
const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++)
if (strcmp(name, secstrs + s->sh_name) == 0 )
return s;
return NULL;
}
extern void fixup_pv_table(const void *, unsigned long );
extern void fixup_smp(const void *, unsigned long );
int module_finalize(const Elf32_Ehdr *hdr, const Elf_Shdr *sechdrs,
struct module *mod)
{
const Elf_Shdr *s = NULL;
#ifdef CONFIG_ARM_UNWIND
const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
const Elf_Shdr *sechdrs_end = sechdrs + hdr->e_shnum;
struct list_head *unwind_list = &mod->arch.unwind_list;
INIT_LIST_HEAD(unwind_list);
mod->arch.init_table = NULL;
for (s = sechdrs; s < sechdrs_end; s++) {
const char *secname = secstrs + s->sh_name;
const char *txtname;
const Elf_Shdr *txt_sec;
if (!(s->sh_flags & SHF_ALLOC) ||
s->sh_type != ELF_SECTION_UNWIND)
continue ;
if (!strcmp(".ARM.exidx" , secname))
txtname = ".text" ;
else
txtname = secname + strlen(".ARM.exidx" );
txt_sec = find_mod_section(hdr, sechdrs, txtname);
if (txt_sec) {
struct unwind_table *table =
unwind_table_add(s->sh_addr,
s->sh_size,
txt_sec->sh_addr,
txt_sec->sh_size);
list_add(&table->mod_list, unwind_list);
/* save init table for module_arch_freeing_init */
if (strcmp(".ARM.exidx.init.text" , secname) == 0 )
mod->arch.init_table = table;
}
}
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
s = find_mod_section(hdr, sechdrs, ".pv_table" );
if (s)
fixup_pv_table((void *)s->sh_addr, s->sh_size);
#endif
s = find_mod_section(hdr, sechdrs, ".alt.smp.init" );
if (s && !is_smp())
#ifdef CONFIG_SMP_ON_UP
fixup_smp((void *)s->sh_addr, s->sh_size);
#else
return -EINVAL;
#endif
return 0 ;
}
void
module_arch_cleanup(struct module *mod)
{
#ifdef CONFIG_ARM_UNWIND
struct unwind_table *tmp;
struct unwind_table *n;
list_for_each_entry_safe(tmp, n,
&mod->arch.unwind_list, mod_list) {
list_del(&tmp->mod_list);
unwind_table_del(tmp);
}
mod->arch.init_table = NULL;
#endif
}
void module_arch_freeing_init(struct module *mod)
{
#ifdef CONFIG_ARM_UNWIND
struct unwind_table *init = mod->arch.init_table;
if (init) {
mod->arch.init_table = NULL;
list_del(&init->mod_list);
unwind_table_del(init);
}
#endif
}
Messung V0.5 in Prozent C=97 H=90 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland