From d7427fcf3a9a091f7e7b1e2e92c63df688d27886 Mon Sep 17 00:00:00 2001 From: wuhao Date: Thu, 28 Aug 2025 19:52:40 +0800 Subject: [PATCH] adapted to loongarch --- src/Makefile | 2 + src/arch/loongarch64/arch_coro.c | 113 ++++++ src/arch/loongarch64/arch_elf.c | 255 ++++++++++++++ src/arch/loongarch64/arch_parse.c | 268 +++++++++++++++ src/arch/loongarch64/arch_patch.c | 79 +++++ src/arch/loongarch64/arch_process.c | 104 ++++++ src/arch/loongarch64/arch_ptrace.c | 455 +++++++++++++++++++++++++ src/arch/loongarch64/arch_strip.c | 240 +++++++++++++ src/arch/loongarch64/loongarch64_imm.h | 63 ++++ src/include/kpatch_elf.h | 3 +- src/include/kpatch_elf_objinfo.h | 7 + tests/Makefile | 4 +- tests/run_tests.sh | 2 +- 13 files changed, 1592 insertions(+), 3 deletions(-) create mode 100644 src/arch/loongarch64/arch_coro.c create mode 100644 src/arch/loongarch64/arch_elf.c create mode 100644 src/arch/loongarch64/arch_parse.c create mode 100644 src/arch/loongarch64/arch_patch.c create mode 100644 src/arch/loongarch64/arch_process.c create mode 100644 src/arch/loongarch64/arch_ptrace.c create mode 100644 src/arch/loongarch64/arch_strip.c create mode 100644 src/arch/loongarch64/loongarch64_imm.h diff --git a/src/Makefile b/src/Makefile index c541529..1e3b2fe 100644 --- a/src/Makefile +++ b/src/Makefile @@ -26,6 +26,8 @@ ifeq ($(ARCH),aarch64) VPATH = arch/aarch64 else ifeq ($(ARCH),riscv64) VPATH = arch/riscv64 +else ifeq ($(ARCH),loongarch64) +VPATH = arch/loongarch64 else VPATH = arch/x86 endif diff --git a/src/arch/loongarch64/arch_coro.c b/src/arch/loongarch64/arch_coro.c new file mode 100644 index 0000000..602f261 --- /dev/null +++ b/src/arch/loongarch64/arch_coro.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/kpatch_user.h" +#include "include/kpatch_coro.h" +#include "include/kpatch_common.h" +#include "include/kpatch_elf.h" +#include "include/kpatch_ptrace.h" +#include "include/kpatch_log.h" + + +asm ( + ".global makecontext_call\n" + "makecontext_call:\n" + "move $fp, $sp\n" + "bstrpick.d $fp, $fp, 63, 4\n" + "addi.d $fp, $fp, -0x400\n" + "addi.d $a0, $fp, -128\n" // ss_sp = fp - 128 + "st.d $a0, $fp, 0x10\n" // uc_stack.ss_sp + "li.d $a0, 128\n" // ss_size = 128 + "st.d $a0, $fp, 0x20\n" // uc_stack.ss_size + "move $a0, $fp\n" + "li.d $a1, 0x100\n" + "move $a2, $zero\n" // argc = 0 + "jr $t0\n" // makecontext + "break 0\n" + "makecontext_call_end:\n" +); + +extern unsigned char makecontext_call, makecontext_call_end; + +int +locate_start_context_symbol(struct kpatch_process *proc, + unsigned long *pstart_context) +{ + struct object_file *olibc; + struct user_regs_struct regs; + int rv; + unsigned long makecontext; + + olibc = kpatch_process_get_obj_by_regex(proc, "^libc\\.so"); + if (olibc == NULL) { + kperr("FAIL. Can't find libc\n"); + return -1; + } + + rv = kpatch_resolve_undefined_single_dynamic(olibc, + "makecontext", &makecontext); + makecontext = vaddr2addr(olibc, makecontext); + if (rv < 0 || makecontext == 0) { + kperr("FAIL. Can't find makecontext\n"); + return -1; + } + + regs.regs[11] = makecontext; + rv = kpatch_execute_remote(proc2pctx(proc), &makecontext_call, + &makecontext_call_end - &makecontext_call, ®s); + if (rv < 0) { + kperr("FAIL. Can't execute makecontext\n"); + return -1; + } + + rv = kpatch_process_mem_read(proc, + regs.regs[23] - STACK_OFFSET_START_CONTEXT, + pstart_context, sizeof(*pstart_context)); + if (rv < 0) { + kperr("FAIL. Can't peek __start_context address\n"); + return -1; + } + return rv; +} + +int get_ptr_guard(struct kpatch_process *proc, + unsigned long *ptr_guard) +{ + (void)proc; + (void)ptr_guard; + kpinfo("NOTE: LoongArch not support pointer guard\n"); + return 0; +} + +int _UCORO_access_reg(unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, + int write, void *arg) +{ + struct UCORO_info *info = (struct UCORO_info *)arg; + unsigned long *regs = (unsigned long *)info->coro->env[0].__jmpbuf; + + if (write) { + kperr("_UCORO_access_reg: write is not implemeneted (%d)\n", reg); + return -UNW_EINVAL; + } + switch (reg) { + case UNW_LOONGARCH64_PC: // __pc + *val = regs[0]; break; + case UNW_LOONGARCH64_R23: // __regs[0] + *val = regs[1]; break; + case UNW_LOONGARCH64_R24: // __regs[1] + *val = regs[2]; break; + case UNW_LOONGARCH64_R25...UNW_LOONGARCH64_R31:// __regs[2-11] + *val = regs[3 + reg - UNW_LOONGARCH64_R25]; break; + case UNW_LOONGARCH64_R3: // __sp + *val = regs[13]; break; + default: + return _UPT_access_reg(as, reg, val, write, arg); + } + return 0; +} diff --git a/src/arch/loongarch64/arch_elf.c b/src/arch/loongarch64/arch_elf.c new file mode 100644 index 0000000..85d21cc --- /dev/null +++ b/src/arch/loongarch64/arch_elf.c @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "include/kpatch_common.h" +#include "include/kpatch_user.h" +#include "include/kpatch_process.h" +#include "include/kpatch_elf.h" +#include "include/kpatch_file.h" +#include "include/kpatch_ptrace.h" +#include "include/kpatch_log.h" +#include "loongarch64_imm.h" + + +/* + * We use jump table to access undefined global symbols. For STT_FUNC, + * it acts as a PLT and for STT_OBJECT as a GOT. + * When used as a GOT, that is used for a variable, we need jump table + * to hold variable's --value--. But now jump table holding is its address! + * See kpatch_elf.c#L770. + * We must fetch variable's value, just like dynamic linker COPY, and + * replace jump tables's holding. The max size limited to 8 bytes by now. + * + * In fact, this function should be in kpatch_arch_add_jmp_entry. + */ +static unsigned long adjust_got_jump_table(struct object_file *o, + GElf_Sym *s, + GElf_Rela *r) +{ + switch (GELF_R_TYPE(r->r_info)) { + case R_LARCH_GOT_PC_HI20: + case R_LARCH_GOT_PC_LO12: + case R_LARCH_GOT_HI20: + case R_LARCH_GOT_LO12: + return s->st_value + r->r_addend; + case R_LARCH_JUMP_SLOT: + if (s->st_shndx == SHN_UNDEF) { + if (!s->st_value) { + kperr("Undefined symbol without jump table entry\n"); + return -1ul; + } + return s->st_value; + } + break; + case R_LARCH_TLS_IE_PC_HI20: + case R_LARCH_TLS_IE_PC_LO12: + case R_LARCH_TLS_GD_PC_HI20: + case R_LARCH_TLS_GD_HI20: + case R_LARCH_TLS_GD_PCREL20_S2: + case R_LARCH_TLS_LD_PC_HI20: + case R_LARCH_TLS_LD_HI20: + case R_LARCH_TLS_LD_PCREL20_S2: + return r->r_addend + o->load_offset; + } + + return 0; +} + +int kpatch_arch_apply_relocate_add(struct object_file *o, GElf_Shdr *relsec) +{ + struct kpatch_file *kp = o->kpfile.patch; + GElf_Ehdr *ehdr = (void *)kp + kp->kpatch_offset; + GElf_Rela *relocs = (void *)ehdr + relsec->sh_offset; + GElf_Shdr *shdr = (void *)ehdr + ehdr->e_shoff; + GElf_Shdr *symhdr = NULL; + GElf_Shdr *tshdr = shdr + relsec->sh_info; + void *t = (void *)ehdr + shdr[relsec->sh_info].sh_offset; + void *tshdr2 = (void *)shdr[relsec->sh_info].sh_addr; + int i; + const char *scnname; + GElf_Sym *symtable; + + for (i = 1; i < ehdr->e_shnum; i++) { + if (shdr[i].sh_type == SHT_SYMTAB) { + symhdr = &shdr[i]; + break; + } + } + + if (symhdr == NULL) { + kperr("symhdr is null, failed to do relocations.\n"); + return -1; + } + + symtable = (GElf_Sym *)((void *)ehdr + symhdr->sh_offset); + scnname = secname(ehdr, shdr + relsec->sh_info); + kpdebug("applying relocations to '%s'\n", scnname); + + for (i = 0; i < relsec->sh_size / sizeof(*relocs); i++) { + GElf_Rela *r = relocs + i; + GElf_Sym *s; + unsigned long val; + void *loc, *loc2; + + if (r->r_offset >= tshdr->sh_size) { + kperr("Relocation offset for section '%s'" + " is at 0x%lx beyond the section size 0x%lx\n", + scnname, r->r_offset, tshdr->sh_size); + return -1; + } + + /* Location in our address space */ + loc = t + r->r_offset; + /* Location in target process address space (for relative addressing) */ + loc2 = tshdr2 + r->r_offset; + s = &symtable[GELF_R_SYM(r->r_info)]; + if ((val = adjust_got_jump_table(o, s, r)) == -1ul) + return -1; + else if (val == 1) + val = s->st_value; + else + val = s->st_value + r->r_addend; + + switch (GELF_R_TYPE(r->r_info)) { + case R_LARCH_32: + *(int32_t *)loc = (int32_t)val; + break; + case R_LARCH_64: + *(int64_t *)loc = val; + break; + case R_LARCH_ADD32: + *(int32_t *)loc += (int32_t)val; + break; + case R_LARCH_ADD64: + *(int64_t *)loc += val; + break; + case R_LARCH_SUB32: + *(int32_t *)loc -= (int32_t)val; + break; + case R_LARCH_SUB64: + *(int64_t *)loc -= val; + break; + case R_LARCH_ABS_HI20: + *(uint32_t *)loc = set_pcrel_imm(*(uint32_t *)loc, val >> 12); + break; + case R_LARCH_PCALA_HI20: + uint32_t imm_pcala_hi20 = (((val + 0x800) & ~0xFFF) - ((uint64_t)loc2 & ~0xFFF)) >> 12; + *(uint32_t *)loc = set_pcrel_imm(*(uint32_t *)loc, imm_pcala_hi20); + break; + case R_LARCH_ABS_LO12: + case R_LARCH_PCALA_LO12: + *(uint32_t *)loc = set_2ri12_imm(*(uint32_t *)loc, (int32_t)val); + break; + case R_LARCH_B16: + int64_t off_b16 = (int64_t)(val - (uint64_t)loc2); + if (off_b16 < -(1<<17) || off_b16 >= (1<<17)) { + kperr("R_LARCH_B16 relocation out of range: 0x%lx\n", val); + return -1; + } + if (off_b16 & 0x3) { + kperr("R_LARCH_B16 requires 4-byte alignment\n"); + return -1; + } + *(uint32_t *)loc = set_2ri16_imm(*(uint32_t *)loc, (int32_t)off_b16); + break; + case R_LARCH_B21: + int64_t off_b21 = (int64_t)(val - (uint64_t)loc2); + if (off_b21 < -(1<<22) || off_b21 >= (1<<22)) { + kperr("R_LARCH_B21 relocation out of range: 0x%lx\n", val); + return -1; + } + if (off_b21 & 0x3) { + kperr("R_LARCH_B21 requires 4-byte alignment\n"); + return -1; + } + *(uint32_t *)loc = set_1ri21_imm(*(uint32_t *)loc, (int32_t)off_b21); + break; + case R_LARCH_B26: + int64_t off_b26 = (int64_t)(val - (uint64_t)loc2); + if (off_b26 < -(1<<27) || off_b26 >= (1<<27)) { + kperr("R_LARCH_B26 relocation out of range: 0x%lx\n", val); + return -1; + } + if (off_b26 & 0x3) { + kperr("R_LARCH_B21 requires 4-byte alignment\n"); + return -1; + } + *(uint32_t *)loc = set_i26_imm(*(uint32_t *)loc, (int32_t)off_b26); + break; + case R_LARCH_PCREL20_S2: + val -= (unsigned long)loc2; + if ((long)val < -(1<<21) || (long)val >= (1<<21)) { + kperr("R_LARCH_PCREL20_S2 relocation out of range: 0x%lx\n", val); + return -1; + } + if (val & 0x3) { + kperr("R_LARCH_PCREL20_S2 requires 4-byte alignment\n"); + return -1; + } + *(uint32_t *)loc = set_pcrel_imm(*(uint32_t *)loc, (uint32_t)val); + break; + case R_LARCH_GOT_PC_HI20: + case R_LARCH_TLS_IE_PC_HI20: + int32_t imm_pc_hi20 = (((int64_t)val & ~0xFFF) - ((uint64_t)loc2 & ~0xFFF)) >> 12; + *(uint32_t *)loc = set_pcrel_imm(*(uint32_t *)loc, imm_pc_hi20); + break; + case R_LARCH_GOT_PC_LO12: + case R_LARCH_TLS_IE_PC_LO12: + int32_t imm_pc_lo12 = (int64_t)val & 0xFFF; + *(uint32_t *)loc = set_2ri12_imm(*(uint32_t *)loc, imm_pc_lo12); + break; + case R_LARCH_GOT_HI20: + case R_LARCH_TLS_IE_HI20: + *(uint32_t *)loc = set_pcrel_imm(*(uint32_t *)loc, (int32_t)val >> 12); + break; + case R_LARCH_GOT_LO12: + case R_LARCH_TLS_IE_LO12: + *(uint32_t *)loc = set_2ri12_imm(*(uint32_t *)loc, (int32_t)val); + break; + case R_LARCH_RELAX: + break; + case R_LARCH_NONE: + break; + default: + kperr("Unsupported relocation type: %ld\n", GELF_R_TYPE(r->r_info)); + return -EINVAL; + } + } + + return 0; +} + +/* all undefined global symbols access(GOT/PLT) go through this table */ +#define JMP_TABLE_JUMP0 0x1c00000f // pcaddu12i $t3, 0 +#define JMP_TABLE_JUMP1 0x28c041ef // ld.d $t3, $t3, 16 +#define JMP_TABLE_JUMP2 0x4c0001ed // jirl $t1, $t3, 0 +unsigned long kpatch_arch_add_jmp_entry(struct object_file *o, unsigned long addr) +{ + struct kpatch_jmp_table_entry entry; + int e, *p = (int*)&entry; + + p[0] = JMP_TABLE_JUMP0; + p[1] = JMP_TABLE_JUMP1; + p[2] = JMP_TABLE_JUMP2; + entry.addr = addr; + if (o->jmp_table == NULL) { + kperr("JMP TABLE not found\n"); + return 0; + } + + if (o->jmp_table->cur_entry >= o->jmp_table->max_entry) { + kperr("JMP TABLE overflow\n"); + return 0; + } + e = o->jmp_table->cur_entry++; + o->jmp_table->entries[e] = entry; + return (unsigned long)(o->kpta + o->kpfile.patch->jmp_offset + \ + ((void *)&o->jmp_table->entries[e] - (void *)o->jmp_table)); +} diff --git a/src/arch/loongarch64/arch_parse.c b/src/arch/loongarch64/arch_parse.c new file mode 100644 index 0000000..aaf6144 --- /dev/null +++ b/src/arch/loongarch64/arch_parse.c @@ -0,0 +1,268 @@ +#include + +#include "include/kpatch_log.h" +#include "include/kpatch_parse.h" +#include "include/kpatch_flags.h" + +int is_function_start(struct kp_file *f, int l, kpstr_t *nm) +{ + char *s; + kpstr_t nm2, attr; + int l0 = l, func = 0; + + kpstrset(nm, "", 0); + kpstrset(&attr, "", 0); + for (; l < f->nr_lines; l++) { + if (l != l0 && cline(f, l)[0] == '\0') + continue; + if ((is_sect_cmd(f, l) && is_code_sect(csect(f, l))) || + ctype(f, l) == DIRECTIVE_ALIGN) + continue; + get_type_args(cline(f, l), &nm2, &attr); + if ((ctype(f, l) == DIRECTIVE_WEAK && l0 != l) || + ctype(f, l) == DIRECTIVE_GLOBL || ctype(f, l) == DIRECTIVE_HIDDEN || + ctype(f, l) == DIRECTIVE_PROTECTED || ctype(f, l) == DIRECTIVE_INTERNAL || + (ctype(f, l) == DIRECTIVE_TYPE && !kpstrcmpz(&attr, "@function"))) { + s = cline(f, l); + get_token(&s, &nm2); /* skip command */ + get_token(&s, &nm2); + if (nm->l && kpstrcmp(nm, &nm2)) /* verify name matches in all .weak/.globl/.type commands */ + return 0; + *nm = nm2; + func = func ? 1 : ctype(f, l) == DIRECTIVE_TYPE; + continue; + } + + /* particularly: for "-freorder-functions" optimization under -O2/-O3/-Os, + ".LCOLD*" or ".LHOT*" label may appear at the head of function or variable cblock, + it should not be divided into an independent cblock belonging to ATTR or OTHER */ + if(ctype(f, l) == DIRECTIVE_LABEL) { + s = cline(f, l); + if(strstr(s, ".LCOLD") || strstr(s, ".LHOT")) + continue; + } + + break; + } + return func; +} + +void recog_func_attr(struct kp_file *f, int i, kpstr_t *nm, int *cnt) +{ + kpstr_t func_nm, func_attr; + + if(ctype(f, i) == DIRECTIVE_TYPE) { + kpstrset(&func_nm, "", 0); + kpstrset(&func_attr, "", 0); + + get_type_args(cline(f, i), &func_nm, &func_attr); + if(!kpstrcmpz(&func_attr, "@function")) { + if(func_nm.l > nm->l) + remove_cold_suffix(&func_nm); /* remove .cold */ + + if(!kpstrcmp(&func_nm, nm)) /* verify name matches */ + ++(*cnt); + } + } +} + +int is_data_def(char *s, int type) +{ + kpstr_t t; + + get_token(&s, &t); + if ( + /* strings */ + !kpstrcmpz(&t, ".ascii") || + !kpstrcmpz(&t, ".asciz") || + !kpstrcmpz(&t, ".string") || + /* numeric */ + !kpstrcmpz(&t, ".byte") || + !kpstrcmpz(&t, ".half") || + !kpstrcmpz(&t, ".word") || + !kpstrcmpz(&t, ".dword") || + !kpstrcmpz(&t, ".long") || + !kpstrcmpz(&t, ".quad") || + /* other */ + !kpstrcmpz(&t, ".value") || + !kpstrcmpz(&t, ".comm") || + !kpstrcmpz(&t, ".zero") || + !kpstrcmpz(&t, ".space") || + !kpstrcmpz(&t, ".uleb128") || + !kpstrcmpz(&t, ".sleb128") || + !kpstrcmpz(&t, ".8byte") || + !kpstrcmpz(&t, ".4byte") + ) + return 1; + return 0; +} + +int is_variable_start(struct kp_file *f, int l, int *e, int *pglobl, kpstr_t *nm) +{ + char *s; + int l0 = l, globl = 0; + kpstr_t nm2, attr; + + kpstrset(nm, "", 0); + kpstrset(&attr, "", 0); + for ( ; cline(f, l); l++) { + + /* first verify that all the commands we met has the same symbol name... just to be safe! */ + s = cline(f, l); + if (*s == '\0' && l != l0) + continue; + + /* particularly: for "-freorder-functions" optimization under -O2/-O3/-Os, + ".LCOLD*" or ".LHOT*" label may appear at the head of function or variable cblock, + it should not be divided into an independent cblock belonging to ATTR or OTHER */ + if(ctype(f, l) == DIRECTIVE_LABEL) { + if(strstr(s, ".LCOLD") || strstr(s, ".LHOT")) + continue; + } + + switch (ctype(f, l)) { + case DIRECTIVE_TYPE: + case DIRECTIVE_GLOBL: + case DIRECTIVE_LOCAL: + get_token(&s, &nm2); + case DIRECTIVE_LABEL: + get_token(&s, &nm2); + if (nm->l && kpstrcmp(nm, &nm2)) /* some other symbol met... stop */ + return 0; + *nm = nm2; + break; + } + + switch (ctype(f, l)) { + case DIRECTIVE_TEXT: + case DIRECTIVE_DATA: + case DIRECTIVE_BSS: + case DIRECTIVE_SECTION: + case DIRECTIVE_PUSHSECTION: + case DIRECTIVE_POPSECTION: + case DIRECTIVE_PREVIOUS: + case DIRECTIVE_SUBSECTION: + break; + case DIRECTIVE_TYPE: + get_type_args(cline(f, l), &nm2, &attr); + if (kpstrcmpz(&attr, "@object") && kpstrcmpz(&attr, "@gnu_unique_object")) + return 0; + break; + case DIRECTIVE_GLOBL: + globl = 1; + break; + case DIRECTIVE_ALIGN: + break; + case DIRECTIVE_COMMENT: + case DIRECTIVE_SIZE: + /* can't start with .size */ + if (l0 == l) + return 0; + break; + case DIRECTIVE_LABEL: + if (!is_data_sect(csect(f, l))) + return 0; + /* fall throught */ + case DIRECTIVE_LOCAL: + if (e) + *e = l + 1; + if (pglobl) + *pglobl = globl; + return 1; + default: + return 0; + } + } + return 0; +} + +/* break manually crafted multiple statements separated by ; to separate lines */ +void init_multilines(struct kp_file *f) +{ + int i, nr, sz = 64, slen, first_token; + char **lines = NULL, *s, *se; + int *lines_num = NULL; + kpstr_t t; + + nr = 0; + for (i = 0; i < f->nr_lines; i++) { + if (nr + 1000 >= sz || !lines) { + sz *= 2; + lines = kp_realloc(lines, (sz/2) * sizeof(char *), sz * sizeof(char *)); + lines_num = kp_realloc(lines_num, (sz/2) * sizeof(int), sz * sizeof(int)); + } + + s = f->lines[i]; + if (strpbrk(s, ";:") != NULL) { + while (s && *s) { + se = s; + slen = strlen(s); + first_token = 1; + while (se) { + get_token(&se, &t); + if (t.l == 1 && t.s[0] == '#') + goto done; + if (t.l == 1 && t.s[0] == ';') { + slen = t.s - s; + break; + } + /* first token with ':' after is + * the label, separate it unless + * it is done already (next non-blank + * is '\0') + */ + if (first_token && se && + se[0] == ':' && + se[1] != '\0') { + slen = se - s + 1; + se++; + break; + } + first_token = 0; + } + lines[nr] = strndup(s, slen); + s = se; + lines_num[nr] = i; + nr++; + if (nr >= sz) + kpfatal("oops, not prepared to handle >1000 asm statements in single line"); + } + free(f->lines[i]); + } else { +done: + lines[nr] = s; + lines_num[nr] = i; + nr++; + } + } + free(f->lines); + f->lines = lines; + f->lines_num = lines_num; + f->nr_lines = nr; +} + +int parse_ctype(char *origs, bool with_checks) +{ + char *s = origs; + int type; + kpstr_t t; + + s = skip_blanks(s); + if (s[0] == '#') + return DIRECTIVE_COMMENT; /* Single-line comment */ + + get_token(&s, &t); + type = find_ctype(&t); + + if (type >= 0) + return type; + + /* + * Asm labels starting from digits are local labels, they can be even created multiple times in the same function. + * So there is no reason to handle them and bother with renaming at all. It would create conflicts at our brains + * and require special tracking and matching... Brrrr.... */ + if (s && *s == ':') + return !isdigit(t.s[0]) ? DIRECTIVE_LABEL : DIRECTIVE_LOCAL_LABEL; + + return DIRECTIVE_OTHER; +} diff --git a/src/arch/loongarch64/arch_patch.c b/src/arch/loongarch64/arch_patch.c new file mode 100644 index 0000000..ef227e4 --- /dev/null +++ b/src/arch/loongarch64/arch_patch.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/kpatch_patch.h" +#include "include/kpatch_user.h" +#include "include/kpatch_storage.h" +#include "include/kpatch_process.h" +#include "include/kpatch_file.h" +#include "include/kpatch_common.h" +#include "include/kpatch_elf.h" +#include "include/kpatch_ptrace.h" +#include "include/list.h" +#include "include/kpatch_log.h" +#include "loongarch64_imm.h" + +/* + * This flag is local, i.e. it is never stored to the + * patch applied to patient's memory. + */ +unsigned int PATCH_APPLIED = (1 << 31); +unsigned int HUNK_SIZE = 4; + +int patch_apply_hunk(struct object_file *o, size_t nhunk) +{ + int ret; + struct kpatch_info *info = &o->info[nhunk]; + unsigned long pundo; + + if (is_new_func(info)) + return 0; + + if (info->daddr & 0x3) { + kperr("unaligned patch address: 0x%lx\n", info->daddr); + return -1; + } + pundo = o->kpta + o->kpfile.patch->user_undo + nhunk * HUNK_SIZE; + kpinfo("%s origcode from 0x%lx+0x%x to 0x%lx\n", + o->name, info->daddr, HUNK_SIZE, pundo); + ret = kpatch_process_memcpy(o->proc, pundo, info->daddr, HUNK_SIZE); + if (ret < 0) + return ret; + + kpinfo("%s hunk 0x%lx+0x%x -> 0x%lx+0x%x\n", + o->name, info->daddr, info->dlen, info->saddr, info->slen); + + int64_t byte_offset = (int64_t)(info->saddr - info->daddr); + int64_t imm26 = byte_offset >> 2; + if (imm26 < -(1 << 25) || imm26 >= (1 << 25) - 1) { + kperr("branch target out of range: %ld\n", imm26); + return -1; + } + + uint32_t imm26_l16= (uint32_t)(imm26 & 0xFFFFu) ; + uint32_t imm26_h10= (uint32_t)((imm26 >> 16) & 0x3FFu); + uint32_t inst = 0x50000000 | (imm26_l16 << 10) | imm26_h10; + char code[HUNK_SIZE]; + *(uint32_t *)code = inst; + ret = kpatch_process_mem_write(o->proc, + code, + info->daddr, + HUNK_SIZE); + if (ret < 0) { + kperr("Failed to write remote info"); + } + + /* + * NOTE(pboldin): This is only stored locally, as information have + * been copied to patient's memory already. + */ + info->flags |= PATCH_APPLIED; + return ret ? -1 : 0; +} diff --git a/src/arch/loongarch64/arch_process.c b/src/arch/loongarch64/arch_process.c new file mode 100644 index 0000000..1e99565 --- /dev/null +++ b/src/arch/loongarch64/arch_process.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "include/kpatch_process.h" +#include "include/kpatch_file.h" +#include "include/kpatch_common.h" +#include "include/kpatch_elf.h" +#include "include/kpatch_ptrace.h" +#include "include/list.h" +#include "include/kpatch_log.h" + +/* + * Find region for a patch. Take object's `previous_hole` as a left candidate + * and the next hole as a right candidate. Pace through them until there is + * enough space in the hole for the patch. + * + * Since holes can be much larger than 2GiB take extra caution to allocate + * patch region inside the (-2GiB, +2GiB) range from the original object. + */ +unsigned long object_find_patch_region(struct object_file *obj, + size_t memsize, + struct vm_hole **hole) +{ + struct list_head *head = &obj->proc->vmaholes; + struct vm_hole *left_hole = obj->previous_hole; + struct vm_hole *right_hole = next_hole(left_hole, head); + unsigned long max_distance = 0x80000000; + struct obj_vm_area *sovma; + + unsigned long obj_start, obj_end; + unsigned long region_start = 0, region_end = 0; + + kpdebug("Looking for patch region for '%s'...\n", obj->name); + + sovma = list_first_entry(&obj->vma, struct obj_vm_area, list); + obj_start = sovma->inmem.start; + sovma = list_entry(obj->vma.prev, struct obj_vm_area, list); + obj_end = sovma->inmem.end; + + max_distance -= memsize; + /* TODO carefully check for the holes laying between obj_start and + * obj_end, i.e. just after the executable segment of an executable + */ + while (left_hole != NULL && right_hole != NULL) { + if (right_hole != NULL && + right_hole->start - obj_start > max_distance) + right_hole = NULL; + else if (hole_size(right_hole) > memsize) { + region_start = right_hole->start; + region_end = + (right_hole->end - obj_start) <= max_distance ? + right_hole->end - memsize : + obj_start + max_distance; + *hole = right_hole; + break; + } else + right_hole = next_hole(right_hole, head); + + if (left_hole != NULL && + obj_end - left_hole->end > max_distance) + left_hole = NULL; + else if (hole_size(left_hole) > memsize) { + region_start = + (obj_end - left_hole->start) <= max_distance ? + left_hole->start : obj_end > max_distance ? + obj_end - max_distance : 0; + region_end = left_hole->end - memsize; + *hole = left_hole; + break; + } else + left_hole = prev_hole(left_hole, head); + } + + if (region_start == region_end) { + kperr("can't find suitable region for patch on '%s'\n", + obj->name); + return -1UL; + } + region_start = (region_start >> PAGE_SHIFT) << PAGE_SHIFT; + kpdebug("Found patch region for '%s' at %lx\n", obj->name, region_start); + return region_start; +} diff --git a/src/arch/loongarch64/arch_ptrace.c b/src/arch/loongarch64/arch_ptrace.c new file mode 100644 index 0000000..d9871b8 --- /dev/null +++ b/src/arch/loongarch64/arch_ptrace.c @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "include/kpatch_process.h" +#include "include/kpatch_common.h" +#include "include/kpatch_ptrace.h" +#include "include/kpatch_log.h" + +#include + +/* LoongArch register indices */ +#define REG_A0 4 +#define REG_A1 5 +#define REG_A2 6 +#define REG_A3 7 +#define REG_A4 8 +#define REG_A5 9 +#define REG_A6 10 +#define REG_A7 11 +#define REG_PC csr_era + + +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) { + kplogerror("ptrace(PTRACE_GETREGS)"); + 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) { + kplogerror("ptrace(PTRACE_SETREGSET)"); + return -1; + } + return 0; +} + +/** + * This is rather tricky since we are accounting for the non-main + * thread calling for execve(). See `ptrace(2)` for details. + * + * FIXME(pboldin): this is broken for multi-threaded calls + * to execve. Sight. + */ +int +kpatch_arch_ptrace_kickstart_execve_wrapper(kpatch_process_t *proc) +{ + int ret = 0; + int pid = 0; + struct kpatch_ptrace_ctx *pctx, *ptmp, *execve_pctx = NULL; + long rv; + struct user_regs_struct regs; + + kpdebug("kpatch_arch_ptrace_kickstart_execve_wrapper\n"); + + list_for_each_entry(pctx, &proc->ptrace.pctxs, list) { + /* proc->pid equals to THREAD ID of the thread + * executing execve.so's version of execve + */ + if (pctx->pid != proc->pid) + continue; + execve_pctx = pctx; + break; + } + + if (execve_pctx == NULL) { + kperr("can't find thread executing execve\n"); + return -1; + } + + /* Send a message to our `execve` wrapper so it will continue + * execution + */ + ret = send(proc->send_fd, &ret, sizeof(int), 0); + if (ret < 0) { + kplogerror("send failed\n"); + return ret; + } + + /* Wait for it to reach BRKN instruction just before real execve */ + while (1) { + ret = wait_for_stop(execve_pctx, NULL); + if (ret < 0) { + kplogerror("wait_for_stop\n"); + return ret; + } + + rv = read_gregs(execve_pctx->pid, ®s); + if (rv == -1) + return rv; + + rv = ptrace(PTRACE_PEEKTEXT, execve_pctx->pid, (void *)regs.REG_PC, NULL); + if ((rv == -1) && errno) + return rv; + if ((unsigned)rv == *(unsigned*)(char[])BREAK_INSN) + break; + } + + /* Wait for SIGTRAP from the execve. It happens from the thread + * group ID, so find it if thread doing execve() is not it. */ + if (execve_pctx != proc2pctx(proc)) { + pid = get_threadgroup_id(proc->pid); + if (pid < 0) + return -1; + + proc->pid = pid; + } + + ret = wait_for_stop(execve_pctx, (void *)(uintptr_t)pid); + if (ret < 0) { + kplogerror("waitpid\n"); + return ret; + } + + list_for_each_entry_safe(pctx, ptmp, &proc->ptrace.pctxs, list) { + if (pctx->pid == proc->pid) + continue; + kpatch_ptrace_detach(pctx); + kpatch_ptrace_ctx_destroy(pctx); + } + + /* Suddenly, /proc/pid/mem gets invalidated */ + { + char buf[sizeof("/proc/0123456789/mem")]; + close(proc->memfd); + + snprintf(buf, sizeof(buf), "/proc/%d/mem", proc->pid); + proc->memfd = open(buf, O_RDWR); + if (proc->memfd < 0) { + kplogerror("Failed to open proc mem\n"); + return -1; + } + } + + kpdebug("...done\n"); + + return 0; +} + +int +wait_for_mmap(struct kpatch_ptrace_ctx *pctx, + unsigned long *pbase) +{ + int ret, status = 0, insyscall = 0; + long rv; + struct user_regs_struct regs; + + while (1) { + ret = ptrace(PTRACE_SYSCALL, pctx->pid, NULL, + (void *)(uintptr_t)status); + if (ret < 0) { + kplogerror("can't PTRACE_SYSCALL tracee - %d\n", + pctx->pid); + return -1; + } + + ret = waitpid(pctx->pid, &status, __WALL); + if (ret < 0) { + kplogerror("can't wait tracee - %d\n", pctx->pid); + return -1; + } + + if (WIFEXITED(status)) { + status = WTERMSIG(status); + continue; + } else if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + status = 0; + continue; + } + + status = 0; + + if (insyscall == 0) { + rv = read_gregs(pctx->pid, ®s); + if (rv == -1) + return -1; + insyscall = regs.regs[REG_A7]; + continue; + } else if (insyscall == __NR_mmap) { + rv = read_gregs(pctx->pid, ®s); + if (rv == -1) + return -1; + *pbase = regs.REG_PC; + break; + } + + insyscall = !insyscall; + } + + return 0; +} + +int kpatch_arch_syscall_remote(struct kpatch_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[] = { + 0x00, 0x00, 0x2b, 0x00, // syscall 0 + 0x00, 0x00, 0x2a, 0x00 // break 0 + }; + int ret; + + kpdebug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); + memset(®s, 0, sizeof(regs)); + regs.regs[REG_A7] = (unsigned long)nr; + regs.regs[REG_A0] = arg1; + regs.regs[REG_A1] = arg2; + regs.regs[REG_A2] = arg3; + regs.regs[REG_A3] = arg4; + regs.regs[REG_A4] = arg5; + regs.regs[REG_A5] = arg6; + + ret = kpatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); + if (ret == 0) + *res = regs.regs[REG_A0]; + + + return ret; +} + +int kpatch_arch_ptrace_resolve_ifunc(struct kpatch_ptrace_ctx *pctx, + unsigned long *addr) +{ + struct user_regs_struct regs; + unsigned char call_ra[] = { + 0x81, 0x00, 0x00, 0x4c, // jirl ra, r4, 0 + 0x00, 0x00, 0x2a, 0x00, // break 0 + }; + int ret; + + kpdebug("Executing call_ra %lx (pid %d)\n", *addr, pctx->pid); + memset(®s, 0, sizeof(regs)); + regs.regs[REG_A0] = *addr; + + ret = kpatch_execute_remote(pctx, call_ra, sizeof(call_ra), ®s); + if (ret == 0) + *addr = regs.regs[REG_A0]; + + return ret; +} + +int +kpatch_arch_execute_remote_func(struct kpatch_ptrace_ctx *pctx, + const unsigned char *code, + size_t codelen, + struct user_regs_struct *pregs, + int (*func)(struct kpatch_ptrace_ctx *pctx, const void *data), + const void *data) +{ + struct user_regs_struct orig_regs, regs; + unsigned char orig_code[codelen]; + int ret; + kpatch_process_t *proc = pctx->proc; + unsigned long libc_base = proc->libc_base; + + ret = read_gregs(pctx->pid, &orig_regs); + if (ret < 0) + return -1; + ret = kpatch_process_mem_read(proc, libc_base, + (unsigned long *)orig_code, codelen); + if (ret < 0) { + kplogerror("can't peek original code - %d\n", pctx->pid); + return -1; + } + + ret = kpatch_process_mem_write(proc, (unsigned long *)code, + libc_base, codelen); + if (ret < 0) { + kplogerror("can't poke syscall code - %d\n", pctx->pid); + goto poke_back; + } + + /* set new regs: original regs with new pc, new copy-regs(arguments) */ + regs = orig_regs; + regs.REG_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) { + kplogerror("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: + kpatch_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->regs[x] = src->regs[x] + COPY_REG(REG_A0); + COPY_REG(REG_A1); + COPY_REG(REG_A2); + COPY_REG(REG_A3); + COPY_REG(REG_A4); + COPY_REG(REG_A5); + COPY_REG(REG_A6); + COPY_REG(REG_A7); +#undef COPY_REG +} + +int +kpatch_arch_ptrace_waitpid(kpatch_process_t *proc, + struct timespec *timeout, + const sigset_t *sigset) +{ + struct kpatch_ptrace_ctx *pctx; + siginfo_t siginfo; + int ret, status; + pid_t pid; + struct user_regs_struct regs; + + /* Immediately reap one attached thread */ + pid = waitpid(-1, &status, __WALL | WNOHANG); + + if (pid < 0) { + kplogerror("can't wait for tracees\n"); + return -1; + } + + /* There is none ready, wait for notification via signal */ + if (pid == 0) { + ret = sigtimedwait(sigset, &siginfo, timeout); + if (ret == -1 && errno == EAGAIN) { + /* We have timeouted */ + return -1; + } + + if (ret == -1 && errno == EINVAL) { + kperr("invalid timeout\n"); + return -1; + } + + /* We have got EINTR and must restart */ + if (ret == -1 && errno == EINTR) + return 0; + + /** + * Kernel stacks signals that follow too quickly. + * Deal with it by waiting for any child, not just + * one that is specified in signal + */ + pid = waitpid(-1, &status, __WALL | WNOHANG); + + if (pid == 0) { + kperr("missing waitpid for %d\n", siginfo.si_pid); + return 0; + } + + if (pid < 0) { + kplogerror("can't wait for tracee %d\n", siginfo.si_pid); + return -1; + } + } + + if (!WIFSTOPPED(status) && WIFSIGNALED(status)) { + /* Continue, resending the signal */ + ret = ptrace(PTRACE_CONT, pid, NULL, + (void *)(uintptr_t)WTERMSIG(status)); + if (ret < 0) { + kplogerror("can't start tracee %d\n", pid); + return -1; + } + return 0; + } + + if (WIFEXITED(status)) { + pctx = kpatch_ptrace_find_thread(proc, pid, 0UL); + if (pctx == NULL) { + kperr("got unexpected child '%d' exit\n", pid); + } else { + /* It's dead */ + pctx->pid = pctx->running = 0; + } + return 1; + } + + ret = read_gregs(pid, ®s); + if (ret < 0) + return -1; + + pctx = kpatch_ptrace_find_thread(proc, pid, regs.REG_PC); + + if (pctx == NULL) { + /* We either don't know anything about this thread or + * even worse -- we stopped it in the wrong place. + * Bail out. + */ + pctx = kpatch_ptrace_find_thread(proc, pid, 0); + if (pctx != NULL) { + pctx->running = 0; + kperr("the thread ran out: %d, pc= %lx, expected = %lx\n", + pid, regs.REG_PC, pctx->execute_until); + } else { + kperr("the thread ran out: %d, pc= %lx\n", pid, regs.REG_PC); + } + + /* TODO: fix the latter by SINGLESTEPping such a thread with + * the original instruction in place */ + errno = ESRCH; + return -1; + } + + pctx->running = 0; + + /* Restore thread registers, pctx is now valid */ + kpdebug("Got thread %d at %lx\n", pctx->pid, + regs.REG_PC - BREAK_INSN_LENGTH); + + regs.REG_PC = pctx->execute_until; + + ret = write_gregs(pctx->pid, ®s); + if (ret < 0) + return -1; + + return 1; +} diff --git a/src/arch/loongarch64/arch_strip.c b/src/arch/loongarch64/arch_strip.c new file mode 100644 index 0000000..356c08b --- /dev/null +++ b/src/arch/loongarch64/arch_strip.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/kpatch_file.h" +#include "include/kpatch_common.h" + +#include +#include "include/kpatch_elf_objinfo.h" + +#include "include/kpatch_log.h" +#include "include/kpatch_strip.h" + +#define SECTION_OFFSET_FOUND 0x0 +#define SECTION_NOT_FOUND 0x1 + +/* Find Global Offset Table entry with the address of the TLS-variable + * specified by the `tls_offset`. Dynamic linker allocates Thread-Local storage + * as described in ABI and places the correct offset at that address in GOT. We + * then read this offset and use it in our jmp table. + */ +static unsigned long +objinfo_find_tls_got_by_offset(kpatch_objinfo *oi, + unsigned long tls_offset) +{ + Elf64_Rela *rela; + size_t nrela; + + if (kpatch_objinfo_load_tls_reladyn(oi) < 0) + kpfatalerror("kpatch_objinfo_load_tls_reladyn"); + + rela = oi->tlsreladyn; + nrela = oi->ntlsreladyn; + + for (; nrela != 0; rela++, nrela--) { + if (GELF_R_TYPE(rela->r_info) != R_LARCH_TLS_IE_HI20 && + GELF_R_TYPE(rela->r_info) != R_LARCH_TLS_IE_LO12) + continue; + + if (ELF64_R_SYM(rela->r_info) == 0 && + rela->r_addend == tls_offset) + return rela->r_offset; + } + + kperr("cannot find GOT entry for %lx\n", tls_offset); + return 0; +} + +static unsigned long +objinfo_find_tls_got_by_symname(kpatch_objinfo *oi, + const char *symname) +{ + Elf64_Rela *rela; + size_t nrela; + Elf64_Sym sym; + + if (kpatch_objinfo_load_tls_reladyn(oi) < 0) + kpfatalerror("kpatch_objinfo_load_tls_reladyn"); + + rela = oi->tlsreladyn; + nrela = oi->ntlsreladyn; + + for (; nrela != 0; rela++, nrela--) { + const char *origname; + + if (GELF_R_TYPE(rela->r_info) != R_LARCH_TLS_IE_HI20 && + GELF_R_TYPE(rela->r_info) != R_LARCH_TLS_IE_LO12) + continue; + + if (ELF64_R_SYM(rela->r_info) == 0) + continue; + + if (!gelf_getsym(oi->dynsymtab, ELF64_R_SYM(rela->r_info), &sym)) + kpfatalerror("gelf_getsym"); + + origname = kpatch_objinfo_strptr(oi, DYNAMIC_NAME, + sym.st_name); + + if (strcmp(origname, symname) == 0 && + rela->r_addend == 0) + return rela->r_offset; + } + + kpfatalerror("cannot find GOT entry for %s\n", symname); + return 0; +} + +static inline int +update_reloc_with_tls_got_entry(kpatch_objinfo *origbin, + kpatch_objinfo *patch, + GElf_Rela *rela, + GElf_Sym *sym) +{ + unsigned long got_offset; + char *symname, *tmp; + + symname = (char *)kpatch_objinfo_strptr(patch, SYMBOL_NAME, sym->st_name); + + tmp = strchr(symname, '@'); + if (tmp != NULL) { + *tmp = '\0'; + } + + if (GELF_ST_BIND(sym->st_info) == STB_LOCAL || + sym->st_shndx != SHN_UNDEF) { + /* This symbol should have a TLS_TPRELnn entry in the GOT with + * the offset of sym->st_value. Find GOT entry for this TLS + * variable. Make st_value point to that GOT entry and mark it + * with flag. + */ + + got_offset = objinfo_find_tls_got_by_offset(origbin, sym->st_value); + } else if (GELF_ST_BIND(sym->st_info) == STB_GLOBAL && + sym->st_shndx == SHN_UNDEF) { + /* This is a GLOBAL symbol we require from some other binary. + * It has a GOT entry that is referenced by the symbol name, + * not the offset. + */ + + got_offset = objinfo_find_tls_got_by_symname(origbin, symname); + } else { + kperr("get symbol '%s' got_offset failed\n", symname); + return -1; + } + + if (rela->r_addend != got_offset) { + kpinfo("Changing TLS_GOT_HI20 symbol %s from %lx to %lx\n", + symname, rela->r_addend, got_offset); + rela->r_addend = got_offset; + } + return 0; +} + +int +kpatch_arch_fixup_rela_update_tls(kpatch_objinfo *origbin, + kpatch_objinfo *patch, + GElf_Rela *rela, + GElf_Sym *sym, + GElf_Shdr *sh_text, + unsigned char *text) +{ + const char *symname = kpatch_objinfo_strptr(patch, SYMBOL_NAME, sym->st_name); + + switch (GELF_R_TYPE(rela->r_info)) { + case R_LARCH_TLS_LE_LO12_R: + case R_LARCH_TLS_LE_LO12: { + unsigned long orig_offset; + if (kpatch_get_original_symbol_loc(origbin, symname, &orig_offset, NULL) != SECTION_OFFSET_FOUND) { + kperr("TLS symbol %s not found in target program\n", symname); + return -1; + } + if (orig_offset != sym->st_value) { + kperr("TLS symbol %s offset not match (orig:0x%lx != patch:0x%lx)\n", + symname, orig_offset, sym->st_value); + return -1; + } + rela->r_info = GELF_R_INFO(0, R_LARCH_NONE); + break; + } + + case R_LARCH_TLS_IE_PC_HI20: + case R_LARCH_TLS_IE_PC_LO12: + case R_LARCH_TLS_IE_HI20: + case R_LARCH_TLS_IE_LO12: + return update_reloc_with_tls_got_entry(origbin, patch, rela, sym); + + default: + kperr("Unsupported TLS relocation type: %lu\n", GELF_R_TYPE(rela->r_info)); + return -1; + } + return 0; +} + +int +kpatch_arch_fixup_rela_copy(kpatch_objinfo *origbin, GElf_Sym *s, + const char *symname) +{ + if (strchr(symname, '@') && + !kpatch_objinfo_find_scn_by_name(origbin, symname, NULL)) { + s->st_shndx = SHN_UNDEF; + return 1; + } + return 0; +} + +int +kpatch_arch_fixup_addr_bias(kpatch_objinfo *orig, kpatch_objinfo *patch) +{ + GElf_Sym sorig, spat; + GElf_Shdr shdr; + const char *name, *tmp; + size_t i, j; + + for (i = 1; i < patch->nsym; i++) { + if (!gelf_getsym(patch->symtab, i, &spat)) { + kperr("Failed to do gelf_getsym"); + return -1; + } + if ((spat.st_shndx == SHN_UNDEF) || + (GELF_ST_TYPE(spat.st_info) != STT_FUNC)) + continue; + if (kpatch_objinfo_getshdr(patch, spat.st_shndx, &shdr) == NULL) + return -1; + name = kpatch_objinfo_strptr(patch, SECTION_NAME, shdr.sh_name); + if (!strncmp(name, ".kpatch.", 8)) + continue; + + name = kpatch_objinfo_strptr(patch, SYMBOL_NAME, spat.st_name); + for (j = 1; j < orig->nsym; j++) { + if (!gelf_getsym(orig->symtab, j, &sorig)) { + kperr("Failed to do gelf_getsym origbin\n"); + return -1; + } + tmp = kpatch_objinfo_strptr(orig, SYMBOL_NAME, sorig.st_name); + if (!strcmp(tmp, name) && (sorig.st_info == spat.st_info)) { + if (!kpatch_objinfo_getshdr(orig, sorig.st_shndx, &shdr)) + return -1; + break; + } + } + + if (j == orig->nsym) { + kperr("Not found '%s' in original\n", name); + continue; + } + + if (spat.st_value != sorig.st_value - shdr.sh_addr) { + kpwarn("Fixed %ld bytes address bias of original symbol %s\n", + spat.st_value + shdr.sh_addr - sorig.st_value, name); + spat.st_value = sorig.st_value - shdr.sh_addr; + gelf_update_sym(patch->symtab, i, &spat); + } + } + return 0; +} diff --git a/src/arch/loongarch64/loongarch64_imm.h b/src/arch/loongarch64/loongarch64_imm.h new file mode 100644 index 0000000..b9ba771 --- /dev/null +++ b/src/arch/loongarch64/loongarch64_imm.h @@ -0,0 +1,63 @@ +#ifndef LOONGARCH_IMM_H +#define LOONGARCH_IMM_H + +#include + +/* Masks for clearing immediate-fields */ +#define LOONG_MASK_TYPE_2RI8 (~(0xFFu << 10)) +#define LOONG_MASK_TYPE_2RI12 (~(0xFFFu << 10)) +#define LOONG_MASK_TYPE_2RI14 (~(0x3FFFu << 10)) +#define LOONG_MASK_TYPE_2RI16 (~(0xFFFFu << 10)) +#define LOONG_MASK_1RI21 (~((0xFFFFu << 10) | 0x1Fu)) +#define LOONG_MASK_I26 (~((0xFFFFu << 10) | 0x3FFu)) +#define LOONG_MASK_PCREL_UTYPE (~(0xFFFFFu << 5)) +/* 2RI8-type */ +static inline uint32_t set_2ri8_imm(uint32_t ins, uint32_t imm) +{ + return (ins & LOONG_MASK_TYPE_2RI8) | ((imm & 0xFFu) << 10); +} + +/* 2RI12-type */ +static inline uint32_t set_2ri12_imm(uint32_t ins, uint32_t imm) +{ + return (ins & LOONG_MASK_TYPE_2RI12) | ((imm & 0xFFFu) << 10); +} + +/* 2RI14-type */ +static inline uint32_t set_2ri14_imm(uint32_t ins, uint32_t imm) +{ + return (ins & LOONG_MASK_TYPE_2RI14) | ((imm & 0x3FFFu) << 10); +} + +/* 2RI16-type */ +static inline uint32_t set_2ri16_imm(uint32_t ins, uint32_t imm) +{ + return (ins & LOONG_MASK_TYPE_2RI16) | ((imm & 0xFFFFu) << 10); +} + +/* 1RI21-type */ +static inline uint32_t set_1ri21_imm(uint32_t ins, int32_t offset) +{ + int32_t imm_val = offset >> 2; + uint32_t imm_lo = (uint32_t)(imm_val & 0xFFFFu) << 10; + uint32_t imm_hi = ((uint32_t)imm_val >> 16) & 0x1Fu; + return (ins & LOONG_MASK_1RI21) | imm_lo | imm_hi; +} + +/* I26-type */ +static inline uint32_t set_i26_imm(uint32_t ins, int32_t offset) +{ + int32_t imm_val = offset >> 2; + uint32_t imm_lo = (uint32_t)(imm_val & 0xFFFFu) << 10; + uint32_t imm_hi = ((uint32_t)imm_val >> 16) & 0x3FFu; + return (ins & LOONG_MASK_I26) | imm_lo | imm_hi; +} + +/* PCREL */ +static inline uint32_t set_pcrel_imm(uint32_t ins, uint32_t imm) +{ + + return (ins & LOONG_MASK_PCREL_UTYPE) | (((imm >> 2) & 0xFFFFFu) << 5); +} + +#endif /* LOONGARCH_IMM_H */ diff --git a/src/include/kpatch_elf.h b/src/include/kpatch_elf.h index 9461c76..13d23bf 100644 --- a/src/include/kpatch_elf.h +++ b/src/include/kpatch_elf.h @@ -1,6 +1,5 @@ #ifndef __KPATCH_ELF__ #define __KPATCH_ELF__ - #include #include "kpatch_process.h" @@ -37,6 +36,8 @@ struct kpatch_jmp_table_entry { #ifdef __riscv /* at least 3 instructions for arbitrary +-2G access */ unsigned long jmp1; +#elif defined (__loongarch64) + unsigned long jmp1; #endif unsigned long addr; }; diff --git a/src/include/kpatch_elf_objinfo.h b/src/include/kpatch_elf_objinfo.h index e784881..5e0807b 100644 --- a/src/include/kpatch_elf_objinfo.h +++ b/src/include/kpatch_elf_objinfo.h @@ -71,6 +71,13 @@ kpatch_is_tls_rela(Elf64_Rela *rela) return (ELF64_R_TYPE(rela->r_info) == R_RISCV_TLS_TPREL32 || ELF64_R_TYPE(rela->r_info) == R_RISCV_TLS_TPREL64); } +#elif __loongarch64 +static inline int +kpatch_is_tls_rela(Elf64_Rela *rela) +{ + return (ELF64_R_TYPE(rela->r_info) == R_LARCH_TLS_TPREL32 || + ELF64_R_TYPE(rela->r_info) == R_LARCH_TLS_TPREL64); +} #else static inline int kpatch_is_tls_rela(Elf64_Rela *rela) diff --git a/tests/Makefile b/tests/Makefile index 113d1f6..8e555ed 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,7 +10,9 @@ endif ifeq ($(ARCH), riscv64) SUBDIRS := $(filter-out $(AARCH64_NO_SUPPORT_TESTS), $(SUBDIRS)) endif - +ifeq ($(ARCH), loongarch64) + SUBDIRS := $(filter-out $(AARCH64_NO_SUPPORT_TESTS), $(SUBDIRS)) +endif GCC_GE8_GENSRC_TESTS := $(patsubst gcc_ge8_gensrc/%/sub_desc,%,$(wildcard gcc_ge8_gensrc/*/sub_desc)) KPATCH_PATH:=$(CURDIR)/../src diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 12e7f94..87ea5b7 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -616,7 +616,7 @@ main() { exit 1 esac - if test "$ARCH" = "aarch64" -o "$ARCH" = "riscv64"; then + if test "$ARCH" = "aarch64" -o "$ARCH" = "riscv64" -o "$ARCH" = "loongarch64"; then if test "$FLAVOR" = "test_patch_startup" || \ test "$FLAVOR" = "test_patch_startup_ld_linux"; then exit 0 -- Gitee