Merge patch series "riscv: Add remaining module relocations and tests"

Charlie Jenkins <charlie@rivosinc.com> says:

A handful of module relocations were missing, this patch includes the
remaining ones. I also wrote some test cases to ensure that module
loading works properly. Some relocations cannot be supported in the
kernel, these include the ones that rely on thread local storage and
dynamic linking.

This patch also overhauls the implementation of ADD/SUB/SET/ULEB128
relocations to handle overflow. "Overflow" is different for ULEB128
since it is a variable-length encoding that the compiler can be expected
to generate enough space for. Instead of overflowing, ULEB128 will
expand into the next 8-bit segment of the location.

A psABI proposal [1] was merged that mandates that SET_ULEB128 and
SUB_ULEB128 are paired, however the discussion following the merging of
the pull request revealed that while the pull request was valid, it
would be better for linkers to properly handle this overflow. This patch
proactively implements this methodology for future compatibility.

This can be tested by enabling KUNIT, RUNTIME_KERNEL_TESTING_MENU, and
RISCV_MODULE_LINKING_KUNIT.

[1] https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/403

* b4-shazam-merge:
  riscv: Add tests for riscv module loading
  riscv: Add remaining module relocations
  riscv: Avoid unaligned access when relocating modules

Link: https://lore.kernel.org/r/20231101-module_relocations-v9-0-8dfa3483c400@rivosinc.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
This commit is contained in:
Palmer Dabbelt 2023-11-07 14:59:35 -08:00
commit b51fc88cb3
No known key found for this signature in database
GPG Key ID: 2E1319F35FBB1889
18 changed files with 869 additions and 105 deletions

View File

@ -0,0 +1 @@
source "arch/riscv/kernel/tests/Kconfig.debug"

View File

@ -49,6 +49,7 @@ typedef union __riscv_fp_state elf_fpregset_t;
#define R_RISCV_TLS_DTPREL64 9
#define R_RISCV_TLS_TPREL32 10
#define R_RISCV_TLS_TPREL64 11
#define R_RISCV_IRELATIVE 58
/* Relocation types not used by the dynamic linker */
#define R_RISCV_BRANCH 16
@ -81,7 +82,6 @@ typedef union __riscv_fp_state elf_fpregset_t;
#define R_RISCV_ALIGN 43
#define R_RISCV_RVC_BRANCH 44
#define R_RISCV_RVC_JUMP 45
#define R_RISCV_LUI 46
#define R_RISCV_GPREL_I 47
#define R_RISCV_GPREL_S 48
#define R_RISCV_TPREL_I 49
@ -93,6 +93,9 @@ typedef union __riscv_fp_state elf_fpregset_t;
#define R_RISCV_SET16 55
#define R_RISCV_SET32 56
#define R_RISCV_32_PCREL 57
#define R_RISCV_PLT32 59
#define R_RISCV_SET_ULEB128 60
#define R_RISCV_SUB_ULEB128 61
#endif /* _UAPI_ASM_RISCV_ELF_H */

View File

@ -57,6 +57,7 @@ obj-y += stacktrace.o
obj-y += cacheinfo.o
obj-y += patch.o
obj-y += probes/
obj-y += tests/
obj-$(CONFIG_MMU) += vdso.o vdso/
obj-$(CONFIG_RISCV_MISALIGNED) += traps_misaligned.o

View File

