From e7a3ac5c3ee65aff1f9438788ea0ba1ca4f07475 Mon Sep 17 00:00:00 2001 From: Jvle Date: Sat, 1 Mar 2025 19:50:34 +0800 Subject: [PATCH] riscv64: add riscv64 support for 25.03 Signed-off-by: Jvle --- upatch-diff/create-diff-object.c | 30 ++- upatch-diff/elf-common.c | 37 +++- upatch-diff/elf-common.h | 2 + upatch-diff/elf-compare.c | 2 + upatch-diff/elf-insn.c | 8 + upatch-diff/elf-insn.h | 4 + upatch-diff/upatch-elf.c | 10 + upatch-diff/upatch-elf.h | 1 + upatch-manage/arch/riscv64/insn.h | 123 ++++++++++++ upatch-manage/arch/riscv64/process.h | 28 +++ upatch-manage/arch/riscv64/ptrace.c | 207 ++++++++++++++++++++ upatch-manage/arch/riscv64/relocation.c | 242 ++++++++++++++++++++++++ upatch-manage/arch/riscv64/resolve.c | 154 +++++++++++++++ upatch-manage/upatch-patch.c | 22 +++ upatch-manage/upatch-ptrace.c | 4 + upatch-manage/upatch-ptrace.h | 9 +- 16 files changed, 879 insertions(+), 4 deletions(-) create mode 100644 upatch-manage/arch/riscv64/insn.h create mode 100644 upatch-manage/arch/riscv64/process.h create mode 100644 upatch-manage/arch/riscv64/ptrace.c create mode 100644 upatch-manage/arch/riscv64/relocation.c create mode 100644 upatch-manage/arch/riscv64/resolve.c diff --git a/upatch-diff/create-diff-object.c b/upatch-diff/create-diff-object.c index 1c14b7a4..7966abdc 100644 --- a/upatch-diff/create-diff-object.c +++ b/upatch-diff/create-diff-object.c @@ -614,7 +614,8 @@ static void replace_section_syms(struct upatch_elf *uelf) !is_text_section(sym->sec) && (rela->type == R_X86_64_32S || rela->type == R_X86_64_32 || - rela->type == R_AARCH64_ABS64) && + rela->type == R_AARCH64_ABS64 || + rela->type == R_RISCV_64) && rela->addend == (long)sym->sec->sh.sh_size && end == (long)sym->sec->sh.sh_size) { ERROR("Relocation refer end of data sections."); @@ -759,6 +760,12 @@ static void include_symbol(struct symbol *sym) if (sym->sec && (sym->type == STT_SECTION || sym->status != SAME)) { include_section(sym->sec); } +#ifdef __riscv + /* .L symbols not exist in EXE. If they are included, so are their sections. */ + else if (sym->sec && !sym->sec->include && !strncmp(sym->name, ".L", 2)) { + include_section(sym->sec); + } +#endif } static void include_section(struct section *sec) @@ -949,6 +956,23 @@ static void verify_patchability(struct upatch_elf *uelf) } } +/* + * These types are for linker optimization and memory layout. + * They have no associated symbols and their names are empty + * string which would mismatch running-elf symbols in later + * lookup_relf(). Drop these useless items now. + */ +static void rv_drop_useless_rela(struct section *relasec) +{ + struct rela *rela, *saferela; + list_for_each_entry_safe(rela, saferela, &relasec->relas, list) + if (rela->type == R_RISCV_RELAX || rela->type == R_RISCV_ALIGN) { + list_del(&rela->list); + memset(rela, 0, sizeof(*rela)); + free(rela); + } +} + static void migrate_included_elements(struct upatch_elf *uelf_patched, struct upatch_elf *uelf_out) { @@ -975,7 +999,9 @@ static void migrate_included_elements(struct upatch_elf *uelf_patched, if (!is_rela_section(sec) && sec->secsym && !sec->secsym->include) { sec->secsym = NULL; // break link to non-included section symbol - } + } else if (uelf_patched->arch == RISCV64) { + rv_drop_useless_rela(sec); + } } /* migrate included symbols from kelf to out */ diff --git a/upatch-diff/elf-common.c b/upatch-diff/elf-common.c index 5aa35f19..babf4d53 100644 --- a/upatch-diff/elf-common.c +++ b/upatch-diff/elf-common.c @@ -26,6 +26,32 @@ #include "elf-common.h" +#ifdef __riscv +/* + * .L local symbols are named as ".L" + "class prefix" + "number". + * The numbers are volatile due to code change. + * Compare class prefix(composed of letters) only. + */ +static int mangled_strcmp_dot_L(char *str1, char *str2) +{ + if (!*str2 || strncmp(str2, ".L", 2)) + return 1; + + /* RISCV_FAKE_LABEL_NAME matched exactly */ + if (!strcmp(str1, ".L0 ") || !strcmp(str2, ".L0 ")) + return strcmp(str1, str2); + + char *p = str1 + 2; + char *q = str2 + 2; + while (*p < '0' || *p > '9') p++; + while (*q < '0' || *q > '9') q++; + if ((p - str1 != q - str2) || strncmp(str1, str2, p - str1)) + return 1; + + return 0; +} +#endif + int mangled_strcmp(char *str1, char *str2) { /* @@ -35,6 +61,12 @@ int mangled_strcmp(char *str1, char *str2) return strcmp(str1, str2); } +#ifdef __riscv + if (!strncmp(str1, ".L", 2)) { + return mangled_strcmp_dot_L(str1, str2); + } +#endif + while (*str1 == *str2) { if (!*str2) { return 0; @@ -112,6 +144,8 @@ bool is_gcc6_localentry_bundled_sym(struct upatch_elf *uelf) return false; case X86_64: return false; + case RISCV64: + return false; default: ERROR("unsupported arch"); } @@ -121,9 +155,10 @@ bool is_gcc6_localentry_bundled_sym(struct upatch_elf *uelf) bool is_mapping_symbol(struct upatch_elf *uelf, struct symbol *sym) { - if (uelf->arch != AARCH64) { + if ((uelf->arch != AARCH64) && (uelf->arch != RISCV64)) { return false; } + if (sym->name && sym->name[0] == '$' && sym->type == STT_NOTYPE && sym->bind == STB_LOCAL) { diff --git a/upatch-diff/elf-common.h b/upatch-diff/elf-common.h index bc2c1acf..01273140 100644 --- a/upatch-diff/elf-common.h +++ b/upatch-diff/elf-common.h @@ -224,6 +224,8 @@ static inline unsigned int absolute_rela_type(struct upatch_elf *uelf) return R_AARCH64_ABS64; case X86_64: return R_X86_64_64; + case RISCV64: + return R_RISCV_64; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-compare.c b/upatch-diff/elf-compare.c index 7ca6845e..d6fd3b40 100644 --- a/upatch-diff/elf-compare.c +++ b/upatch-diff/elf-compare.c @@ -362,6 +362,8 @@ static bool line_macro_change_only(struct upatch_elf *uelf, struct section *sec) return line_macro_change_only_aarch64(uelf, sec); case X86_64: return line_macro_change_only_x86_64(uelf, sec); + case RISCV64: + /* TODO: not support*/ default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-insn.c b/upatch-diff/elf-insn.c index 8fcfc39d..bb913dd7 100644 --- a/upatch-diff/elf-insn.c +++ b/upatch-diff/elf-insn.c @@ -66,6 +66,8 @@ long rela_target_offset(struct upatch_elf *uelf, struct section *relasec, struct long add_off; switch (uelf->arch) { + case RISCV64: + /* fall through */ case AARCH64: add_off = 0; break; @@ -105,6 +107,12 @@ unsigned int insn_length(struct upatch_elf *uelf, void *addr) insn_init(&decoded_insn, addr, 1); insn_get_length(&decoded_insn); return decoded_insn.length; + case RISCV64: + /* LSB 2 bits distinguish insn size. Now only RV32, RVC supported. */ + if ((*(char *)addr & 0x3) == 0x3) { + return RISCV64_INSN_LEN_4; + } + return RISCV64_INSN_LEN_2; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-insn.h b/upatch-diff/elf-insn.h index f64ebe19..f52b9756 100644 --- a/upatch-diff/elf-insn.h +++ b/upatch-diff/elf-insn.h @@ -31,6 +31,10 @@ #define ARM64_INSTR_LEN 4 +// riscv +#define RISCV64_INSN_LEN_4 4 +#define RISCV64_INSN_LEN_2 2 + void rela_insn(const struct section *sec, const struct rela *rela, struct insn *insn); /* diff --git a/upatch-diff/upatch-elf.c b/upatch-diff/upatch-elf.c index a51f0080..278a2215 100644 --- a/upatch-diff/upatch-elf.c +++ b/upatch-diff/upatch-elf.c @@ -368,6 +368,16 @@ void upatch_elf_open(struct upatch_elf *uelf, const char *name) case EM_X86_64: uelf->arch = X86_64; break; + case EM_RISCV: + /* + | Val | Macros | Description | + | 1 | ELFCLASS32 | riscv32 | + | 2 | ELFCLASS64 | riscv64 | + */ + if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + uelf->arch = RISCV64; + } + break; default: ERROR("unsupported architecture here"); } diff --git a/upatch-diff/upatch-elf.h b/upatch-diff/upatch-elf.h index 030feca6..7597e8e3 100644 --- a/upatch-diff/upatch-elf.h +++ b/upatch-diff/upatch-elf.h @@ -126,6 +126,7 @@ struct symbol { enum architecture { X86_64 = 0x1 << 0, AARCH64 = 0x1 << 1, + RISCV64 = 0x1 << 2, }; struct upatch_elf { diff --git a/upatch-manage/arch/riscv64/insn.h b/upatch-manage/arch/riscv64/insn.h new file mode 100644 index 00000000..8ab4daf7 --- /dev/null +++ b/upatch-manage/arch/riscv64/insn.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 laokz + */ + +#ifndef _ARCH_RISCV64_INSN_H +#define _ARCH_RISCV64_INSN_H + +static inline unsigned set_utype_imm(unsigned ins, unsigned long imm) +{ + /* + imm[11] to counteract lo12 sign extension in next instruction */ + unsigned long temp_imm = imm; + temp_imm += (temp_imm & 0x800) << 1; + return (temp_imm & 0xfffff000) | (ins & 0xfff); +} + +static inline unsigned set_itype_imm(unsigned ins, unsigned long imm) +{ + return ((imm & 0xfff) << 20) | (ins & 0xfffff); +} + +static inline unsigned set_stype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[11-8,7-5] 1,1111, 1111,1 111, imm[4-1,0] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 11-----5 4---0 + 1111,111 1,1111 + imm mask fe0 1f + + ==>imm bit no. 31----25 11--7 + */ + return (ins & 0x1fff07f) | + ((imm & 0xfe0) << (31 - 11)) | ((imm & 0x1f) << (11 - 4)); +} + +static inline unsigned set_jtype_imm(unsigned ins, unsigned long imm) +{ + /* + imm bit no. 20 19------12 11 10---------1 + 1, 1111,1111, 1 111,1111,111 0 + mask 100000 ff000 800 7fe + + ==>imm bit no. 31 19------12 20 30---------21 + */ + return (ins & 0xfff) | + ((imm & 0x100000) << (31 - 20)) | (imm & 0xff000) | + ((imm & 0x800) << (20 - 11)) | ((imm & 0x7fe) << (30 - 10)); +} + +static inline unsigned set_btype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[12 10-8,7-5] 1,1111, 1111,1 111, imm[4-1,11] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 12 11 10----5 4--1 + 1, 1 111,111 1,111 0 + imm mask 1000 800 7e0 1e + + ==>imm bit no. 31 7 30---25 11-8 + */ + return (ins & 0x01fff07f) | + ((imm & 0x1000) << (31 - 12)) | ((imm & 0x800) >> (11 - 7)) | + ((imm & 0x7e0) << (30 - 10)) | ((imm & 0x1e) << (11 - 4)); +} + +static inline unsigned short set_cjtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm opcode + ins: 111 offset[11,4 9 8 10, 6 7 3 2, 1 5] 11 + ins mask e 0 0 3 + + imm bit no. 11 10 9-8 7 6 5 4 3-1 + 1 1 11, 1 1 1 1, 111 0 + imm mask 800 400 300 80 40 20 10 e + + ==>imm bit no. 12 8 10-9 6 7 2 11 5-3 + */ + return (ins & 0xe003) | + ((imm & 0x800) << (12 - 11)) | ((imm & 0x400) >> (10 - 8)) | + ((imm & 0x300) << (10 - 9)) | ((imm & 0x80) >> (7 - 6)) | + ((imm & 0x40) << (7 - 6)) | ((imm & 0x20) >> (5 - 2)) | + ((imm & 0x10) << (11 - 4)) | ((imm & 0xe) << (5 - 3)); +} + +/* only support C.LUI */ +static inline unsigned short set_citype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[17] rd imm[16:12] opcode + ins: 111 imm[17], 1111,1 imm[16-14,13-12] 11 + ins mask e f 8 3 + + imm bit no. 17 16--12 + 1 1,1111 + imm mask 20000 1f000 + + ==>imm bit no. 12 6---2 + */ + return (ins & 0xef83) | + ((imm & 0x20000) >> (17 - 12)) | ((imm & 0x1f000) >> (16 - 6)); +} + +/* only support C.BEQZ C.BNEZ */ +static inline unsigned short set_cbtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[8 4 3] rs imm[7 6 2 1 5] opcode + ins: 111 0,0 0 11,1 0 0 0,0 0 11 + ins mask e 3 8 3 + + imm bit no. 8 , 7 6 5 4,3 2 1 0 + imm mask 100 c0 20 18 6 + + ==>imm bit no. 12 6-5 2 11-10 4-3 + */ + return (ins & 0xe383) | + ((imm & 0x100) << (12 - 8)) | ((imm & 0xc0) >> (7 - 6)) | + ((imm & 0x20) >> (5 - 2)) | ((imm & 0x18) << (11 - 4)) | + ((imm & 0x6) << (4 - 2)); +} + +#endif diff --git a/upatch-manage/arch/riscv64/process.h b/upatch-manage/arch/riscv64/process.h new file mode 100644 index 00000000..91926fa2 --- /dev/null +++ b/upatch-manage/arch/riscv64/process.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __PROCESS__ +#define __PROCESS__ + +#ifndef MAX_DISTANCE +#define MAX_DISTANCE 0x80000000 +#endif + +#endif diff --git a/upatch-manage/arch/riscv64/ptrace.c b/upatch-manage/arch/riscv64/ptrace.c new file mode 100644 index 00000000..c5691c94 --- /dev/null +++ b/upatch-manage/arch/riscv64/ptrace.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include "upatch-ptrace.h" + +int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) +{ + struct iovec regs_iov; + struct user_regs_struct regs; + + regs_iov.iov_base = ®s; + regs_iov.iov_len = sizeof(regs); + + if (ptrace(PTRACE_GETREGSET, pid, + (void *)NT_PRSTATUS, (void *)®s_iov) < 0) { + log_error("Cannot get regs from %d\n", pid); + return -1; + } + *sp = (unsigned long)regs.sp; + *pc = (unsigned long)regs.pc; + return 0; +} + +static long read_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_GETREGSET)"); + return -1; + } + return 0; +} + +static long write_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_SETREGSET)"); + return -1; + } + return 0; +} + +long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, + unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6, + unsigned long *res) +{ + struct user_regs_struct regs; + unsigned char syscall[] = { + 0x73, 0x00, 0x00, 0x00, // ecall + 0x73, 0x00, 0x10, 0x00, // ebreak + }; + long ret; + + log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); + regs.a7 = (unsigned long)nr; + regs.a0 = arg1; + regs.a1 = arg2; + regs.a2 = arg3; + regs.a3 = arg4; + regs.a4 = arg5; + regs.a5 = arg6; + + ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); + if (ret == 0) + *res = regs.a0; + + return ret; +} + +long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, + const unsigned char *code, size_t codelen, + struct user_regs_struct *pregs, + int (*func)(struct upatch_ptrace_ctx *pctx, + const void *data), + const void *data) +{ + struct user_regs_struct orig_regs, regs; + unsigned char orig_code[codelen]; + long ret; + struct upatch_process *proc = pctx->proc; + unsigned long libc_base = proc->libc_base; + + ret = read_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + return -1; + } + ret = upatch_process_mem_read(proc, libc_base, + (unsigned long *)orig_code, codelen); + if (ret < 0) { + log_error("can't peek original code - %d\n", pctx->pid); + return -1; + } + ret = upatch_process_mem_write(proc, (const unsigned long *)code, libc_base, + codelen); + if (ret < 0) { + log_error("can't poke syscall code - %d\n", pctx->pid); + goto poke_back; + } + + regs = orig_regs; + regs.pc = libc_base; + + copy_regs(®s, pregs); + + ret = write_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = func(pctx, data); + if (ret < 0) { + log_error("failed call to func\n"); + goto poke_back; + } + + ret = read_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = write_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + goto poke_back; + } + + *pregs = regs; + +poke_back: + upatch_process_mem_write(proc, (unsigned long *)orig_code, libc_base, + codelen); + return ret; +} + +void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) +{ +#define COPY_REG(x) dst->x = src->x + COPY_REG(a0); + COPY_REG(a1); + COPY_REG(a2); + COPY_REG(a3); + COPY_REG(a4); + COPY_REG(a5); + COPY_REG(a6); + COPY_REG(a7); +#undef COPY_REG +} + +#define UPATCH_INSN_LEN 8 +#define UPATCH_ADDR_LEN 8 +#define ORIGIN_INSN_LEN (UPATCH_INSN_LEN + UPATCH_ADDR_LEN) + +size_t get_origin_insn_len() +{ + return ORIGIN_INSN_LEN; +} + +size_t get_upatch_insn_len() +{ + return UPATCH_INSN_LEN; +} + +size_t get_upatch_addr_len() +{ + return UPATCH_ADDR_LEN; +} + +/* + * On RISC-V, there must be 3 instructors(12 bytes) to jump to + * arbitrary address. The core upatch-manage limit jump instructor + * to one long(8 bytes), for us is +-2G range. + */ +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr) +{ + unsigned long offset; + unsigned int insn0, insn4; + + offset = new_addr - old_addr; + offset += (offset & 0x800) << 1; + insn0 = 0xf97 | (offset & 0xfffff000); // auipc t6, off[20] + insn4 = 0xf8067 | ((offset & 0xfff) << 20); // jalr zero, off[12](t6) + return (unsigned long)(insn0 | ((unsigned long)insn4 << 32)); +} diff --git a/upatch-manage/arch/riscv64/relocation.c b/upatch-manage/arch/riscv64/relocation.c new file mode 100644 index 00000000..6c28bf8c --- /dev/null +++ b/upatch-manage/arch/riscv64/relocation.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "insn.h" +#include "upatch-relocation.h" +#include "upatch-resolve.h" + +/* + * In PCREL_LO12 relocation entity, its corresponding symbol's value + * points to the ..._HI20 instruction, where the LO12 part of the + * immediate is part of the ..._HI20 symbol value. + */ +static unsigned long find_pcrel_hi_value(GElf_Rela *r, int idx, GElf_Sym *st, unsigned long v) +{ + int i = idx; + r--; + for (; i > 0; i--, r--) { + if ((r->r_offset == v) && + ((GELF_R_TYPE(r->r_info) == R_RISCV_PCREL_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GOT_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GD_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_GOT_HI20))) + return st[GELF_R_SYM(r->r_info)].st_value; + } + + /* should never happen */ + log_error("Not found no. %d rela's corresponding HI20\n", idx); + return 0; +} + +/* + * The patch is a .o file, has only static relocations, all symbols + * have been resolved with our jump table act as got/plt. + */ +int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, + unsigned int relsec) +{ + unsigned int i; + GElf_Sym *sym, *symtab; + char const *sym_name; + unsigned long uloc_sec; + void *loc; + void *uloc; + u64 val; + GElf_Shdr *shdrs = (void *)uelf->info.shdrs; + GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; + + symtab = (GElf_Sym *)shdrs[symindex].sh_addr; + for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { + /* loc corresponds to P in the kernel space */ + loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + + /* uloc corresponds P in user space */ + uloc_sec = shdrs[shdrs[relsec].sh_info].sh_addralign; + uloc = (void *)uloc_sec + rel[i].r_offset; + + /* sym is the ELF symbol we're referring to */ + sym = symtab + GELF_R_SYM(rel[i].r_info); + if (GELF_ST_TYPE(sym->st_info) == STT_SECTION && + sym->st_shndx < uelf->info.hdr->e_shnum) + sym_name = uelf->info.shstrtab + + shdrs[sym->st_shndx].sh_name; + else + sym_name = uelf->strtab + sym->st_name; + + /* val corresponds to (S + A) */ + val = (s64)(sym->st_value + rel[i].r_addend); + log_debug( + "upatch: reloc symbol, name=%s, k_addr=0x%lx, u_addr=0x%lx, " + "r_offset=0x%lx, st_value=0x%lx, r_addend=0x%lx \n", + sym_name, shdrs[shdrs[relsec].sh_info].sh_addr, + uloc_sec, rel[i].r_offset, sym->st_value, rel[i].r_addend); + + /* Perform the static relocation. */ + switch (GELF_R_TYPE(rel[i].r_info)) { + case R_RISCV_NONE: + case R_RISCV_TPREL_ADD: + break; + + case R_RISCV_64: + *(unsigned long *)loc = val; + break; + + /* seems no need to recalculate as it should confined in the same func */ + case R_RISCV_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 4096 || (signed)val < -4096) + goto overflow; + *(unsigned *)loc = set_btype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_JAL: + val -= (unsigned long)uloc; + if ((signed)val >= (1 << 20) || (signed)val < -(1 << 20)) + goto overflow; + *(unsigned *)loc = set_jtype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_CALL: + case R_RISCV_CALL_PLT: + // in our jump table, must not overflow + val -= (unsigned long)uloc; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + *(unsigned *)(loc + 4) = set_itype_imm(*(unsigned *)(loc + 4), val); + break; + + case R_RISCV_GOT_HI20: + case R_RISCV_TLS_GOT_HI20: + case R_RISCV_TLS_GD_HI20: + case R_RISCV_PCREL_HI20: + val -= (unsigned long)uloc; // fall through + case R_RISCV_HI20: + case R_RISCV_TPREL_HI20: + if ((long)val != (long)(int)val) + goto overflow; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_I: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_I: + case R_RISCV_TPREL_LO12_I: + *(unsigned *)loc = set_itype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_S: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_S: + case R_RISCV_TPREL_LO12_S: + *(unsigned *)loc = set_stype_imm(*(unsigned *)loc, val); + break; + + /* inner function label calculation, must not overflow */ + case R_RISCV_ADD8: + *(char *)loc += val; + break; + case R_RISCV_ADD16: + *(short *)loc += val; + break; + case R_RISCV_ADD32: + *(int *)loc += val; + break; + case R_RISCV_ADD64: + *(long *)loc += val; + break; + + case R_RISCV_SUB8: + *(char *)loc -= val; + break; + case R_RISCV_SUB16: + *(short *)loc -= val; + break; + case R_RISCV_SUB32: + *(int *)loc -= val; + break; + case R_RISCV_SUB64: + *(long *)loc -= val; + break; + + case R_RISCV_RVC_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 256 || (signed)val < -256) + goto overflow; + *(unsigned short *)loc = set_cbtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_JUMP: + val -= (unsigned long)uloc; + if ((signed)val >= 2048 || (signed)val < -2048) + goto overflow; + *(unsigned short *)loc = set_cjtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_LUI: + if ((signed)val >= (1 << 17) || (signed)val < -(1 << 17) || (val & 0x3f000) == 0) + goto overflow; + *(unsigned short *)loc = set_citype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_SET8: + *(char *)loc = val; + break; + case R_RISCV_SET16: + *(short *)loc = val; + break; + case R_RISCV_32_PCREL: + case R_RISCV_PLT32: + val -= (unsigned long)uloc; // fall through + case R_RISCV_32: + case R_RISCV_SET32: + if ((long)val != (long)(int)val) + goto overflow; + *(int *)loc = val; + break; + + case R_RISCV_SUB6: + char w6 = (*(char *)loc - (char)val) & 0x3f; + *(char *)loc = (*(char *)loc & 0xc0) | w6; + break; + case R_RISCV_SET6: + *(char *)loc = (*(char *)loc & 0xc0) | (val & 0x3f); + break; + + default: + log_error("upatch: unsupported RELA relocation: %lu\n", + GELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + return 0; + +overflow: + log_error("upatch: overflow in relocation type %d val %lx\n", + (int)GELF_R_TYPE(rel[i].r_info), val); + return -ENOEXEC; +} diff --git a/upatch-manage/arch/riscv64/resolve.c b/upatch-manage/arch/riscv64/resolve.c new file mode 100644 index 00000000..127fcc00 --- /dev/null +++ b/upatch-manage/arch/riscv64/resolve.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include "log.h" +#include "upatch-ptrace.h" +#include "upatch-resolve.h" + +/* + * auipc t6,0x0 + * ld t6,16(t6) # addr + * jr t6 + * undefined + */ +#define RISCV64_JMP_TABLE_JUMP0 0x010fbf8300000f97 +#define RISCV64_JMP_TABLE_JUMP1 0x000f8067 + +struct upatch_jmp_table_entry { + unsigned long inst[2]; + unsigned long addr[2]; +}; + +unsigned int get_jmp_table_entry() +{ + return sizeof(struct upatch_jmp_table_entry); +} + +static unsigned long setup_jmp_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long origin_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + if (index >= uelf->jmp_max_entry) { + log_error("jmp table overflow\n"); + return 0; + } + + table[index].inst[0] = RISCV64_JMP_TABLE_JUMP0; + table[index].inst[1] = RISCV64_JMP_TABLE_JUMP1; + table[index].addr[0] = jmp_addr; + table[index].addr[1] = origin_addr; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +static unsigned long setup_got_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long tls_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + + if (index >= uelf->jmp_max_entry) { + log_error("got table overflow\n"); + return 0; + } + + table[index].inst[0] = jmp_addr; + table[index].inst[1] = tls_addr; + table[index].addr[0] = 0xffffffff; + table[index].addr[1] = 0xffffffff; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type, unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_jmp_table(uelf, jmp_addr, (unsigned long)addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} + +unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type, unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + /* + * Addr with this type means the symbol is a dynamic TLS variable. + * Addr points to a GOT entry(16 bytes) having type + * + * typedef struct { + * unsigned long int ti_module; + * unsigned long int ti_offset; + * } tls_index; + * + * We also need copy ti_offset to our jump table. + * + * The corresponding symbol will associate with TLS_GD_HI20 + * relocation type, using this tls_index as argument to call + * `void *__tls_get_addr (tls_index *ti)` to resolve the real address. + */ + if (r_type == R_RISCV_TLS_DTPMOD64 && + upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), + &tls_addr, sizeof(tls_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} diff --git a/upatch-manage/upatch-patch.c b/upatch-manage/upatch-patch.c index 4a1e5689..c57ae74d 100644 --- a/upatch-manage/upatch-patch.c +++ b/upatch-manage/upatch-patch.c @@ -476,6 +476,23 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, upatch_func->addr = upatch_funcs_addr[i].addr; upatch_func->addr.old_addr += uelf->relf->load_bias; + +#ifdef __riscv +#define RISCV_MAX_JUMP_RANGE (1L << 31) + /* + * On RISC-V, to jump to arbitrary address, there must be + * at least 12 bytes to hold 3 instructors. Struct upatch_info + * new_insn field is only 8 bytes. We can only jump into + * +-2G ranges. Here do the check. + */ + long offset = upatch_func->addr.new_addr - upatch_func->addr.old_addr; + if (offset >= RISCV_MAX_JUMP_RANGE || offset < -RISCV_MAX_JUMP_RANGE) { + log_error("new_addr=%lx old_addr=%lx exceed +-2G range\n", + upatch_func->addr.new_addr, upatch_func->addr.old_addr); + goto out; + } +#endif + ret = upatch_process_mem_read(obj->proc, upatch_func->addr.old_addr, &upatch_func->old_insn, get_origin_insn_len()); if (ret) { @@ -484,7 +501,12 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, goto out; } +#ifdef __riscv + upatch_func->new_insn = get_new_insn( + upatch_func->addr.old_addr, upatch_func->addr.new_addr); +#else upatch_func->new_insn = get_new_insn(); +#endif log_debug("\taddr: 0x%lx -> 0x%lx, insn: 0x%lx -> 0x%lx, name: '%s'\n", upatch_func->addr.old_addr, upatch_func->addr.new_addr, upatch_func->old_insn[0], upatch_func->new_insn, diff --git a/upatch-manage/upatch-ptrace.c b/upatch-manage/upatch-ptrace.c index ab21891d..4ebe0380 100644 --- a/upatch-manage/upatch-ptrace.c +++ b/upatch-manage/upatch-ptrace.c @@ -25,6 +25,10 @@ #include #include +#ifdef __riscv +/* user_regs_struct defined here */ +#include +#endif #include #include diff --git a/upatch-manage/upatch-ptrace.h b/upatch-manage/upatch-ptrace.h index 8f36565d..16178188 100644 --- a/upatch-manage/upatch-ptrace.h +++ b/upatch-manage/upatch-ptrace.h @@ -22,6 +22,10 @@ #define __UPATCH_PTRACE__ #include +#ifdef __riscv +/* user_regs_struct defined here */ +#include +#endif #include "upatch-process.h" #include "list.h" @@ -79,6 +83,9 @@ long upatch_execute_remote(struct upatch_ptrace_ctx *, size_t get_origin_insn_len(void); size_t get_upatch_insn_len(void); size_t get_upatch_addr_len(void); +#ifdef __riscv +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr); +#else unsigned long get_new_insn(void); - +#endif #endif -- Gitee