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