// SPDX-License-Identifier: GPL-2.0-only #include <byteswap.h> #include <elf.h> #include <endian.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #ifdef be32toh /* If libc provides le{16,32,64}toh() then we'll use them */ #elif BYTE_ORDER == LITTLE_ENDIAN # define le16toh(x) (x) # define le32toh(x) (x) # define le64toh(x) (x) #elif BYTE_ORDER == BIG_ENDIAN # define le16toh(x) bswap_16(x) # define le32toh(x) bswap_32(x) # define le64toh(x) bswap_64(x) #endif /* MIPS opcodes, in bits 31:26 of an instruction */ #define OP_SPECIAL 0x00 #define OP_REGIMM 0x01 #define OP_BEQ 0x04 #define OP_BNE 0x05 #define OP_BLEZ 0x06 #define OP_BGTZ 0x07 #define OP_BEQL 0x14 #define OP_BNEL 0x15 #define OP_BLEZL 0x16 #define OP_BGTZL 0x17 #define OP_LL 0x30 #define OP_LLD 0x34 #define OP_SC 0x38 #define OP_SCD 0x3c /* Bits 20:16 of OP_REGIMM instructions */ #define REGIMM_BLTZ 0x00 #define REGIMM_BGEZ 0x01 #define REGIMM_BLTZL 0x02 #define REGIMM_BGEZL 0x03 #define REGIMM_BLTZAL 0x10 #define REGIMM_BGEZAL 0x11 #define REGIMM_BLTZALL 0x12 #define REGIMM_BGEZALL 0x13 /* Bits 5:0 of OP_SPECIAL instructions */ #define SPECIAL_SYNC 0x0f static void usage(FILE *f) { fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n"); } static int se16(uint16_t x) { return (int16_t)x; } static bool is_ll(uint32_t insn) { switch (insn >> 26) { case OP_LL: case OP_LLD: return true; default: return false; } } static bool is_sc(uint32_t insn) { switch (insn >> 26) { case OP_SC: case OP_SCD: return true; default: return false; } } static bool is_sync(uint32_t insn) { /* Bits 31:11 should all be zeroes */ if (insn >> 11) return false; /* Bits 5:0 specify the SYNC special encoding */ if ((insn & 0x3f) != SPECIAL_SYNC) return false; return true; } static bool is_branch(uint32_t insn, int *off) { switch (insn >> 26) { case OP_BEQ: case OP_BEQL: case OP_BNE: case OP_BNEL: case OP_BGTZ: case OP_BGTZL: case OP_BLEZ: case OP_BLEZL: *off = se16(insn) + 1; return true; case OP_REGIMM: switch ((insn >> 16) & 0x1f) { case REGIMM_BGEZ: case REGIMM_BGEZL: case REGIMM_BGEZAL: case REGIMM_BGEZALL: case REGIMM_BLTZ: case REGIMM_BLTZL: case REGIMM_BLTZAL: case REGIMM_BLTZALL: *off = se16(insn) + 1; return true; default: return false; } default: return false; } } static int check_ll(uint64_t pc, uint32_t *code, size_t sz) { ssize_t i, max, sc_pos; int off; /* * Every LL must be preceded by a sync instruction in order to ensure * that instruction reordering doesn't allow a prior memory access to * execute after the LL & cause erroneous results. */ if (!is_sync(le32toh(code[-1]))) { fprintf(stderr, "%" PRIx64 ": LL not preceded by sync\n", pc); return -EINVAL; } /* Find the matching SC instruction */ max = sz / 4; for (sc_pos = 0; sc_pos < max; sc_pos++) { if (is_sc(le32toh(code[sc_pos]))) break; } if (sc_pos >= max) { fprintf(stderr, "%" PRIx64 ": LL has no matching SC\n", pc); return -EINVAL; } /* * Check branches within the LL/SC loop target sync instructions, * ensuring that speculative execution can't generate memory accesses * due to instructions outside of the loop. */ for (i = 0; i < sc_pos; i++) { if (!is_branch(le32toh(code[i]), &off)) continue; /* * If the branch target is within the LL/SC loop then we don't * need to worry about it. */ if ((off >= -i) && (off <= sc_pos)) continue; /* If the branch targets a sync instruction we're all good... */ if (is_sync(le32toh(code[i + off]))) continue; /* ...but if not, we have a problem */ fprintf(stderr, "%" PRIx64 ": Branch target not a sync\n", pc + (i * 4)); return -EINVAL; } return 0; } static int check_code(uint64_t pc, uint32_t *code, size_t sz) { int err = 0; if (sz % 4) { fprintf(stderr, "%" PRIx64 ": Section size not a multiple of 4\n", pc); err = -EINVAL; sz -= (sz % 4); } if (is_ll(le32toh(code[0]))) { fprintf(stderr, "%" PRIx64 ": First instruction in section is an LL\n", pc); err = -EINVAL; } #define advance() ( \ code++, \ pc += 4, \ sz -= 4 \ ) /* * Skip the first instruction, allowing check_ll to look backwards * unconditionally. */ advance(); /* Now scan through the code looking for LL instructions */ for (; sz; advance()) { if (is_ll(le32toh(code[0]))) err |= check_ll(pc, code, sz); } return err; } int main(int argc, char *argv[]) { int vmlinux_fd, status, err, i; const char *vmlinux_path; struct stat st; Elf64_Ehdr *eh; Elf64_Shdr *sh; void *vmlinux; status = EXIT_FAILURE; if (argc < 2) { usage(stderr); goto out_ret; } vmlinux_path = argv[1]; vmlinux_fd = open(vmlinux_path, O_RDONLY); if (vmlinux_fd == -1) { perror("Unable to open vmlinux"); goto out_ret; } err = fstat(vmlinux_fd, &st); if (err) { perror("Unable to stat vmlinux"); goto out_close; } vmlinux = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, vmlinux_fd, 0); if (vmlinux == MAP_FAILED) { perror("Unable to mmap vmlinux"); goto out_close; } eh = vmlinux; if (memcmp(eh->e_ident, ELFMAG, SELFMAG)) { fprintf(stderr, "vmlinux is not an ELF?\n"); goto out_munmap; } if (eh->e_ident[EI_CLASS] != ELFCLASS64) { fprintf(stderr, "vmlinux is not 64b?\n"); goto out_munmap; } if (eh->e_ident[EI_DATA] != ELFDATA2LSB) { fprintf(stderr, "vmlinux is not little endian?\n"); goto out_munmap; } for (i = 0; i < le16toh(eh->e_shnum); i++) { sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize)); if (sh->sh_type != SHT_PROGBITS) continue; if (!(sh->sh_flags & SHF_EXECINSTR)) continue; err = check_code(le64toh(sh->sh_addr), vmlinux + le64toh(sh->sh_offset), le64toh(sh->sh_size)); if (err) goto out_munmap; } status = EXIT_SUCCESS; out_munmap: munmap(vmlinux, st.st_size); out_close: close(vmlinux_fd); out_ret: fprintf(stdout, "loongson3-llsc-check returns %s\n", status ? "failure" : "success"); return status; }