Path: blob/master/arch/mips/tools/loongson3-llsc-check.c
26424 views
// SPDX-License-Identifier: GPL-2.0-only1#include <byteswap.h>2#include <elf.h>3#include <endian.h>4#include <errno.h>5#include <fcntl.h>6#include <inttypes.h>7#include <stdbool.h>8#include <stdio.h>9#include <stdlib.h>10#include <string.h>11#include <sys/mman.h>12#include <sys/types.h>13#include <sys/stat.h>14#include <unistd.h>1516#ifdef be32toh17/* If libc provides le{16,32,64}toh() then we'll use them */18#elif BYTE_ORDER == LITTLE_ENDIAN19# define le16toh(x) (x)20# define le32toh(x) (x)21# define le64toh(x) (x)22#elif BYTE_ORDER == BIG_ENDIAN23# define le16toh(x) bswap_16(x)24# define le32toh(x) bswap_32(x)25# define le64toh(x) bswap_64(x)26#endif2728/* MIPS opcodes, in bits 31:26 of an instruction */29#define OP_SPECIAL 0x0030#define OP_REGIMM 0x0131#define OP_BEQ 0x0432#define OP_BNE 0x0533#define OP_BLEZ 0x0634#define OP_BGTZ 0x0735#define OP_BEQL 0x1436#define OP_BNEL 0x1537#define OP_BLEZL 0x1638#define OP_BGTZL 0x1739#define OP_LL 0x3040#define OP_LLD 0x3441#define OP_SC 0x3842#define OP_SCD 0x3c4344/* Bits 20:16 of OP_REGIMM instructions */45#define REGIMM_BLTZ 0x0046#define REGIMM_BGEZ 0x0147#define REGIMM_BLTZL 0x0248#define REGIMM_BGEZL 0x0349#define REGIMM_BLTZAL 0x1050#define REGIMM_BGEZAL 0x1151#define REGIMM_BLTZALL 0x1252#define REGIMM_BGEZALL 0x135354/* Bits 5:0 of OP_SPECIAL instructions */55#define SPECIAL_SYNC 0x0f5657static void usage(FILE *f)58{59fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n");60}6162static int se16(uint16_t x)63{64return (int16_t)x;65}6667static bool is_ll(uint32_t insn)68{69switch (insn >> 26) {70case OP_LL:71case OP_LLD:72return true;7374default:75return false;76}77}7879static bool is_sc(uint32_t insn)80{81switch (insn >> 26) {82case OP_SC:83case OP_SCD:84return true;8586default:87return false;88}89}9091static bool is_sync(uint32_t insn)92{93/* Bits 31:11 should all be zeroes */94if (insn >> 11)95return false;9697/* Bits 5:0 specify the SYNC special encoding */98if ((insn & 0x3f) != SPECIAL_SYNC)99return false;100101return true;102}103104static bool is_branch(uint32_t insn, int *off)105{106switch (insn >> 26) {107case OP_BEQ:108case OP_BEQL:109case OP_BNE:110case OP_BNEL:111case OP_BGTZ:112case OP_BGTZL:113case OP_BLEZ:114case OP_BLEZL:115*off = se16(insn) + 1;116return true;117118case OP_REGIMM:119switch ((insn >> 16) & 0x1f) {120case REGIMM_BGEZ:121case REGIMM_BGEZL:122case REGIMM_BGEZAL:123case REGIMM_BGEZALL:124case REGIMM_BLTZ:125case REGIMM_BLTZL:126case REGIMM_BLTZAL:127case REGIMM_BLTZALL:128*off = se16(insn) + 1;129return true;130131default:132return false;133}134135default:136return false;137}138}139140static int check_ll(uint64_t pc, uint32_t *code, size_t sz)141{142ssize_t i, max, sc_pos;143int off;144145/*146* Every LL must be preceded by a sync instruction in order to ensure147* that instruction reordering doesn't allow a prior memory access to148* execute after the LL & cause erroneous results.149*/150if (!is_sync(le32toh(code[-1]))) {151fprintf(stderr, "%" PRIx64 ": LL not preceded by sync\n", pc);152return -EINVAL;153}154155/* Find the matching SC instruction */156max = sz / 4;157for (sc_pos = 0; sc_pos < max; sc_pos++) {158if (is_sc(le32toh(code[sc_pos])))159break;160}161if (sc_pos >= max) {162fprintf(stderr, "%" PRIx64 ": LL has no matching SC\n", pc);163return -EINVAL;164}165166/*167* Check branches within the LL/SC loop target sync instructions,168* ensuring that speculative execution can't generate memory accesses169* due to instructions outside of the loop.170*/171for (i = 0; i < sc_pos; i++) {172if (!is_branch(le32toh(code[i]), &off))173continue;174175/*176* If the branch target is within the LL/SC loop then we don't177* need to worry about it.178*/179if ((off >= -i) && (off <= sc_pos))180continue;181182/* If the branch targets a sync instruction we're all good... */183if (is_sync(le32toh(code[i + off])))184continue;185186/* ...but if not, we have a problem */187fprintf(stderr, "%" PRIx64 ": Branch target not a sync\n",188pc + (i * 4));189return -EINVAL;190}191192return 0;193}194195static int check_code(uint64_t pc, uint32_t *code, size_t sz)196{197int err = 0;198199if (sz % 4) {200fprintf(stderr, "%" PRIx64 ": Section size not a multiple of 4\n",201pc);202err = -EINVAL;203sz -= (sz % 4);204}205206if (is_ll(le32toh(code[0]))) {207fprintf(stderr, "%" PRIx64 ": First instruction in section is an LL\n",208pc);209err = -EINVAL;210}211212#define advance() ( \213code++, \214pc += 4, \215sz -= 4 \216)217218/*219* Skip the first instruction, allowing check_ll to look backwards220* unconditionally.221*/222advance();223224/* Now scan through the code looking for LL instructions */225for (; sz; advance()) {226if (is_ll(le32toh(code[0])))227err |= check_ll(pc, code, sz);228}229230return err;231}232233int main(int argc, char *argv[])234{235int vmlinux_fd, status, err, i;236const char *vmlinux_path;237struct stat st;238Elf64_Ehdr *eh;239Elf64_Shdr *sh;240void *vmlinux;241242status = EXIT_FAILURE;243244if (argc < 2) {245usage(stderr);246goto out_ret;247}248249vmlinux_path = argv[1];250vmlinux_fd = open(vmlinux_path, O_RDONLY);251if (vmlinux_fd == -1) {252perror("Unable to open vmlinux");253goto out_ret;254}255256err = fstat(vmlinux_fd, &st);257if (err) {258perror("Unable to stat vmlinux");259goto out_close;260}261262vmlinux = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, vmlinux_fd, 0);263if (vmlinux == MAP_FAILED) {264perror("Unable to mmap vmlinux");265goto out_close;266}267268eh = vmlinux;269if (memcmp(eh->e_ident, ELFMAG, SELFMAG)) {270fprintf(stderr, "vmlinux is not an ELF?\n");271goto out_munmap;272}273274if (eh->e_ident[EI_CLASS] != ELFCLASS64) {275fprintf(stderr, "vmlinux is not 64b?\n");276goto out_munmap;277}278279if (eh->e_ident[EI_DATA] != ELFDATA2LSB) {280fprintf(stderr, "vmlinux is not little endian?\n");281goto out_munmap;282}283284for (i = 0; i < le16toh(eh->e_shnum); i++) {285sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize));286287if (sh->sh_type != SHT_PROGBITS)288continue;289if (!(sh->sh_flags & SHF_EXECINSTR))290continue;291292err = check_code(le64toh(sh->sh_addr),293vmlinux + le64toh(sh->sh_offset),294le64toh(sh->sh_size));295if (err)296goto out_munmap;297}298299status = EXIT_SUCCESS;300out_munmap:301munmap(vmlinux, st.st_size);302out_close:303close(vmlinux_fd);304out_ret:305fprintf(stdout, "loongson3-llsc-check returns %s\n",306status ? "failure" : "success");307return status;308}309310311