@ -7,6 +7,9 @@
#include <linux/elf.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/hashtable.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/moduleloader.h>
#include <linux/vmalloc.h>
#include <linux/sizes.h>
@ -14,6 +17,38 @@
#include <asm/alternative.h>
#include <asm/sections.h>
struct used_bucket {
struct list_head head;
struct hlist_head *bucket;
};
struct relocation_head {
struct hlist_node node;
struct list_head *rel_entry;
void *location;
};
struct relocation_entry {
struct list_head head;
Elf_Addr value;
unsigned int type;
};
struct relocation_handlers {
int (*reloc_handler)(struct module *me, void *location, Elf_Addr v);
int (*accumulate_handler)(struct module *me, void *location,
long buffer);
};
unsigned int initialize_relocation_hashtable(unsigned int num_relocations);
void process_accumulated_relocations(struct module *me);
int add_relocation_to_accumulate(struct module *me, int type, void *location,
unsigned int hashtable_bits, Elf_Addr v);
struct hlist_head *relocation_hashtable;
struct list_head used_buckets_list;
/*
* The auipc+jalr instruction pair can reach any PC-relative offset
* in the range [-2^31 - 2^11, 2^31 - 2^11)
@ -27,68 +62,90 @@ static bool riscv_insn_valid_32bit_offset(ptrdiff_t val)
#endif
}
static int apply_r_riscv_32_rela(struct module *me, u32 *location, Elf_Addr v)
static int riscv_insn_rmw(void *location, u32 keep, u32 set)
{
u16 *parcel = location;
u32 insn = (u32)le16_to_cpu(parcel[0]) | (u32)le16_to_cpu(parcel[1]) << 16;
insn &= keep;
insn |= set;
parcel[0] = cpu_to_le16(insn);
parcel[1] = cpu_to_le16(insn >> 16);
return 0;
}
static int riscv_insn_rvc_rmw(void *location, u16 keep, u16 set)
{
u16 *parcel = location;
u16 insn = le16_to_cpu(*parcel);
insn &= keep;
insn |= set;
*parcel = cpu_to_le16(insn);
return 0;
}
static int apply_r_riscv_32_rela(struct module *me, void *location, Elf_Addr v)
{
if (v != (u32)v) {
pr_err("%s: value %016llx out of range for 32-bit field\n",
me->name, (long long)v);
return -EINVAL;
}
*location = v;
*(u32 *)location = v;
return 0;
}
static int apply_r_riscv_64_rela(struct module *me, u32 *location, Elf_Addr v)
static int apply_r_riscv_64_rela(struct module *me, void *location, Elf_Addr v)
{
*(u64 *)location = v;
return 0;
}
static int apply_r_riscv_branch_rela(struct module *me, u32 *location,
static int apply_r_riscv_branch_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u32 imm12 = (offset & 0x1000) << (31 - 12);
u32 imm11 = (offset & 0x800) >> (11 - 7);
u32 imm10_5 = (offset & 0x7e0) << (30 - 10);
u32 imm4_1 = (offset & 0x1e) << (11 - 4);
*location = (*location & 0x1fff07f) | imm12 | imm11 | imm10_5 | imm4_1;
return 0;
return riscv_insn_rmw(location, 0x1fff07f, imm12 | imm11 | imm10_5 | imm4_1);
}
static int apply_r_riscv_jal_rela(struct module *me, u32 *location,
static int apply_r_riscv_jal_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u32 imm20 = (offset & 0x100000) << (31 - 20);
u32 imm19_12 = (offset & 0xff000);
u32 imm11 = (offset & 0x800) << (20 - 11);
u32 imm10_1 = (offset & 0x7fe) << (30 - 10);
*location = (*location & 0xfff) | imm20 | imm19_12 | imm11 | imm10_1;
return 0;
return riscv_insn_rmw(location, 0xfff, imm20 | imm19_12 | imm11 | imm10_1);
}
static int apply_r_riscv_rvc_branch_rela(struct module *me, u32 *location,
static int apply_r_riscv_rvc_branch_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u16 imm8 = (offset & 0x100) << (12 - 8);
u16 imm7_6 = (offset & 0xc0) >> (6 - 5);
u16 imm5 = (offset & 0x20) >> (5 - 2);
u16 imm4_3 = (offset & 0x18) << (12 - 5);
u16 imm2_1 = (offset & 0x6) << (12 - 10);
*(u16 *)location = (*(u16 *)location & 0xe383) |
imm8 | imm7_6 | imm5 | imm4_3 | imm2_1;
return 0;
return riscv_insn_rvc_rmw(location, 0xe383,
imm8 | imm7_6 | imm5 | imm4_3 | imm2_1);
}
static int apply_r_riscv_rvc_jump_rela(struct module *me, u32 *location,
static int apply_r_riscv_rvc_jump_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u16 imm11 = (offset & 0x800) << (12 - 11);
u16 imm10 = (offset & 0x400) >> (10 - 8);
u16 imm9_8 = (offset & 0x300) << (12 - 11);
@ -98,16 +155,14 @@ static int apply_r_riscv_rvc_jump_rela(struct module *me, u32 *location,
u16 imm4 = (offset & 0x10) << (12 - 5);
u16 imm3_1 = (offset & 0xe) << (12 - 10);
*(u16 *)location = (*(u16 *)location & 0xe003) |
imm11 | imm10 | imm9_8 | imm7 | imm6 | imm5 | imm4 | imm3_1;
return 0;
return riscv_insn_rvc_rmw(location, 0xe003,
imm11 | imm10 | imm9_8 | imm7 | imm6 | imm5 | imm4 | imm3_1);
}
static int apply_r_riscv_pcrel_hi20_rela(struct module *me, u32 *location,
static int apply_r_riscv_pcrel_hi20_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
s32 hi20;
ptrdiff_t offset = (void *)v - location;
if (!riscv_insn_valid_32bit_offset(offset)) {
pr_err(
@ -116,23 +171,20 @@ static int apply_r_riscv_pcrel_hi20_rela(struct module *me, u32 *location,
return -EINVAL;
}
hi20 = (offset + 0x800) & 0xfffff000;
*location = (*location & 0xfff) | hi20;
return 0;
return riscv_insn_rmw(location, 0xfff, (offset + 0x800) & 0xfffff000);
}
static int apply_r_riscv_pcrel_lo12_i_rela(struct module *me, u32 *location,
static int apply_r_riscv_pcrel_lo12_i_rela(struct module *me, void *location,
Elf_Addr v)
{
/*
* v is the lo12 value to fill. It is calculated before calling this
* handler.
*/
*location = (*location & 0xfffff) | ((v & 0xfff) << 20);
return 0;
return riscv_insn_rmw(location, 0xfffff, (v & 0xfff) << 20);
}
static int apply_r_riscv_pcrel_lo12_s_rela(struct module *me, u32 *location,
static int apply_r_riscv_pcrel_lo12_s_rela(struct module *me, void *location,
Elf_Addr v)
{
/*
@ -142,15 +194,12 @@ static int apply_r_riscv_pcrel_lo12_s_rela(struct module *me, u32 *location,
u32 imm11_5 = (v & 0xfe0) << (31 - 11);
u32 imm4_0 = (v & 0x1f) << (11 - 4);
*location = (*location & 0x1fff07f) | imm11_5 | imm4_0;
return 0;
return riscv_insn_rmw(location, 0x1fff07f, imm11_5 | imm4_0);
}
static int apply_r_riscv_hi20_rela(struct module *me, u32 *location,
static int apply_r_riscv_hi20_rela(struct module *me, void *location,
Elf_Addr v)
{
s32 hi20;
if (IS_ENABLED(CONFIG_CMODEL_MEDLOW)) {
pr_err(
"%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
@ -158,22 +207,20 @@ static int apply_r_riscv_hi20_rela(struct module *me, u32 *location,
return -EINVAL;
}
hi20 = ((s32)v + 0x800) & 0xfffff000;
*location = (*location & 0xfff) | hi20;
return 0;
return riscv_insn_rmw(location, 0xfff, ((s32)v + 0x800) & 0xfffff000);
}
static int apply_r_riscv_lo12_i_rela(struct module *me, u32 *location,
static int apply_r_riscv_lo12_i_rela(struct module *me, void *location,
Elf_Addr v)
{
/* Skip medlow checking because of filtering by HI20 already */
s32 hi20 = ((s32)v + 0x800) & 0xfffff000;
s32 lo12 = ((s32)v - hi20);
*location = (*location & 0xfffff) | ((lo12 & 0xfff) << 20);
return 0;
return riscv_insn_rmw(location, 0xfffff, (lo12 & 0xfff) << 20);
}
static int apply_r_riscv_lo12_s_rela(struct module *me, u32 *location,
static int apply_r_riscv_lo12_s_rela(struct module *me, void *location,
Elf_Addr v)
{
/* Skip medlow checking because of filtering by HI20 already */
@ -181,20 +228,18 @@ static int apply_r_riscv_lo12_s_rela(struct module *me, u32 *location,
s32 lo12 = ((s32)v - hi20);
u32 imm11_5 = (lo12 & 0xfe0) << (31 - 11);
u32 imm4_0 = (lo12 & 0x1f) << (11 - 4);
*location = (*location & 0x1fff07f) | imm11_5 | imm4_0;
return 0;
return riscv_insn_rmw(location, 0x1fff07f, imm11_5 | imm4_0);
}
static int apply_r_riscv_got_hi20_rela(struct module *me, u32 *location,
static int apply_r_riscv_got_hi20_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
s32 hi20;
ptrdiff_t offset = (void *)v - location;
/* Always emit the got entry */
if (IS_ENABLED(CONFIG_MODULE_SECTIONS)) {
offset = module_emit_got_entry(me, v);
offset = (void *)offset - (void *)location;
offset = (void *)module_emit_got_entry(me, v) - location;
} else {
pr_err(
"%s: can not generate the GOT entry for symbol = %016llx from PC = %p\n",
@ -202,22 +247,19 @@ static int apply_r_riscv_got_hi20_rela(struct module *me, u32 *location,
return -EINVAL;
}
hi20 = (offset + 0x800) & 0xfffff000;
*location = (*location & 0xfff) | hi20;
return 0;
return riscv_insn_rmw(location, 0xfff, (offset + 0x800) & 0xfffff000);
}
static int apply_r_riscv_call_plt_rela(struct module *me, u32 *location,
static int apply_r_riscv_call_plt_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u32 hi20, lo12;
if (!riscv_insn_valid_32bit_offset(offset)) {
/* Only emit the plt entry if offset over 32-bit range */
if (IS_ENABLED(CONFIG_MODULE_SECTIONS)) {
offset = module_emit_plt_entry(me, v);
offset = (void *)offset - (void *)location;
offset = (void *)module_emit_plt_entry(me, v) - location;
} else {
pr_err(
"%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
@ -228,15 +270,14 @@ static int apply_r_riscv_call_plt_rela(struct module *me, u32 *location,
hi20 = (offset + 0x800) & 0xfffff000;
lo12 = (offset - hi20) & 0xfff;
*location = (*location & 0xfff) | hi20;
*(location + 1) = (*(location + 1) & 0xfffff) | (lo12 << 20);
return 0;
riscv_insn_rmw(location, 0xfff, hi20);
return riscv_insn_rmw(location + 4, 0xfffff, lo12 << 20);
}
static int apply_r_riscv_call_rela(struct module *me, u32 *location,
static int apply_r_riscv_call_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - (void *)location;
ptrdiff_t offset = (void *)v - location;
u32 hi20, lo12;
if (!riscv_insn_valid_32bit_offset(offset)) {
@ -248,18 +289,17 @@ static int apply_r_riscv_call_rela(struct module *me, u32 *location,
hi20 = (offset + 0x800) & 0xfffff000;
lo12 = (offset - hi20) & 0xfff;
*location = (*location & 0xfff) | hi20;
*(location + 1) = (*(location + 1) & 0xfffff) | (lo12 << 20);
return 0;
riscv_insn_rmw(location, 0xfff, hi20);
return riscv_insn_rmw(location + 4, 0xfffff, lo12 << 20);
}
static int apply_r_riscv_relax_rela(struct module *me, u32 *location,
static int apply_r_riscv_relax_rela(struct module *me, void *location,
Elf_Addr v)
{
return 0;
}
static int apply_r_riscv_align_rela(struct module *me, u32 *location,
static int apply_r_riscv_align_rela(struct module *me, void *location,
Elf_Addr v)
{
pr_err(
@ -268,91 +308,441 @@ static int apply_r_riscv_align_rela(struct module *me, u32 *location,
return -EINVAL;
}
static int apply_r_riscv_add16_rela(struct module *me, u32 *location,
static int apply_r_riscv_add8_rela(struct module *me, void *location, Elf_Addr v)
{
*(u8 *)location += (u8)v;
return 0;
}
static int apply_r_riscv_add16_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u16 *)location += (u16)v;
return 0;
}
static int apply_r_riscv_add32_rela(struct module *me, u32 *location,
static int apply_r_riscv_add32_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u32 *)location += (u32)v;
return 0;
}
static int apply_r_riscv_add64_rela(struct module *me, u32 *location,
static int apply_r_riscv_add64_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u64 *)location += (u64)v;
return 0;
}
static int apply_r_riscv_sub16_rela(struct module *me, u32 *location,
static int apply_r_riscv_sub8_rela(struct module *me, void *location, Elf_Addr v)
{
*(u8 *)location -= (u8)v;
return 0;
}
static int apply_r_riscv_sub16_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u16 *)location -= (u16)v;
return 0;
}
static int apply_r_riscv_sub32_rela(struct module *me, u32 *location,
static int apply_r_riscv_sub32_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u32 *)location -= (u32)v;
return 0;
}
static int apply_r_riscv_sub64_rela(struct module *me, u32 *location,
static int apply_r_riscv_sub64_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u64 *)location -= (u64)v;
return 0;
}
static int (*reloc_handlers_rela[]) (struct module *me, u32 *location,
Elf_Addr v) = {
[R_RISCV_32] = apply_r_riscv_32_rela,
[R_RISCV_64] = apply_r_riscv_64_rela,
[R_RISCV_BRANCH] = apply_r_riscv_branch_rela,
[R_RISCV_JAL] = apply_r_riscv_jal_rela,
[R_RISCV_RVC_BRANCH] = apply_r_riscv_rvc_branch_rela,
[R_RISCV_RVC_JUMP] = apply_r_riscv_rvc_jump_rela,
[R_RISCV_PCREL_HI20] = apply_r_riscv_pcrel_hi20_rela,
[R_RISCV_PCREL_LO12_I] = apply_r_riscv_pcrel_lo12_i_rela,
[R_RISCV_PCREL_LO12_S] = apply_r_riscv_pcrel_lo12_s_rela,
[R_RISCV_HI20] = apply_r_riscv_hi20_rela,
[R_RISCV_LO12_I] = apply_r_riscv_lo12_i_rela,
[R_RISCV_LO12_S] = apply_r_riscv_lo12_s_rela,
[R_RISCV_GOT_HI20] = apply_r_riscv_got_hi20_rela,
[R_RISCV_CALL_PLT] = apply_r_riscv_call_plt_rela,
[R_RISCV_CALL] = apply_r_riscv_call_rela,
[R_RISCV_RELAX] = apply_r_riscv_relax_rela,
[R_RISCV_ALIGN] = apply_r_riscv_align_rela,
[R_RISCV_ADD16] = apply_r_riscv_add16_rela,
[R_RISCV_ADD32] = apply_r_riscv_add32_rela,
[R_RISCV_ADD64] = apply_r_riscv_add64_rela,
[R_RISCV_SUB16] = apply_r_riscv_sub16_rela,
[R_RISCV_SUB32] = apply_r_riscv_sub32_rela,
[R_RISCV_SUB64] = apply_r_riscv_sub64_rela,
static int dynamic_linking_not_supported(struct module *me, void *location,
Elf_Addr v)
{
pr_err("%s: Dynamic linking not supported in kernel modules PC = %p\n",
me->name, location);
return -EINVAL;
}
static int tls_not_supported(struct module *me, void *location, Elf_Addr v)
{
pr_err("%s: Thread local storage not supported in kernel modules PC = %p\n",
me->name, location);
return -EINVAL;
}
static int apply_r_riscv_sub6_rela(struct module *me, void *location, Elf_Addr v)
{
u8 *byte = location;
u8 value = v;
*byte = (*byte - (value & 0x3f)) & 0x3f;
return 0;
}
static int apply_r_riscv_set6_rela(struct module *me, void *location, Elf_Addr v)
{
u8 *byte = location;
u8 value = v;
*byte = (*byte & 0xc0) | (value & 0x3f);
return 0;
}
static int apply_r_riscv_set8_rela(struct module *me, void *location, Elf_Addr v)
{
*(u8 *)location = (u8)v;
return 0;
}
static int apply_r_riscv_set16_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u16 *)location = (u16)v;
return 0;
}
static int apply_r_riscv_set32_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u32 *)location = (u32)v;
return 0;
}
static int apply_r_riscv_32_pcrel_rela(struct module *me, void *location,
Elf_Addr v)
{
*(u32 *)location = v - (uintptr_t)location;
return 0;
}
static int apply_r_riscv_plt32_rela(struct module *me, void *location,
Elf_Addr v)
{
ptrdiff_t offset = (void *)v - location;
if (!riscv_insn_valid_32bit_offset(offset)) {
/* Only emit the plt entry if offset over 32-bit range */
if (IS_ENABLED(CONFIG_MODULE_SECTIONS)) {
offset = (void *)module_emit_plt_entry(me, v) - location;
} else {
pr_err("%s: target %016llx can not be addressed by the 32-bit offset from PC = %p\n",
me->name, (long long)v, location);
return -EINVAL;
}
}
*(u32 *)location = (u32)offset;
return 0;
}
static int apply_r_riscv_set_uleb128(struct module *me, void *location, Elf_Addr v)
{
*(long *)location = v;
return 0;
}
static int apply_r_riscv_sub_uleb128(struct module *me, void *location, Elf_Addr v)
{
*(long *)location -= v;
return 0;
}
static int apply_6_bit_accumulation(struct module *me, void *location, long buffer)
{
u8 *byte = location;
u8 value = buffer;
if (buffer > 0x3f) {
pr_err("%s: value %ld out of range for 6-bit relocation.\n",
me->name, buffer);
return -EINVAL;
}
*byte = (*byte & 0xc0) | (value & 0x3f);
return 0;
}
static int apply_8_bit_accumulation(struct module *me, void *location, long buffer)
{
if (buffer > U8_MAX) {
pr_err("%s: value %ld out of range for 8-bit relocation.\n",
me->name, buffer);
return -EINVAL;
}
*(u8 *)location = (u8)buffer;
return 0;
}
static int apply_16_bit_accumulation(struct module *me, void *location, long buffer)
{
if (buffer > U16_MAX) {
pr_err("%s: value %ld out of range for 16-bit relocation.\n",
me->name, buffer);
return -EINVAL;
}
*(u16 *)location = (u16)buffer;
return 0;
}
static int apply_32_bit_accumulation(struct module *me, void *location, long buffer)
{
if (buffer > U32_MAX) {
pr_err("%s: value %ld out of range for 32-bit relocation.\n",
me->name, buffer);
return -EINVAL;
}
*(u32 *)location = (u32)buffer;
return 0;
}
static int apply_64_bit_accumulation(struct module *me, void *location, long buffer)
{
*(u64 *)location = (u64)buffer;
return 0;
}
static int apply_uleb128_accumulation(struct module *me, void *location, long buffer)
{
/*
* ULEB128 is a variable length encoding. Encode the buffer into
* the ULEB128 data format.
*/
u8 *p = location;
while (buffer != 0) {
u8 value = buffer & 0x7f;
buffer >>= 7;
value |= (!!buffer) << 7;
*p++ = value;
}
return 0;
}
/*
* Relocations defined in the riscv-elf-psabi-doc.
* This handles static linking only.
*/
static const struct relocation_handlers reloc_handlers[] = {
[R_RISCV_32] = { apply_r_riscv_32_rela },
[R_RISCV_64] = { apply_r_riscv_64_rela },
[R_RISCV_RELATIVE] = { dynamic_linking_not_supported },
[R_RISCV_COPY] = { dynamic_linking_not_supported },
[R_RISCV_JUMP_SLOT] = { dynamic_linking_not_supported },
[R_RISCV_TLS_DTPMOD32] = { dynamic_linking_not_supported },
[R_RISCV_TLS_DTPMOD64] = { dynamic_linking_not_supported },
[R_RISCV_TLS_DTPREL32] = { dynamic_linking_not_supported },
[R_RISCV_TLS_DTPREL64] = { dynamic_linking_not_supported },
[R_RISCV_TLS_TPREL32] = { dynamic_linking_not_supported },
[R_RISCV_TLS_TPREL64] = { dynamic_linking_not_supported },
/* 12-15 undefined */
[R_RISCV_BRANCH] = { apply_r_riscv_branch_rela },
[R_RISCV_JAL] = { apply_r_riscv_jal_rela },
[R_RISCV_CALL] = { apply_r_riscv_call_rela },
[R_RISCV_CALL_PLT] = { apply_r_riscv_call_plt_rela },
[R_RISCV_GOT_HI20] = { apply_r_riscv_got_hi20_rela },
[R_RISCV_TLS_GOT_HI20] = { tls_not_supported },
[R_RISCV_TLS_GD_HI20] = { tls_not_supported },
[R_RISCV_PCREL_HI20] = { apply_r_riscv_pcrel_hi20_rela },
[R_RISCV_PCREL_LO12_I] = { apply_r_riscv_pcrel_lo12_i_rela },
[R_RISCV_PCREL_LO12_S] = { apply_r_riscv_pcrel_lo12_s_rela },
[R_RISCV_HI20] = { apply_r_riscv_hi20_rela },
[R_RISCV_LO12_I] = { apply_r_riscv_lo12_i_rela },
[R_RISCV_LO12_S] = { apply_r_riscv_lo12_s_rela },
[R_RISCV_TPREL_HI20] = { tls_not_supported },
[R_RISCV_TPREL_LO12_I] = { tls_not_supported },
[R_RISCV_TPREL_LO12_S] = { tls_not_supported },
[R_RISCV_TPREL_ADD] = { tls_not_supported },
[R_RISCV_ADD8] = { apply_r_riscv_add8_rela, apply_8_bit_accumulation },
[R_RISCV_ADD16] = { apply_r_riscv_add16_rela,
apply_16_bit_accumulation },
[R_RISCV_ADD32] = { apply_r_riscv_add32_rela,
apply_32_bit_accumulation },
[R_RISCV_ADD64] = { apply_r_riscv_add64_rela,
apply_64_bit_accumulation },
[R_RISCV_SUB8] = { apply_r_riscv_sub8_rela, apply_8_bit_accumulation },
[R_RISCV_SUB16] = { apply_r_riscv_sub16_rela,
apply_16_bit_accumulation },
[R_RISCV_SUB32] = { apply_r_riscv_sub32_rela,
apply_32_bit_accumulation },
[R_RISCV_SUB64] = { apply_r_riscv_sub64_rela,
apply_64_bit_accumulation },
/* 41-42 reserved for future standard use */
[R_RISCV_ALIGN] = { apply_r_riscv_align_rela },
[R_RISCV_RVC_BRANCH] = { apply_r_riscv_rvc_branch_rela },
[R_RISCV_RVC_JUMP] = { apply_r_riscv_rvc_jump_rela },
/* 46-50 reserved for future standard use */
[R_RISCV_RELAX] = { apply_r_riscv_relax_rela },
[R_RISCV_SUB6] = { apply_r_riscv_sub6_rela, apply_6_bit_accumulation },
[R_RISCV_SET6] = { apply_r_riscv_set6_rela, apply_6_bit_accumulation },
[R_RISCV_SET8] = { apply_r_riscv_set8_rela, apply_8_bit_accumulation },
[R_RISCV_SET16] = { apply_r_riscv_set16_rela,
apply_16_bit_accumulation },
[R_RISCV_SET32] = { apply_r_riscv_set32_rela,
apply_32_bit_accumulation },
[R_RISCV_32_PCREL] = { apply_r_riscv_32_pcrel_rela },
[R_RISCV_IRELATIVE] = { dynamic_linking_not_supported },
[R_RISCV_PLT32] = { apply_r_riscv_plt32_rela },
[R_RISCV_SET_ULEB128] = { apply_r_riscv_set_uleb128,
apply_uleb128_accumulation },
[R_RISCV_SUB_ULEB128] = { apply_r_riscv_sub_uleb128,
apply_uleb128_accumulation },
/* 62-191 reserved for future standard use */
/* 192-255 nonstandard ABI extensions */
};
void process_accumulated_relocations(struct module *me)
{
/*
* Only ADD/SUB/SET/ULEB128 should end up here.
*
* Each bucket may have more than one relocation location. All
* relocations for a location are stored in a list in a bucket.
*
* Relocations are applied to a temp variable before being stored to the
* provided location to check for overflow. This also allows ULEB128 to
* properly decide how many entries are needed before storing to
* location. The final value is stored into location using the handler
* for the last relocation to an address.
*
* Three layers of indexing:
* - Each of the buckets in use
* - Groups of relocations in each bucket by location address
* - Each relocation entry for a location address
*/
struct used_bucket *bucket_iter;
struct relocation_head *rel_head_iter;
struct relocation_entry *rel_entry_iter;
int curr_type;
void *location;
long buffer;
list_for_each_entry(bucket_iter, &used_buckets_list, head) {
hlist_for_each_entry(rel_head_iter, bucket_iter->bucket, node) {
buffer = 0;
location = rel_head_iter->location;
list_for_each_entry(rel_entry_iter,
rel_head_iter->rel_entry, head) {
curr_type = rel_entry_iter->type;
reloc_handlers[curr_type].reloc_handler(
me, &buffer, rel_entry_iter->value);
kfree(rel_entry_iter);
}
reloc_handlers[curr_type].accumulate_handler(
me, location, buffer);
kfree(rel_head_iter);
}
kfree(bucket_iter);
}
kfree(relocation_hashtable);
}
int add_relocation_to_accumulate(struct module *me, int type, void *location,
unsigned int hashtable_bits, Elf_Addr v)
{
struct relocation_entry *entry;
struct relocation_head *rel_head;
struct hlist_head *current_head;
struct used_bucket *bucket;
unsigned long hash;
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
INIT_LIST_HEAD(&entry->head);
entry->type = type;
entry->value = v;
hash = hash_min((uintptr_t)location, hashtable_bits);
current_head = &relocation_hashtable[hash];
/* Find matching location (if any) */
bool found = false;
struct relocation_head *rel_head_iter;
hlist_for_each_entry(rel_head_iter, current_head, node) {
if (rel_head_iter->location == location) {
found = true;
rel_head = rel_head_iter;
break;
}
}
if (!found) {
rel_head = kmalloc(sizeof(*rel_head), GFP_KERNEL);
rel_head->rel_entry =
kmalloc(sizeof(struct list_head), GFP_KERNEL);
INIT_LIST_HEAD(rel_head->rel_entry);
rel_head->location = location;
INIT_HLIST_NODE(&rel_head->node);
if (!current_head->first) {
bucket =
kmalloc(sizeof(struct used_bucket), GFP_KERNEL);
INIT_LIST_HEAD(&bucket->head);
bucket->bucket = current_head;
list_add(&bucket->head, &used_buckets_list);
}
hlist_add_head(&rel_head->node, current_head);
}
/* Add relocation to head of discovered rel_head */
list_add_tail(&entry->head, rel_head->rel_entry);
return 0;
}
unsigned int initialize_relocation_hashtable(unsigned int num_relocations)
{
/* Can safely assume that bits is not greater than sizeof(long) */
unsigned long hashtable_size = roundup_pow_of_two(num_relocations);
unsigned int hashtable_bits = ilog2(hashtable_size);
/*
* Double size of hashtable if num_relocations * 1.25 is greater than
* hashtable_size.
*/
int should_double_size = ((num_relocations + (num_relocations >> 2)) > (hashtable_size));
hashtable_bits += should_double_size;
hashtable_size <<= should_double_size;
relocation_hashtable = kmalloc_array(hashtable_size,
sizeof(*relocation_hashtable),
GFP_KERNEL);
__hash_init(relocation_hashtable, hashtable_size);
INIT_LIST_HEAD(&used_buckets_list);
return hashtable_bits;
}
int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
unsigned int symindex, unsigned int relsec,
struct module *me)
{
Elf_Rela *rel = (void *) sechdrs[relsec].sh_addr;
int (*handler)(struct module *me, u32 *location, Elf_Addr v);
int (*handler)(struct module *me, void *location, Elf_Addr v);
Elf_Sym *sym;
u32 *location;
void *location;
unsigned int i, type;
Elf_Addr v;
int res;
unsigned int num_relocations = sechdrs[relsec].sh_size / sizeof(*rel);
unsigned int hashtable_bits = initialize_relocation_hashtable(num_relocations);
pr_debug("Applying relocate section %u to %u\n", relsec,
sechdrs[relsec].sh_info);
for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
for (i = 0; i < num_relocations; i++) {
/* This is where to make the change */
location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
+ rel[i].r_offset;
@ -370,8 +760,8 @@ int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
type = ELF_RISCV_R_TYPE(rel[i].r_info);
if (type < ARRAY_SIZE(reloc_handlers_rela))
handler = reloc_handlers_rela[type];
if (type < ARRAY_SIZE(reloc_handlers))
handler = reloc_handlers[type].reloc_handler;
else
handler = NULL;
@ -427,11 +817,16 @@ int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
}
}
res = handler(me, location, v);
if (reloc_handlers[type].accumulate_handler)
res = add_relocation_to_accumulate(me, type, location, hashtable_bits, v);
else
res = handler(me, location, v);
if (res)
return res;
}
process_accumulated_relocations(me);
return 0;
}

View File

@ -0,0 +1,35 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "arch/riscv/kernel Testing and Coverage"
config AS_HAS_ULEB128
def_bool $(as-instr,.reloc label$(comma) R_RISCV_SET_ULEB128$(comma) 127\n.reloc label$(comma) R_RISCV_SUB_ULEB128$(comma) 127\nlabel:\n.word 0)
menuconfig RUNTIME_KERNEL_TESTING_MENU
bool "arch/riscv/kernel runtime Testing"
def_bool y
help
Enable riscv kernel runtime testing.
if RUNTIME_KERNEL_TESTING_MENU
config RISCV_MODULE_LINKING_KUNIT
bool "KUnit test riscv module linking at runtime" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
Enable this option to test riscv module linking at boot. This will
enable a module called "test_module_linking".
KUnit tests run during boot and output the results to the debug log
in TAP format (http://testanything.org/). Only useful for kernel devs
running the KUnit test harness, and not intended for inclusion into a
production build.
For more information on KUnit and unit tests in general please refer
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
endif # RUNTIME_TESTING_MENU
endmenu # "arch/riscv/kernel runtime Testing"

View File

@ -0,0 +1 @@
obj-$(CONFIG_RISCV_MODULE_LINKING_KUNIT) += module_test/

View File

@ -0,0 +1,15 @@
obj-m += test_module_linking.o
test_sub := test_sub6.o test_sub8.o test_sub16.o test_sub32.o test_sub64.o
test_set := test_set6.o test_set8.o test_set16.o test_set32.o
test_module_linking-objs += $(test_sub)
test_module_linking-objs += $(test_set)
ifeq ($(CONFIG_AS_HAS_ULEB128),y)
test_module_linking-objs += test_uleb128.o
endif
test_module_linking-objs += test_module_linking_main.o

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Rivos Inc.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <kunit/test.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test module linking");
extern int test_set32(void);
extern int test_set16(void);
extern int test_set8(void);
extern int test_set6(void);
extern long test_sub64(void);
extern int test_sub32(void);
extern int test_sub16(void);
extern int test_sub8(void);
extern int test_sub6(void);
#ifdef CONFIG_AS_HAS_ULEB128
extern int test_uleb_basic(void);
extern int test_uleb_large(void);
#endif
#define CHECK_EQ(lhs, rhs) KUNIT_ASSERT_EQ(test, lhs, rhs)
void run_test_set(struct kunit *test);
void run_test_sub(struct kunit *test);
void run_test_uleb(struct kunit *test);
void run_test_set(struct kunit *test)
{
int val32 = test_set32();
int val16 = test_set16();
int val8 = test_set8();
int val6 = test_set6();
CHECK_EQ(val32, 0);
CHECK_EQ(val16, 0);
CHECK_EQ(val8, 0);
CHECK_EQ(val6, 0);
}
void run_test_sub(struct kunit *test)
{
int val64 = test_sub64();
int val32 = test_sub32();
int val16 = test_sub16();
int val8 = test_sub8();
int val6 = test_sub6();
CHECK_EQ(val64, 0);
CHECK_EQ(val32, 0);
CHECK_EQ(val16, 0);
CHECK_EQ(val8, 0);
CHECK_EQ(val6, 0);
}
#ifdef CONFIG_AS_HAS_ULEB128
void run_test_uleb(struct kunit *test)
{
int val_uleb = test_uleb_basic();
int val_uleb2 = test_uleb_large();
CHECK_EQ(val_uleb, 0);
CHECK_EQ(val_uleb2, 0);
}
#endif
static struct kunit_case __refdata riscv_module_linking_test_cases[] = {
KUNIT_CASE(run_test_set),
KUNIT_CASE(run_test_sub),
#ifdef CONFIG_AS_HAS_ULEB128
KUNIT_CASE(run_test_uleb),
#endif
{}
};
static struct kunit_suite riscv_module_linking_test_suite = {
.name = "riscv_checksum",
.test_cases = riscv_module_linking_test_cases,
};
kunit_test_suites(&riscv_module_linking_test_suite);

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_set16
test_set16:
lw a0, set16
la t0, set16
#ifdef CONFIG_32BIT
slli t0, t0, 16
srli t0, t0, 16
#else
slli t0, t0, 48
srli t0, t0, 48
#endif
sub a0, a0, t0
ret
.data
set16:
.reloc set16, R_RISCV_SET16, set16
.word 0

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_set32
test_set32:
lw a0, set32
la t0, set32
#ifndef CONFIG_32BIT
slli t0, t0, 32
srli t0, t0, 32
#endif
sub a0, a0, t0
ret
.data
set32:
.reloc set32, R_RISCV_SET32, set32
.word 0

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_set6
test_set6:
lw a0, set6
la t0, set6
#ifdef CONFIG_32BIT
slli t0, t0, 26
srli t0, t0, 26
#else
slli t0, t0, 58
srli t0, t0, 58
#endif
sub a0, a0, t0
ret
.data
set6:
.reloc set6, R_RISCV_SET6, set6
.word 0

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_set8
test_set8:
lw a0, set8
la t0, set8
#ifdef CONFIG_32BIT
slli t0, t0, 24
srli t0, t0, 24
#else
slli t0, t0, 56
srli t0, t0, 56
#endif
sub a0, a0, t0
ret
.data
set8:
.reloc set8, R_RISCV_SET8, set8
.word 0

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_sub16
test_sub16:
lh a0, sub16
addi a0, a0, -32
ret
first:
.space 32
second:
.data
sub16:
.reloc sub16, R_RISCV_ADD16, second
.reloc sub16, R_RISCV_SUB16, first
.half 0

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_sub32
test_sub32:
lw a0, sub32
addi a0, a0, -32
ret
first:
.space 32
second:
.data
sub32:
.reloc sub32, R_RISCV_ADD32, second
.reloc sub32, R_RISCV_SUB32, first
.word 0

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_sub6
test_sub6:
lb a0, sub6
addi a0, a0, -32
ret
first:
.space 32
second:
.data
sub6:
.reloc sub6, R_RISCV_SET6, second
.reloc sub6, R_RISCV_SUB6, first
.byte 0

View File

@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_sub64
test_sub64:
#ifdef CONFIG_32BIT
lw a0, sub64
#else
ld a0, sub64
#endif
addi a0, a0, -32
ret
first:
.space 32
second:
.data
sub64:
.reloc sub64, R_RISCV_ADD64, second
.reloc sub64, R_RISCV_SUB64, first
.word 0
.word 0

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_sub8
test_sub8:
lb a0, sub8
addi a0, a0, -32
ret
first:
.space 32
second:
.data
sub8:
.reloc sub8, R_RISCV_ADD8, second
.reloc sub8, R_RISCV_SUB8, first
.byte 0

View File

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Rivos Inc.
*/
.text
.global test_uleb_basic
test_uleb_basic:
ld a0, second
addi a0, a0, -127
ret
.global test_uleb_large
test_uleb_large:
ld a0, fourth
addi a0, a0, -0x07e8
ret
.data
first:
.space 127
second:
.reloc second, R_RISCV_SET_ULEB128, second
.reloc second, R_RISCV_SUB_ULEB128, first
.dword 0
third:
.space 1000
fourth:
.reloc fourth, R_RISCV_SET_ULEB128, fourth
.reloc fourth, R_RISCV_SUB_ULEB128, third
.dword 0