Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 #include <byteswap.h>
0003 #include <elf.h>
0004 #include <endian.h>
0005 #include <errno.h>
0006 #include <fcntl.h>
0007 #include <inttypes.h>
0008 #include <stdbool.h>
0009 #include <stdio.h>
0010 #include <stdlib.h>
0011 #include <string.h>
0012 #include <sys/mman.h>
0013 #include <sys/types.h>
0014 #include <sys/stat.h>
0015 #include <unistd.h>
0016 
0017 #ifdef be32toh
0018 /* If libc provides le{16,32,64}toh() then we'll use them */
0019 #elif BYTE_ORDER == LITTLE_ENDIAN
0020 # define le16toh(x) (x)
0021 # define le32toh(x) (x)
0022 # define le64toh(x) (x)
0023 #elif BYTE_ORDER == BIG_ENDIAN
0024 # define le16toh(x) bswap_16(x)
0025 # define le32toh(x) bswap_32(x)
0026 # define le64toh(x) bswap_64(x)
0027 #endif
0028 
0029 /* MIPS opcodes, in bits 31:26 of an instruction */
0030 #define OP_SPECIAL  0x00
0031 #define OP_REGIMM   0x01
0032 #define OP_BEQ      0x04
0033 #define OP_BNE      0x05
0034 #define OP_BLEZ     0x06
0035 #define OP_BGTZ     0x07
0036 #define OP_BEQL     0x14
0037 #define OP_BNEL     0x15
0038 #define OP_BLEZL    0x16
0039 #define OP_BGTZL    0x17
0040 #define OP_LL       0x30
0041 #define OP_LLD      0x34
0042 #define OP_SC       0x38
0043 #define OP_SCD      0x3c
0044 
0045 /* Bits 20:16 of OP_REGIMM instructions */
0046 #define REGIMM_BLTZ 0x00
0047 #define REGIMM_BGEZ 0x01
0048 #define REGIMM_BLTZL    0x02
0049 #define REGIMM_BGEZL    0x03
0050 #define REGIMM_BLTZAL   0x10
0051 #define REGIMM_BGEZAL   0x11
0052 #define REGIMM_BLTZALL  0x12
0053 #define REGIMM_BGEZALL  0x13
0054 
0055 /* Bits 5:0 of OP_SPECIAL instructions */
0056 #define SPECIAL_SYNC    0x0f
0057 
0058 static void usage(FILE *f)
0059 {
0060     fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n");
0061 }
0062 
0063 static int se16(uint16_t x)
0064 {
0065     return (int16_t)x;
0066 }
0067 
0068 static bool is_ll(uint32_t insn)
0069 {
0070     switch (insn >> 26) {
0071     case OP_LL:
0072     case OP_LLD:
0073         return true;
0074 
0075     default:
0076         return false;
0077     }
0078 }
0079 
0080 static bool is_sc(uint32_t insn)
0081 {
0082     switch (insn >> 26) {
0083     case OP_SC:
0084     case OP_SCD:
0085         return true;
0086 
0087     default:
0088         return false;
0089     }
0090 }
0091 
0092 static bool is_sync(uint32_t insn)
0093 {
0094     /* Bits 31:11 should all be zeroes */
0095     if (insn >> 11)
0096         return false;
0097 
0098     /* Bits 5:0 specify the SYNC special encoding */
0099     if ((insn & 0x3f) != SPECIAL_SYNC)
0100         return false;
0101 
0102     return true;
0103 }
0104 
0105 static bool is_branch(uint32_t insn, int *off)
0106 {
0107     switch (insn >> 26) {
0108     case OP_BEQ:
0109     case OP_BEQL:
0110     case OP_BNE:
0111     case OP_BNEL:
0112     case OP_BGTZ:
0113     case OP_BGTZL:
0114     case OP_BLEZ:
0115     case OP_BLEZL:
0116         *off = se16(insn) + 1;
0117         return true;
0118 
0119     case OP_REGIMM:
0120         switch ((insn >> 16) & 0x1f) {
0121         case REGIMM_BGEZ:
0122         case REGIMM_BGEZL:
0123         case REGIMM_BGEZAL:
0124         case REGIMM_BGEZALL:
0125         case REGIMM_BLTZ:
0126         case REGIMM_BLTZL:
0127         case REGIMM_BLTZAL:
0128         case REGIMM_BLTZALL:
0129             *off = se16(insn) + 1;
0130             return true;
0131 
0132         default:
0133             return false;
0134         }
0135 
0136     default:
0137         return false;
0138     }
0139 }
0140 
0141 static int check_ll(uint64_t pc, uint32_t *code, size_t sz)
0142 {
0143     ssize_t i, max, sc_pos;
0144     int off;
0145 
0146     /*
0147      * Every LL must be preceded by a sync instruction in order to ensure
0148      * that instruction reordering doesn't allow a prior memory access to
0149      * execute after the LL & cause erroneous results.
0150      */
0151     if (!is_sync(le32toh(code[-1]))) {
0152         fprintf(stderr, "%" PRIx64 ": LL not preceded by sync\n", pc);
0153         return -EINVAL;
0154     }
0155 
0156     /* Find the matching SC instruction */
0157     max = sz / 4;
0158     for (sc_pos = 0; sc_pos < max; sc_pos++) {
0159         if (is_sc(le32toh(code[sc_pos])))
0160             break;
0161     }
0162     if (sc_pos >= max) {
0163         fprintf(stderr, "%" PRIx64 ": LL has no matching SC\n", pc);
0164         return -EINVAL;
0165     }
0166 
0167     /*
0168      * Check branches within the LL/SC loop target sync instructions,
0169      * ensuring that speculative execution can't generate memory accesses
0170      * due to instructions outside of the loop.
0171      */
0172     for (i = 0; i < sc_pos; i++) {
0173         if (!is_branch(le32toh(code[i]), &off))
0174             continue;
0175 
0176         /*
0177          * If the branch target is within the LL/SC loop then we don't
0178          * need to worry about it.
0179          */
0180         if ((off >= -i) && (off <= sc_pos))
0181             continue;
0182 
0183         /* If the branch targets a sync instruction we're all good... */
0184         if (is_sync(le32toh(code[i + off])))
0185             continue;
0186 
0187         /* ...but if not, we have a problem */
0188         fprintf(stderr, "%" PRIx64 ": Branch target not a sync\n",
0189             pc + (i * 4));
0190         return -EINVAL;
0191     }
0192 
0193     return 0;
0194 }
0195 
0196 static int check_code(uint64_t pc, uint32_t *code, size_t sz)
0197 {
0198     int err = 0;
0199 
0200     if (sz % 4) {
0201         fprintf(stderr, "%" PRIx64 ": Section size not a multiple of 4\n",
0202             pc);
0203         err = -EINVAL;
0204         sz -= (sz % 4);
0205     }
0206 
0207     if (is_ll(le32toh(code[0]))) {
0208         fprintf(stderr, "%" PRIx64 ": First instruction in section is an LL\n",
0209             pc);
0210         err = -EINVAL;
0211     }
0212 
0213 #define advance() ( \
0214     code++,     \
0215     pc += 4,    \
0216     sz -= 4     \
0217 )
0218 
0219     /*
0220      * Skip the first instruction, allowing check_ll to look backwards
0221      * unconditionally.
0222      */
0223     advance();
0224 
0225     /* Now scan through the code looking for LL instructions */
0226     for (; sz; advance()) {
0227         if (is_ll(le32toh(code[0])))
0228             err |= check_ll(pc, code, sz);
0229     }
0230 
0231     return err;
0232 }
0233 
0234 int main(int argc, char *argv[])
0235 {
0236     int vmlinux_fd, status, err, i;
0237     const char *vmlinux_path;
0238     struct stat st;
0239     Elf64_Ehdr *eh;
0240     Elf64_Shdr *sh;
0241     void *vmlinux;
0242 
0243     status = EXIT_FAILURE;
0244 
0245     if (argc < 2) {
0246         usage(stderr);
0247         goto out_ret;
0248     }
0249 
0250     vmlinux_path = argv[1];
0251     vmlinux_fd = open(vmlinux_path, O_RDONLY);
0252     if (vmlinux_fd == -1) {
0253         perror("Unable to open vmlinux");
0254         goto out_ret;
0255     }
0256 
0257     err = fstat(vmlinux_fd, &st);
0258     if (err) {
0259         perror("Unable to stat vmlinux");
0260         goto out_close;
0261     }
0262 
0263     vmlinux = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, vmlinux_fd, 0);
0264     if (vmlinux == MAP_FAILED) {
0265         perror("Unable to mmap vmlinux");
0266         goto out_close;
0267     }
0268 
0269     eh = vmlinux;
0270     if (memcmp(eh->e_ident, ELFMAG, SELFMAG)) {
0271         fprintf(stderr, "vmlinux is not an ELF?\n");
0272         goto out_munmap;
0273     }
0274 
0275     if (eh->e_ident[EI_CLASS] != ELFCLASS64) {
0276         fprintf(stderr, "vmlinux is not 64b?\n");
0277         goto out_munmap;
0278     }
0279 
0280     if (eh->e_ident[EI_DATA] != ELFDATA2LSB) {
0281         fprintf(stderr, "vmlinux is not little endian?\n");
0282         goto out_munmap;
0283     }
0284 
0285     for (i = 0; i < le16toh(eh->e_shnum); i++) {
0286         sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize));
0287 
0288         if (sh->sh_type != SHT_PROGBITS)
0289             continue;
0290         if (!(sh->sh_flags & SHF_EXECINSTR))
0291             continue;
0292 
0293         err = check_code(le64toh(sh->sh_addr),
0294                  vmlinux + le64toh(sh->sh_offset),
0295                  le64toh(sh->sh_size));
0296         if (err)
0297             goto out_munmap;
0298     }
0299 
0300     status = EXIT_SUCCESS;
0301 out_munmap:
0302     munmap(vmlinux, st.st_size);
0303 out_close:
0304     close(vmlinux_fd);
0305 out_ret:
0306     fprintf(stdout, "loongson3-llsc-check returns %s\n",
0307         status ? "failure" : "success");
0308     return status;
0309 }