| // 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; |
| } |