From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: bpf@vger.kernel.org, daniel@iogearbox.net, andrii@kernel.org,
eddyz87@gmail.com, memxor@gmail.com
Subject: Re: [PATCH v2 bpf-next 03/13] resolve_btfids: Describe machine instructions with LLVM's MC layer
Date: Fri, 13 Nov 2026 11:45:11 +0000 [thread overview]
Message-ID: <avb4R6DFgjdDI17L@mail.gmail.com> (raw)
In-Reply-To: <20261113050901.1399091-4-alexei.starovoitov@gmail.com>
On 26/11/13 05:08AM, Alexei Starovoitov wrote:
> From: Alexei Starovoitov <ast@kernel.org>
>
> mov $0xffffffea,%eax
> ... does -22 reach the ret?
> ret
>
> The answer needs three facts about each instruction in between: its
> length, where it branches, each register that it writes. Implicit writes
> count too: mul, cmpxchg, ldp, a post-index load. After one missed write
> the tool says that a function returns an error that it never returns.
>
> A decoder written by hand is 300 - 800 lines per arch, for ever. LLVM's MC
> (machine code) layer knows the three facts for each arch:
>
> bytes
> | MCDisassembler length, MCInst
> | MCInstrAnalysis branch, call, target
> | MCInstrDesc explicit and implicit defs
> | MCRegAliasIterator eax is a part of rax
> | table of the arch what an opcode computes
> v
> struct text_insn kind, dst, src, imm, defs: no arch in it
>
> LLVM's C API returns only text. So text_mc.cpp is one C++ file with a C
> interface, like tools/perf/util/llvm-c-helpers.cpp.
>
> MC does not say what an instruction computes. The table of an arch does,
> by the names of opcodes and registers. The headers with the enums of a
> target are not installed.
>
> x86-64 arm64
> patterns 13 11 opcode -> mov of an immediate,
> mov, sign extension, select
> return 1 1 RET64, RET
> test instructions 56 55 bytes -> expected text_insn
>
> Where the table says nothing, the tool drops an error. It adds none:
> - An opcode without a pattern makes an unknown value.
> - An opcode with hasUnmodeledSideEffects() writes all registers: hvc and
> vmcall have no defs. So no return value passes ud2 or brk: BUG() has no
> next instruction. The table says of 16 frequent opcodes that their defs
> are complete: lock, mrs, ldxr ..
> - isReturn() holds for iret and eret too. Only the opcode that the table
> names is a return to the caller.
>
> LLVM's API, opcode names and defs change between releases. text_arch_init()
> looks up all names and decodes all test instructions at each start of
> the tool. The build stops if one of them differs.
>
> llvm-mc.sh is the one place that runs llvm-config. A later patch lets
> Kconfig take its exit code. It compiles nothing: 3 ms. The Makefile takes
Acked-by: Anton Protopopov <a.s.protopopov@gmail.com>
> flags and libraries from it, only if kbuild passes --fmodret_scan.
>
> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
> ---
> tools/bpf/resolve_btfids/Build | 5 +
> tools/bpf/resolve_btfids/Makefile | 23 +-
> tools/bpf/resolve_btfids/llvm-mc.sh | 59 ++++
> tools/bpf/resolve_btfids/text_insn.h | 71 ++++
> tools/bpf/resolve_btfids/text_mc.cpp | 406 +++++++++++++++++++++++
> tools/bpf/resolve_btfids/text_mc.h | 105 ++++++
> tools/bpf/resolve_btfids/text_mc_arm64.c | 118 +++++++
> tools/bpf/resolve_btfids/text_mc_x86.c | 118 +++++++
> 8 files changed, 904 insertions(+), 1 deletion(-)
> create mode 100755 tools/bpf/resolve_btfids/llvm-mc.sh
> create mode 100644 tools/bpf/resolve_btfids/text_insn.h
> create mode 100644 tools/bpf/resolve_btfids/text_mc.cpp
> create mode 100644 tools/bpf/resolve_btfids/text_mc.h
> create mode 100644 tools/bpf/resolve_btfids/text_mc_arm64.c
> create mode 100644 tools/bpf/resolve_btfids/text_mc_x86.c
>
> diff --git a/tools/bpf/resolve_btfids/Build b/tools/bpf/resolve_btfids/Build
> index 65af9dee7659..b9e5bf21439f 100644
> --- a/tools/bpf/resolve_btfids/Build
> +++ b/tools/bpf/resolve_btfids/Build
> @@ -3,12 +3,17 @@ hostprogs := resolve_btfids
> resolve_btfids-y += main.o
> resolve_btfids-y += btf_colors.o
> resolve_btfids-y += image.o
> +resolve_btfids-$(LLVM_MC) += text_mc.o
> +resolve_btfids-$(LLVM_MC) += text_mc_$(SRCARCH).o
> resolve_btfids-y += rbtree.o
> resolve_btfids-y += zalloc.o
> resolve_btfids-y += string.o
> resolve_btfids-y += ctype.o
> resolve_btfids-y += str_error_r.o
>
> +# LLVM's C++ API may have moved on, see Makefile
> +$(OUTPUT)text_mc.o: cmd_cxx_o_c += $(or_llvm_mc_help)
> +
> $(OUTPUT)%.o: ../../lib/%.c FORCE
> $(call rule_mkdir)
> $(call if_changed_dep,host_cc_o_c)
> diff --git a/tools/bpf/resolve_btfids/Makefile b/tools/bpf/resolve_btfids/Makefile
> index 6f5206ca00d4..38b7caa98776 100644
> --- a/tools/bpf/resolve_btfids/Makefile
> +++ b/tools/bpf/resolve_btfids/Makefile
> @@ -79,7 +79,28 @@ HOSTCFLAGS_resolve_btfids += -g \
>
> LIBS = $(LIBELF_LIBS) $(ZLIB_LIBS)
>
> +# text_mc.cpp reads machine code with LLVM's MC layer. Only --fmodret_scan needs
> +# it: don't touch LLVM unless kbuild is going to run the tool with that flag.
> +ifneq ($(filter --fmodret_scan,$(RESOLVE_BTFIDS_FLAGS)),)
> +LLVM_MC := y
> +HOSTCXX ?= g++
> +LLVM_MC_SH := LLVM_CONFIG="$(LLVM_CONFIG)" HOSTCXX="$(HOSTCXX)" \
> + $(SHELL) $(srctree)/tools/bpf/resolve_btfids/llvm-mc.sh $(SRCARCH)
> +# Kconfig knows from llvm-mc.sh that LLVM is installed. Whether text_mc.cpp can
> +# be built with it shows here.
> +LLVM_MC_HELP := CONFIG_BPF_FMODRET_SCAN=y needs a C++17 compiler (HOSTCXX=$(HOSTCXX)) and the \
> + headers and libraries of LLVM 17 or later (LLVM_CONFIG=$(LLVM_CONFIG), packages \
> + llvm-dev, llvm-devel). See above what is wrong, or switch the option off
> +or_llvm_mc_help := || { echo '$(LLVM_MC_HELP)' >&2; false; }
> +HOSTCFLAGS_resolve_btfids += -DHAVE_LLVM_MC
> +CXX := $(HOSTCXX)
> +CXXFLAGS := $(filter-out -Werror,$(HOSTCFLAGS_resolve_btfids)) -O2 \
> + $(or $(shell $(LLVM_MC_SH) cxxflags),$(error $(LLVM_MC_HELP)))
> +LIBS += $(or $(shell $(LLVM_MC_SH) libs),$(error $(LLVM_MC_HELP)))
> +endif
> +
> export srctree OUTPUT HOSTCFLAGS_resolve_btfids Q HOSTCC HOSTLD HOSTAR
> +export CXX CXXFLAGS SRCARCH LLVM_MC or_llvm_mc_help
> include $(srctree)/tools/build/Makefile.include
>
> $(BINARY_IN): fixdep FORCE prepare | $(OUTPUT)
> @@ -87,7 +108,7 @@ $(BINARY_IN): fixdep FORCE prepare | $(OUTPUT)
>
> $(BINARY): $(BPFOBJ) $(SUBCMDOBJ) $(BINARY_IN)
> $(call msg,LINK,$@)
> - $(Q)$(HOSTCC) $(BINARY_IN) $(KBUILD_HOSTLDFLAGS) $(EXTRA_LDFLAGS) -o $@ $(BPFOBJ) $(SUBCMDOBJ) $(LIBS)
> + $(Q)$(HOSTCC) $(BINARY_IN) $(KBUILD_HOSTLDFLAGS) $(EXTRA_LDFLAGS) -o $@ $(BPFOBJ) $(SUBCMDOBJ) $(LIBS) $(or_llvm_mc_help)
>
> clean_objects := $(wildcard $(OUTPUT)/*.o \
> $(OUTPUT)/.*.o.cmd \
> diff --git a/tools/bpf/resolve_btfids/llvm-mc.sh b/tools/bpf/resolve_btfids/llvm-mc.sh
> new file mode 100755
> index 000000000000..bc62c4e43406
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/llvm-mc.sh
> @@ -0,0 +1,59 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# usage: llvm-mc.sh SRCARCH [cxxflags | libs]
> +#
> +# text_mc.cpp reads the machine code of the kernel arch SRCARCH with LLVM's MC
> +# layer. Fail, and say why, if this host lacks what it takes to build it.
> +# CONFIG_BPF_FMODRET_SCAN depends on that. Each Kconfig run pays for it, so it is
> +# one run of llvm-config. Whether text_mc.cpp works with this LLVM shows when the
> +# tool is built, and in text_arch_init() when it starts.
> +#
> +# For the Makefile print the flags to compile text_mc.cpp, or the libraries.
> +#
> +# The host's LLVM need not be the one which compiles the kernel, if any does.
> +
> +die()
> +{
> + echo "$0: $*" >&2
> + exit 1
> +}
> +
> +case "$1" in
> +x86) target=X86 ;;
> +arm64) target=AArch64 ;;
> +*) die "no text_mc_$1.c" ;;
> +esac
> +print=$2
> +
> +if [ -z "$LLVM_CONFIG" ]; then
> + case "$LLVM" in
> + */) LLVM_CONFIG=${LLVM}llvm-config ;;
> + -*) LLVM_CONFIG=llvm-config$LLVM ;;
> + *) LLVM_CONFIG=llvm-config ;;
> + esac
> +fi
> +
> +command -v ${HOSTCXX:-g++} > /dev/null || die "no C++ compiler ${HOSTCXX:-g++}"
> +set -- $($LLVM_CONFIG --version --includedir --targets-built 2> /dev/null)
> +[ $# -gt 2 ] || die "cannot run $LLVM_CONFIG"
> +[ "${1%%.*}" -ge 17 ] || die "$LLVM_CONFIG is LLVM $1, not 17 or later"
> +[ -e "$2/llvm/MC/MCInstrDesc.h" ] || die "$2 lacks the headers of LLVM $1"
> +case " $* " in
> +*" $target "*) ;;
> +*) die "$LLVM_CONFIG, LLVM $1, lacks the target $target" ;;
> +esac
> +
> +case "$print" in
> +cxxflags)
> + echo "$($LLVM_CONFIG --cxxflags) -DMC_TARGET=$target"
> + ;;
> +libs)
> + # A static link starts faster, which counts for each module, and does not tie the
> + # tool, which is shipped for the build of external modules, to a shared library
> + set -- --ldflags --libs --system-libs ${target}Desc ${target}Disassembler ${target}Info
> + libs=$($LLVM_CONFIG --link-static "$@" 2> /dev/null) ||
> + libs=$($LLVM_CONFIG --link-shared "$@") || die "no libraries of LLVM"
> + echo $libs -lstdc++ -lm
> + ;;
> +esac
> diff --git a/tools/bpf/resolve_btfids/text_insn.h b/tools/bpf/resolve_btfids/text_insn.h
> new file mode 100644
> index 000000000000..210d749439e7
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/text_insn.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __TEXT_INSN_H
> +#define __TEXT_INSN_H
> +
> +#include <linux/types.h>
> +
> +/*
> + * What one machine instruction does to the integer registers and to the
> + * control flow, in terms which do not depend on the arch. It is what a data
> + * flow analysis of copies needs, see text_flow.h.
> + *
> + * A decoder looks at one instruction and keeps no state. It has to get right
> + * the length, the kind and the registers which are written. An instruction
> + * which it does not know is TEXT_OTHER and writes TEXT_ALL_REGS. It may leave
> + * out a value which dst can get, and never names one which dst cannot get:
> + * "dst = src or a value which is computed" is a TEXT_SELECT of src and src.
> + */
> +enum text_insn_kind {
> + TEXT_OTHER,
> + TEXT_MOV_IMM, /* dst = imm or imm2 */
> + TEXT_MOV, /* dst = src */
> + TEXT_SEXT32, /* dst = (s32)src */
> + TEXT_SELECT, /* dst = src or src2 */
> + TEXT_SELECT_IMM, /* dst = src or imm */
> + TEXT_BRANCH, /* to imm, 0 if it is computed */
> + TEXT_COND_BRANCH, /* to imm or to the next instruction */
> + TEXT_CALL, /* of imm, 0 if it is computed */
> + TEXT_RETURN,
> +};
> +
> +/* The arch numbers its registers */
> +#define TEXT_MAX_REGS 32
> +#define TEXT_ALL_REGS (~0U)
> +#define TEXT_NO_INSN (~0U)
> +
> +struct text_insn {
> + u8 kind;
> + u8 len;
> + u8 dst, src, src2;
> + bool is32; /* It works on the low 32 bits and zero extends the result */
> + /* The registers which it writes, but for dst */
> + u32 defs;
> + u64 imm, imm2;
> + /* For the user of the decoder */
> + u64 addr;
> + u32 target; /* the index of the instruction at imm */
> +};
> +
> +struct text_arch {
> + u8 retval; /* the register with the return value */
> + u32 caller_saved; /* the registers which a call may write */
> + /* A branch to this symbol is a return */
> + const char *return_thunk;
> + /*
> + * Describe the instruction at ADDR, whose bytes are at CODE. At most LEN
> + * bytes can be read. Return a negative value if it is not an instruction.
> + */
> + int (*decode)(const void *code, unsigned int len, u64 addr, struct text_insn *insn);
> +};
> +
> +#ifdef HAVE_LLVM_MC
> +/* Return the arch of the ELF machine MACHINE, or NULL, after a message, if it cannot be decoded */
> +const struct text_arch *text_arch_init(unsigned int machine);
> +#else
> +static inline const struct text_arch *text_arch_init(unsigned int machine)
> +{
> + return NULL;
> +}
> +#endif
> +
> +#endif
> diff --git a/tools/bpf/resolve_btfids/text_mc.cpp b/tools/bpf/resolve_btfids/text_mc.cpp
> new file mode 100644
> index 000000000000..e7166ad4b871
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/text_mc.cpp
> @@ -0,0 +1,406 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * The instructions of any arch in the terms of text_insn.h, on top of LLVM's MC
> + * layer. Nothing in this file depends on the arch: see text_mc.h for the rest.
> + *
> + * length, MCInst MCDisassembler::getInstruction()
> + * branch, call MCInstrAnalysis, which asks MCInstrDesc
> + * target MCInstrAnalysis::evaluateBranch()
> + * registers written MCInstrDesc: defs, implicit defs
> + * eax and rax, w0 and x0 MCRegAliasIterator
> + *
> + * Whatever MC does not describe completely writes TEXT_ALL_REGS.
> + */
> +
> +#include <llvm/Config/llvm-config.h>
> +#include <llvm/MC/MCAsmInfo.h>
> +#include <llvm/MC/MCContext.h>
> +#include <llvm/MC/MCDisassembler/MCDisassembler.h>
> +#include <llvm/MC/MCInst.h>
> +#include <llvm/MC/MCInstrAnalysis.h>
> +#include <llvm/MC/MCInstrDesc.h>
> +#include <llvm/MC/MCInstrInfo.h>
> +#include <llvm/MC/MCRegisterInfo.h>
> +#include <llvm/MC/MCSubtargetInfo.h>
> +#include <llvm/MC/MCTargetOptions.h>
> +#include <llvm/MC/TargetRegistry.h>
> +#include <llvm/Support/Format.h>
> +#include <llvm/Support/TargetSelect.h>
> +#include <llvm/Support/raw_ostream.h>
> +
> +#include <memory>
> +#include <sstream>
> +#include <string>
> +#include <string_view>
> +#include <unordered_map>
> +#include <vector>
> +
> +extern "C" {
> +#include "text_mc.h"
> +}
> +
> +#if LLVM_VERSION_MAJOR < 17
> +#error "LLVM 17, the oldest one which can build the kernel, or later"
> +#endif
> +
> +using namespace llvm;
> +
> +namespace {
> +
> +enum { NO_REG = 0xff, ZERO_REG = 0xfe };
> +
> +/* An LLVM register in the terms of text_insn.h */
> +struct Reg {
> + u32 mask = 0; /* the registers which it overlaps */
> + u8 nr = NO_REG; /* the register if it overlaps one, or ZERO_REG */
> + u8 bits = 0; /* 64: all of it, 32: the low half, a write zero extends, 0: some part */
> +};
> +
> +/* What is known about an opcode before its operands are */
> +struct Opcode {
> + u32 implicit_defs = 0;
> + unsigned int implicit_def = 0, implicit_use = 0; /* the first ones, LLVM registers */
> + bool unmodeled = false, is_return = false;
> + std::vector<const mc_pattern *> patterns;
> +};
> +
> +struct Decoder {
> + std::unique_ptr<MCRegisterInfo> MRI;
> + std::unique_ptr<MCAsmInfo> MAI;
> + std::unique_ptr<MCInstrInfo> MII;
> + std::unique_ptr<MCSubtargetInfo> STI;
> + std::unique_ptr<MCContext> Ctx;
> + std::unique_ptr<MCDisassembler> Dis;
> + std::unique_ptr<MCInstrAnalysis> MIA;
> + std::vector<Reg> regs;
> + std::vector<Opcode> opcodes;
> + MCInst inst;
> +};
> +
> +Decoder d;
> +
> +/* MC_TARGET is LLVM's name of the arch, see llvm-mc.sh */
> +#define INITIALIZE_(target, what) LLVMInitialize##target##what()
> +#define INITIALIZE(target, what) INITIALIZE_(target, what)
> +
> +raw_ostream &error()
> +{
> + return errs() << "text_mc: LLVM " LLVM_VERSION_STRING ": ";
> +}
> +
> +bool create()
> +{
> + MCTargetOptions options;
> + std::string message;
> +#if LLVM_VERSION_MAJOR >= 22
> + Triple triple(mc_arch.triple);
> +#else
> + const char *triple = mc_arch.triple;
> +#endif
> + const Target *target;
> +
> + INITIALIZE(MC_TARGET, TargetInfo);
> + INITIALIZE(MC_TARGET, TargetMC);
> + INITIALIZE(MC_TARGET, Disassembler);
> + target = TargetRegistry::lookupTarget(triple, message);
> + if (!target) {
> + error() << message << "\n";
> + return false;
> + }
> + d.MRI.reset(target->createMCRegInfo(triple));
> + d.MAI.reset(target->createMCAsmInfo(*d.MRI, triple, options));
> + d.MII.reset(target->createMCInstrInfo());
> + d.STI.reset(target->createMCSubtargetInfo(triple, "", mc_arch.features));
> +#if LLVM_VERSION_MAJOR >= 23
> + d.Ctx.reset(new MCContext(triple, *d.MAI, *d.MRI, *d.STI));
> +#else
> + d.Ctx.reset(new MCContext(Triple(mc_arch.triple), d.MAI.get(), d.MRI.get(), d.STI.get()));
> +#endif
> + d.Dis.reset(target->createMCDisassembler(*d.STI, *d.Ctx));
> + d.MIA.reset(target->createMCInstrAnalysis(d.MII.get()));
> + return d.Dis && d.MIA;
> +}
> +
> +/* Call FN(LLVM register, index) for each of the NAMES */
> +template <typename Fn> bool for_each_reg(const char *names, Fn fn)
> +{
> + std::istringstream in(names ?: "");
> + std::string name;
> + bool ok = true;
> +
> + for (unsigned int i = 0, r; in >> name; i++) {
> + for (r = 1; r < d.MRI->getNumRegs() && name != d.MRI->getName(r); r++)
> + ;
> + if (r < d.MRI->getNumRegs()) {
> + fn(r, i);
> + continue;
> + }
> + error() << "no register " << name << "\n";
> + ok = false;
> + }
> + return ok;
> +}
> +
> +bool read_regs()
> +{
> + bool ok;
> +
> + d.regs.resize(d.MRI->getNumRegs());
> + ok = for_each_reg(mc_arch.regs, [&](unsigned int r, unsigned int nr) {
> + for (MCRegAliasIterator alias(r, d.MRI.get(), true); alias.isValid(); ++alias) {
> + Reg ® = d.regs[*alias];
> +
> + /* The iterator may come to a register more than once */
> + reg.mask |= 1U << nr;
> + reg.nr = reg.mask == 1U << nr ? nr : NO_REG;
> + reg.bits = *alias == r ? 64 : 0;
> + }
> + });
> + ok &= for_each_reg(mc_arch.regs32, [&](unsigned int r, unsigned int) { d.regs[r].bits = 32; });
> + ok &= for_each_reg(mc_arch.zero_regs, [&](unsigned int r, unsigned int) { d.regs[r].nr = ZERO_REG; });
> + return ok;
> +}
> +
> +bool read_opcodes()
> +{
> + std::unordered_map<std::string_view, Opcode *> by_name;
> + bool ok = true;
> +
> + d.opcodes.resize(d.MII->getNumOpcodes());
> + by_name.reserve(d.opcodes.size());
> + for (unsigned int op = 0; op < d.opcodes.size(); op++) {
> + const MCInstrDesc &desc = d.MII->get(op);
> + Opcode &o = d.opcodes[op];
> +
> + by_name[d.MII->getName(op)] = &o;
> + for (MCPhysReg r : desc.implicit_defs()) {
> + o.implicit_defs |= d.regs[r].mask;
> + if (!o.implicit_def && d.regs[r].mask)
> + o.implicit_def = r;
> + }
> + for (MCPhysReg r : desc.implicit_uses())
> + if (!o.implicit_use && d.regs[r].mask)
> + o.implicit_use = r;
> + /* defs_of() does not look for the last two */
> + o.unmodeled = desc.hasUnmodeledSideEffects() || desc.hasOptionalDef() ||
> + desc.variadicOpsAreDefs();
> + }
> + for (const mc_pattern *pattern = mc_arch.patterns; pattern->opcode; pattern++) {
> + auto it = by_name.find(pattern->opcode);
> +
> + if (it == by_name.end()) {
> + error() << "no opcode " << pattern->opcode << "\n";
> + ok = false;
> + } else if (pattern->kind == TEXT_RETURN) {
> + it->second->is_return = true;
> + } else if (pattern->kind != TEXT_OTHER) {
> + it->second->patterns.push_back(pattern);
> + } else {
> + it->second->unmodeled = false;
> + it->second->implicit_defs |= pattern->defs;
> + }
> + }
> + return ok;
> +}
> +
> +const Reg ®_of(const MCInst &inst, const Opcode &o, u8 nr, bool def)
> +{
> + static const Reg none;
> +
> + if (nr == MC_IMPLICIT)
> + return d.regs[def ? o.implicit_def : o.implicit_use];
> + if (nr >= inst.getNumOperands() || !inst.getOperand(nr).isReg())
> + return none;
> + return d.regs[inst.getOperand(nr).getReg()];
> +}
> +
> +bool holds(const MCInst &inst, const mc_predicate &p)
> +{
> + const MCOperand *a, *b;
> +
> + if (p.kind == MC_TRUE)
> + return true;
> + if (p.a >= inst.getNumOperands())
> + return false;
> + a = &inst.getOperand(p.a);
> + switch (p.kind) {
> + case MC_ZERO_REG:
> + return a->isReg() && d.regs[a->getReg()].nr == ZERO_REG;
> + case MC_SAME_REG:
> + if ((unsigned int)p.b >= inst.getNumOperands())
> + return false;
> + b = &inst.getOperand(p.b);
> + return a->isReg() && b->isReg() && a->getReg() == b->getReg();
> + case MC_IMM:
> + return a->isImm() && a->getImm() == p.b;
> + }
> + return false;
> +}
> +
> +/* Describe INST as PATTERN says. Return false, and leave TI alone, if it does not match */
> +bool match(const MCInst &inst, const Opcode &o, const mc_pattern &pattern, text_insn *ti)
> +{
> + const Reg &dst = reg_of(inst, o, pattern.dst, true);
> + u8 kind = pattern.kind, src = 0, src2 = 0;
> + bool is32 = dst.bits == 32;
> + u64 imm = pattern.value;
> +
> + if (!holds(inst, pattern.predicates[0]) || !holds(inst, pattern.predicates[1]))
> + return false;
> + if (dst.nr >= TEXT_MAX_REGS || !dst.bits)
> + return false;
> + if (kind != TEXT_MOV && kind != TEXT_SEXT32 && kind != TEXT_SELECT) {
> + if (!pattern.fixed) {
> + if (pattern.imm >= inst.getNumOperands() || !inst.getOperand(pattern.imm).isImm())
> + return false;
> + imm = inst.getOperand(pattern.imm).getImm();
> + }
> + if (pattern.shift >= inst.getNumOperands())
> + return false;
> + if (pattern.shift)
> + imm <<= inst.getOperand(pattern.shift).getImm();
> + if (pattern.invert)
> + imm = ~imm;
> + if (is32)
> + imm = (u32)imm;
> + }
> + if (kind != TEXT_MOV_IMM) {
> + src = src2 = reg_of(inst, o, pattern.src, false).nr;
> + if (kind == TEXT_SELECT)
> + src2 = reg_of(inst, o, pattern.src2, false).nr;
> + /* The zero register is a constant which is not an error: only the other source counts */
> + if (src == ZERO_REG)
> + src = src2;
> + if (src2 == ZERO_REG)
> + src2 = src;
> + if (src >= TEXT_MAX_REGS || src2 >= TEXT_MAX_REGS)
> + return false;
> + }
> + ti->kind = kind;
> + ti->is32 = is32;
> + ti->dst = dst.nr;
> + ti->src = src;
> + ti->src2 = src2;
> + ti->imm = imm;
> + ti->imm2 = pattern.or_zero ? 0 : imm;
> + return true;
> +}
> +
> +void control_flow(const MCInst &inst, const Opcode &o, u64 addr, text_insn *ti)
> +{
> + const MCInstrDesc &desc = d.MII->get(inst.getOpcode());
> + const MCInstrAnalysis &MIA = *d.MIA;
> + u64 target;
> +
> + if (o.is_return)
> + ti->kind = TEXT_RETURN;
> + else if (MIA.isCall(inst))
> + ti->kind = TEXT_CALL;
> + else if (MIA.isUnconditionalBranch(inst) || (MIA.isIndirectBranch(inst) && desc.isBarrier()))
> + ti->kind = TEXT_BRANCH;
> + else if (MIA.isBranch(inst))
> + ti->kind = TEXT_COND_BRANCH;
> + if (ti->kind >= TEXT_BRANCH && MIA.evaluateBranch(inst, addr, ti->len, target))
> + ti->imm = target;
> + if (ti->kind == TEXT_BRANCH || ti->kind == TEXT_RETURN)
> + ti->defs = 0;
> +}
> +
> +/* Return the registers which INST writes */
> +u32 defs_of(const MCInst &inst, const Opcode &o)
> +{
> + const MCInstrDesc &desc = d.MII->get(inst.getOpcode());
> + u32 defs = o.implicit_defs;
> +
> + if (o.unmodeled)
> + return TEXT_ALL_REGS;
> + /* The operands are not those of the description: which ones are the defs? */
> + if (desc.getNumDefs() && inst.getNumOperands() < desc.getNumOperands())
> + return TEXT_ALL_REGS;
> + for (unsigned int i = 0; i < desc.getNumDefs(); i++) {
> + if (!inst.getOperand(i).isReg())
> + return TEXT_ALL_REGS;
> + defs |= d.regs[inst.getOperand(i).getReg()].mask;
> + }
> + return defs;
> +}
> +
> +int decode(const void *code, unsigned int len, u64 addr, text_insn *ti)
> +{
> + MCInst &inst = d.inst;
> + uint64_t size = 0;
> +
> + inst = MCInst();
> + *ti = text_insn{};
> + /* After SoftFail the MCInst is valid: LLVM has it for "ldtr w8, [x8]" */
> + if (d.Dis->getInstruction(inst, size, ArrayRef<uint8_t>((const uint8_t *)code, len), addr,
> + nulls()) == MCDisassembler::Fail)
> + return -1;
> + ti->len = size;
> +
> + const Opcode &o = d.opcodes[inst.getOpcode()];
> +
> + ti->defs = defs_of(inst, o);
> + for (const mc_pattern *pattern : o.patterns) {
> + if (match(inst, o, *pattern, ti)) {
> + ti->defs &= ~(1U << ti->dst);
> + return 0;
> + }
> + }
> + control_flow(inst, o, addr, ti);
> + return 0;
> +}
> +
> +void print(raw_ostream &os, const text_insn &ti)
> +{
> + os << "kind " << (int)ti.kind << " is32 " << ti.is32 << " dst " << (int)ti.dst
> + << " src " << (int)ti.src << " src2 " << (int)ti.src2
> + << " defs " << format_hex(ti.defs, 1) << " imm " << format_hex(ti.imm, 1)
> + << " imm2 " << format_hex(ti.imm2, 1);
> +}
> +
> +bool run_tests()
> +{
> + bool ok = true;
> +
> + for (const mc_test *test = mc_arch.tests; test->code; test++) {
> + const text_insn &want = test->insn;
> + std::istringstream in(test->code);
> + std::vector<uint8_t> code;
> + unsigned int byte;
> + text_insn ti;
> +
> + while (in >> std::hex >> byte)
> + code.push_back(byte);
> + if (!decode(code.data(), code.size(), MC_TEST_ADDR, &ti) && ti.len == code.size() &&
> + ti.kind == want.kind && ti.is32 == want.is32 && ti.dst == want.dst &&
> + ti.src == want.src && ti.src2 == want.src2 &&
> + ti.defs == want.defs && ti.imm == want.imm && ti.imm2 == want.imm2)
> + continue;
> + print(error() << test->code << " is " << d.MII->getName(d.inst.getOpcode()) << ", length "
> + << (int)ti.len << ", ", ti);
> + print(errs() << "\n\texpected: ", want);
> + errs() << "\n";
> + ok = false;
> + }
> + return ok;
> +}
> +
> +} /* namespace */
> +
> +extern "C" const struct text_arch *text_arch_init(unsigned int machine)
> +{
> + static text_arch arch = mc_arch.arch;
> +
> + if (machine != mc_arch.machine) {
> + errs() << "text_mc: built for " << mc_arch.triple << ", not for ELF machine " << machine << "\n";
> + return nullptr;
> + }
> + if (!create() || !read_regs() || !read_opcodes() || !run_tests()) {
> + error() << "the table of the arch does not fit it. Set LLVM_CONFIG to the "
> + "llvm-config of another LLVM, or switch CONFIG_BPF_FMODRET_SCAN off\n";
> + return nullptr;
> + }
> + arch.decode = decode;
> + return &arch;
> +}
> diff --git a/tools/bpf/resolve_btfids/text_mc.h b/tools/bpf/resolve_btfids/text_mc.h
> new file mode 100644
> index 000000000000..9d39095aab20
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/text_mc.h
> @@ -0,0 +1,105 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __TEXT_MC_H
> +#define __TEXT_MC_H
> +
> +#include "text_insn.h"
> +
> +/*
> + * What text_mc.cpp has to be told about an arch to describe its instructions
> + * in the terms of text_insn.h with LLVM's MC layer.
> + *
> + * MC gives the length, the MCInst, the flags of MCInstrDesc, the explicit and
> + * the implicit defs, the aliases of a register and the target of a branch.
> + * It does not say what an instruction computes: the selection patterns are not
> + * a part of it. And the headers with the enums of a target are not installed.
> + * So opcodes and registers are given by name, as MCInstrInfo::getName() and
> + * MCRegisterInfo::getName() return them, and looked up once. A name which this
> + * LLVM does not have is an error.
> + */
> +
> +/* A condition on the operands of an MCInst, like an MCInstPredicate of TableGen */
> +enum mc_predicate_kind {
> + MC_TRUE,
> + MC_ZERO_REG, /* operand A is the zero register */
> + MC_SAME_REG, /* operands A and B are the same register */
> + MC_IMM, /* operand A is the immediate B */
> +};
> +
> +struct mc_predicate {
> + u8 kind;
> + u8 a;
> + s32 b;
> +};
> +
> +#define ZERO_REG(a) { MC_ZERO_REG, a, 0 }
> +#define SAME_REG(a, b) { MC_SAME_REG, a, b }
> +#define IMM(a, b) { MC_IMM, a, b }
> +#define VALUE(v) .fixed = true, .value = v
> +
> +/* Instead of the number of an operand: the first implicit def or use */
> +#define MC_IMPLICIT 0xff
> +
> +/*
> + * What an opcode computes, if it makes or copies a value. The patterns of an
> + * opcode are tried in their order. The width of the operation is that of the
> + * register DST.
> + *
> + * A pattern of kind TEXT_OTHER is about the defs. hasUnmodeledSideEffects() is
> + * how LLVM says that MCInstrDesc does not tell all about an opcode: it writes
> + * TEXT_ALL_REGS, unless such a pattern says that it writes what MCInstrDesc
> + * has, and DEFS.
> + *
> + * A pattern of kind TEXT_RETURN names a return to the caller. isReturn() holds
> + * for a return from an exception as well, which does not hand the return value
> + * register to a caller. Without such a pattern it is an opcode like any other,
> + * and LLVM says hasUnmodeledSideEffects() of it.
> + *
> + * Without a pattern errors are lost, none is made up. So an opcode has one only
> + * if that brings more than a few errors in some kernel.
> + */
> +struct mc_pattern {
> + const char *opcode;
> + u8 kind; /* enum text_insn_kind */
> + u8 dst, src, src2; /* the numbers of the operands. DST is 0 unless it is given */
> + u8 imm; /* the operand with the immediate */
> + u8 shift; /* the operand which says how far the immediate is shifted left */
> + bool invert; /* the immediate */
> + bool or_zero; /* TEXT_MOV_IMM: the immediate or 0 */
> + bool fixed; /* the immediate is VALUE, not an operand */
> + s64 value;
> + u32 defs; /* of TEXT_OTHER */
> + struct mc_predicate predicates[2];
> +};
> +
> +/* An instruction, in hex, and what text_arch.decode() has to say about it at MC_TEST_ADDR */
> +struct mc_test {
> + const char *code;
> + struct text_insn insn;
> +};
> +
> +#define MC_TEST_ADDR 0x1000
> +
> +struct mc_arch {
> + struct text_arch arch;
> + unsigned int machine; /* of the ELF header */
> + const char *triple, *features;
> + /*
> + * The names of registers 0, 1, ... of text_insn.h, separated by spaces.
> + * A write of a register in REGS32 zero extends. A write of any other
> + * alias is not a copy.
> + */
> + const char *regs, *regs32;
> + const char *zero_regs;
> + const struct mc_pattern *patterns;
> + /*
> + * LLVM's tables and names change from release to release, and the ones
> + * above were written for some of them. text_arch_init() fails if one of
> + * these instructions is no longer described as it was.
> + */
> + const struct mc_test *tests;
> +};
> +
> +/* The arch which the tool is built for, see Build */
> +extern const struct mc_arch mc_arch;
> +
> +#endif
> diff --git a/tools/bpf/resolve_btfids/text_mc_arm64.c b/tools/bpf/resolve_btfids/text_mc_arm64.c
> new file mode 100644
> index 000000000000..9cc44281242b
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/text_mc_arm64.c
> @@ -0,0 +1,118 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* arm64 for text_mc.cpp */
> +
> +#include <elf.h>
> +#include <linux/bits.h>
> +
> +#include "text_mc.h"
> +
> +/* The W and the X form of an opcode */
> +#define WX(before, after, ...) \
> + { before "W" after, __VA_ARGS__ }, { before "X" after, __VA_ARGS__ }
> +
> +static const struct mc_pattern arm64_patterns[] = {
> + WX("MOVN", "i", TEXT_MOV_IMM, .imm = 1, .shift = 2, .invert = true),
> + WX("ORR", "rs", TEXT_MOV, .src = 2, .predicates = { ZERO_REG(1), IMM(3, 0) }), /* mov */
> + { "SBFMXri", TEXT_SEXT32, .src = 1, .predicates = { IMM(2, 0), IMM(3, 31) } }, /* sxtw */
> + WX("CSEL", "r", TEXT_SELECT, .src = 1, .src2 = 2),
> + { "CSINVWr", TEXT_MOV_IMM, VALUE(-1), .or_zero = true,
> + .predicates = { ZERO_REG(1), ZERO_REG(2) } }, /* csetm */
> + { "CSINVWr", TEXT_SELECT_IMM, .src = 1, VALUE(-1), .predicates = { ZERO_REG(2) } },
> + /* rn, or a function of rm */
> + { "CSINCWr", TEXT_SELECT, .src = 1, .src2 = 1 },
> + /* and rd, rn, rm, asr #31: rn or 0 */
> + { "ANDWrs", TEXT_SELECT, .src = 1, .src2 = 1, .predicates = { IMM(3, 2 << 6 | 31) } },
> + /* HINT: nop, bti, yield, wfe ... */
> + { "HINT", TEXT_OTHER }, { "AUTIASP", TEXT_OTHER }, { "PRFMui", TEXT_OTHER },
> + { "DMB", TEXT_OTHER }, { "MRS", TEXT_OTHER }, { "MSR", TEXT_OTHER },
> + { "STLRB", TEXT_OTHER }, { "STLRW", TEXT_OTHER },
> + WX("LDXR", "", TEXT_OTHER), WX("STXR", "", TEXT_OTHER), WX("STLXR", "", TEXT_OTHER),
> + { "RET", TEXT_RETURN },
> + {}
> +};
> +
> +#define T(code, insn_kind, ...) { code, { .kind = insn_kind, __VA_ARGS__ } }
> +#define IMMS(a, b) .imm = a, .imm2 = b
> +#define SRC(reg) .src = reg, .src2 = reg
> +
> +/* Each pattern, and what must not match it */
> +static const struct mc_test arm64_tests[] = {
> + T("a0 02 80 12", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffea, 0xffffffea)), /* mov w0, #-22 */
> + T("a0 02 80 92", TEXT_MOV_IMM, IMMS(-22, -22)), /* mov x0, #-22 */
> + T("20 00 a0 12", TEXT_MOV_IMM, .is32 = true, IMMS(0xfffeffff, 0xfffeffff)), /* mov w0, #-0x10001 */
> + T("e0 03 13 2a", TEXT_MOV, .is32 = true, SRC(19)), /* mov w0, w19 */
> + T("e0 03 13 aa", TEXT_MOV, SRC(19)), /* mov x0, x19 */
> + T("20 00 02 2a", TEXT_OTHER, .defs = BIT(0)), /* orr w0, w1, w2 */
> + T("20 00 02 aa", TEXT_OTHER, .defs = BIT(0)), /* orr x0, x1, x2 */
> + T("e0 07 13 2a", TEXT_OTHER, .defs = BIT(0)), /* orr w0, wzr, w19, lsl #1 */
> + T("60 7e 40 93", TEXT_SEXT32, SRC(19)), /* sxtw x0, w19 */
> + T("60 3e 40 93", TEXT_OTHER, .defs = BIT(0)), /* sxth x0, w19 */
> + T("00 01 89 1a", TEXT_SELECT, .is32 = true, .src = 8, .src2 = 9), /* csel w0, w8, w9, eq */
> + T("00 01 89 9a", TEXT_SELECT, .src = 8, .src2 = 9), /* csel x0, x8, x9, eq */
> + T("00 01 9f 9a", TEXT_SELECT, SRC(8)), /* csel x0, x8, xzr, eq */
> + T("e0 03 9f 5a", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffff, 0)), /* csetm w0, ne */
> + T("00 01 9f 5a", TEXT_SELECT_IMM, .is32 = true, SRC(8), IMMS(0xffffffff, 0xffffffff)), /* csinv w0, w8, wzr, eq */
> + T("00 01 89 5a", TEXT_OTHER, .defs = BIT(0)), /* csinv w0, w8, w9, eq */
> + T("00 05 89 1a", TEXT_SELECT, .is32 = true, SRC(8)), /* csinc w0, w8, w9, eq */
> + T("00 7d 89 0a", TEXT_SELECT, .is32 = true, SRC(8)), /* and w0, w8, w9, asr #31 */
> + T("00 01 09 0a", TEXT_OTHER, .defs = BIT(0)), /* and w0, w8, w9 */
> + T("5f 24 03 d5", TEXT_OTHER), /* bti c */
> + T("bf 23 03 d5", TEXT_OTHER, .defs = BIT(30)), /* autiasp */
> + T("20 00 80 f9", TEXT_OTHER), /* prfm pldl1keep, [x1] */
> + T("bf 3b 03 d5", TEXT_OTHER), /* dmb ish */
> + /* The brk of BUG() has no next instruction. No return value passes a brk, like ud2 */
> + T("00 00 21 d4", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* brk #0x800 */
> + T("80 d0 38 d5", TEXT_OTHER, .defs = BIT(0)), /* mrs x0, tpidr_el1 */
> + T("80 d0 18 d5", TEXT_OTHER), /* msr tpidr_el1, x0 */
> + T("20 fc 9f 08", TEXT_OTHER), /* stlrb w0, [x1] */
> + T("20 fc 9f 88", TEXT_OTHER), /* stlr w0, [x1] */
> + T("20 7c 5f 88", TEXT_OTHER, .defs = BIT(0)), /* ldxr w0, [x1] */
> + T("20 7c 5f c8", TEXT_OTHER, .defs = BIT(0)), /* ldxr x0, [x1] */
> + T("41 7c 00 88", TEXT_OTHER, .defs = BIT(0)), /* stxr w0, w1, [x2] */
> + T("41 7c 00 c8", TEXT_OTHER, .defs = BIT(0)), /* stxr w0, x1, [x2] */
> + T("41 fc 00 88", TEXT_OTHER, .defs = BIT(0)), /* stlxr w0, w1, [x2] */
> + T("41 fc 00 c8", TEXT_OTHER, .defs = BIT(0)), /* stlxr w0, x1, [x2] */
> + /* hasUnmodeledSideEffects() */
> + T("02 00 00 d4", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* hvc #0 */
> + /* A write of these which is missed would change the errors of a function in some kernel */
> + T("20 00 a0 52", TEXT_OTHER, .defs = BIT(0)), /* mov w0, #0x10000 */
> + T("20 78 1d 12", TEXT_OTHER, .defs = BIT(0)), /* and w0, w1, #0xfffffffb */
> + T("00 01 09 8a", TEXT_OTHER, .defs = BIT(0)), /* and x0, x8, x9 */
> + T("00 01 09 6a", TEXT_OTHER, .defs = BIT(0)), /* ands w0, w8, w9 */
> + T("00 01 29 0a", TEXT_OTHER, .defs = BIT(0)), /* bic w0, w8, w9 */
> + T("20 00 00 32", TEXT_OTHER, .defs = BIT(0)), /* orr w0, w1, #1 */
> + T("20 04 00 11", TEXT_OTHER, .defs = BIT(0)), /* add w0, w1, #1 */
> + T("20 08 01 53", TEXT_OTHER, .defs = BIT(0)), /* ubfx w0, w1, #1, #2 */
> + T("20 00 40 f9", TEXT_OTHER, .defs = BIT(0)), /* ldr x0, [x1] */
> + T("20 00 40 b9", TEXT_OTHER, .defs = BIT(0)), /* ldr w0, [x1] */
> + T("20 c0 5f b8", TEXT_OTHER, .defs = BIT(0)), /* ldur w0, [x1, #-4] */
> + T("20 c0 9f b8", TEXT_OTHER, .defs = BIT(0)), /* ldursw x0, [x1, #-4] */
> + T("08 09 40 b8", TEXT_OTHER, .defs = BIT(8)), /* ldtr w8, [x8] */
> + /* The control flow */
> + T("10 00 00 14", TEXT_BRANCH, .imm = MC_TEST_ADDR + 0x40), /* b */
> + T("00 01 1f d6", TEXT_BRANCH), /* br x8 */
> + T("00 02 00 b4", TEXT_COND_BRANCH, .imm = MC_TEST_ADDR + 0x40), /* cbz x0 */
> + T("10 00 00 94", TEXT_CALL, .defs = BIT(30), .imm = MC_TEST_ADDR + 0x40), /* bl */
> + T("00 01 3f d6", TEXT_CALL, .defs = BIT(30)), /* blr x8 */
> + T("c0 03 5f d6", TEXT_RETURN), /* ret */
> + T("e0 03 9f d6", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* eret */
> + {}
> +};
> +
> +const struct mc_arch mc_arch = {
> + .arch = {
> + .retval = 0,
> + /* AAPCS64: x0 to x18 and x30 */
> + .caller_saved = 0x4007ffff,
> + },
> + .machine = EM_AARCH64,
> + .triple = "aarch64-unknown-linux-gnu",
> + .features = "+all",
> + .regs = "X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 "
> + "X21 X22 X23 X24 X25 X26 X27 X28 FP LR",
> + .regs32 = "W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 W19 W20 "
> + "W21 W22 W23 W24 W25 W26 W27 W28 W29 W30",
> + .zero_regs = "WZR XZR",
> + .patterns = arm64_patterns,
> + .tests = arm64_tests,
> +};
> diff --git a/tools/bpf/resolve_btfids/text_mc_x86.c b/tools/bpf/resolve_btfids/text_mc_x86.c
> new file mode 100644
> index 000000000000..11223cd1c077
> --- /dev/null
> +++ b/tools/bpf/resolve_btfids/text_mc_x86.c
> @@ -0,0 +1,118 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* x86-64 for text_mc.cpp */
> +
> +#include <elf.h>
> +#include <linux/bits.h>
> +
> +#include "text_mc.h"
> +
> +enum { RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11 };
> +
> +#define I MC_IMPLICIT
> +
> +static const struct mc_pattern x86_patterns[] = {
> + /* A 64-bit immediate, MOV64ri, may be a runtime constant, which the kernel patches */
> + { "MOV32ri", TEXT_MOV_IMM, .imm = 1 },
> + { "MOV64ri32", TEXT_MOV_IMM, .imm = 1 },
> + { "OR32ri8", TEXT_MOV_IMM, .imm = 2, .predicates = { IMM(2, -1) } },
> + { "OR64ri8", TEXT_MOV_IMM, .imm = 2, .predicates = { IMM(2, -1) } },
> + { "MOV32rr", TEXT_MOV, .src = 1 },
> + { "MOV64rr", TEXT_MOV, .src = 1 },
> + { "MOVSX64rr32", TEXT_SEXT32, .src = 1 },
> + { "CDQE", TEXT_SEXT32, .dst = I, .src = I },
> + { "CMOV32rr", TEXT_SELECT, .src = 2, .src2 = 1 },
> + { "CMOV64rr", TEXT_SELECT, .src = 2, .src2 = 1 },
> + /* All bits of the result are equal */
> + { "SBB32rr", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { SAME_REG(1, 2) } },
> + { "SBB64rr", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { SAME_REG(1, 2) } },
> + { "SAR32ri", TEXT_MOV_IMM, VALUE(-1), .or_zero = true, .predicates = { IMM(2, 31) } },
> + { "LOCK_PREFIX", TEXT_OTHER },
> + /* A rep prefix is not a part of the MCInst */
> + { "STOSQ", TEXT_OTHER, .defs = BIT(RCX) },
> + { "RET64", TEXT_RETURN },
> + {}
> +};
> +
> +#define T(code, insn_kind, ...) { code, { .kind = insn_kind, __VA_ARGS__ } }
> +#define IMMS(a, b) .imm = a, .imm2 = b
> +#define SRC(reg) .src = reg, .src2 = reg
> +
> +/* Each pattern, and what must not match it */
> +static const struct mc_test x86_tests[] = {
> + T("b8 ea ff ff ff", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffea, 0xffffffea)), /* mov $-22, %eax */
> + T("48 c7 c3 ea ff ff ff", TEXT_MOV_IMM, .dst = RBX, IMMS(-22, -22)), /* mov $-22, %rbx */
> + T("83 c8 ff", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffff, 0xffffffff)), /* or $-1, %eax */
> + T("48 83 c8 ff", TEXT_MOV_IMM, IMMS(-1, -1)), /* or $-1, %rax */
> + T("83 c8 f0", TEXT_OTHER, .defs = BIT(RAX)), /* or $-16, %eax */
> + T("48 83 c8 f0", TEXT_OTHER, .defs = BIT(RAX)), /* or $-16, %rax */
> + T("19 c0", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffff, 0)), /* sbb %eax, %eax */
> + T("48 19 c0", TEXT_MOV_IMM, IMMS(-1, 0)), /* sbb %rax, %rax */
> + T("19 d8", TEXT_OTHER, .defs = BIT(RAX)), /* sbb %ebx, %eax */
> + T("48 19 d8", TEXT_OTHER, .defs = BIT(RAX)), /* sbb %rbx, %rax */
> + T("c1 f8 1f", TEXT_MOV_IMM, .is32 = true, IMMS(0xffffffff, 0)), /* sar $31, %eax */
> + T("c1 f8 1e", TEXT_OTHER, .defs = BIT(RAX)), /* sar $30, %eax */
> + T("89 d8", TEXT_MOV, .is32 = true, SRC(RBX)), /* mov %ebx, %eax */
> + T("4c 89 e0", TEXT_MOV, SRC(12)), /* mov %r12, %rax */
> + T("48 63 c3", TEXT_SEXT32, SRC(RBX)), /* movslq %ebx, %rax */
> + T("48 98", TEXT_SEXT32, SRC(RAX)), /* cltq */
> + T("0f 44 c3", TEXT_SELECT, .is32 = true, .src = RBX, .src2 = RAX), /* cmove %ebx, %eax */
> + T("48 0f 44 c3", TEXT_SELECT, .src = RBX, .src2 = RAX), /* cmove %rbx, %rax */
> + T("f0", TEXT_OTHER), /* lock */
> + T("f3 48 ab", TEXT_OTHER, .defs = BIT(RCX) | BIT(RDI)), /* rep stosq */
> + /* hasUnmodeledSideEffects() */
> + T("f3 48 a5", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* rep movsq */
> + T("0f 05", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* syscall */
> + T("0f 0b", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* ud2 */
> + /* A write of these which is missed would change the errors of a function in some kernel */
> + T("31 d8", TEXT_OTHER, .defs = BIT(RAX)), /* xor %ebx, %eax */
> + T("8b 03", TEXT_OTHER, .defs = BIT(RAX)), /* mov (%rbx), %eax */
> + T("48 8b 03", TEXT_OTHER, .defs = BIT(RAX)), /* mov (%rbx), %rax */
> + T("8d 04 1b", TEXT_OTHER, .defs = BIT(RAX)), /* lea (%rbx,%rbx), %eax */
> + T("03 03", TEXT_OTHER, .defs = BIT(RAX)), /* add (%rbx), %eax */
> + T("83 c0 10", TEXT_OTHER, .defs = BIT(RAX)), /* add $16, %eax */
> + T("48 83 c0 10", TEXT_OTHER, .defs = BIT(RAX)), /* add $16, %rax */
> + T("05 00 01 00 00", TEXT_OTHER, .defs = BIT(RAX)), /* add $256, %eax */
> + T("81 c3 00 01 00 00", TEXT_OTHER, .defs = BIT(RBX)), /* add $256, %ebx */
> + T("48 81 c3 00 01 00 00", TEXT_OTHER, .defs = BIT(RBX)), /* add $256, %rbx */
> + T("29 d8", TEXT_OTHER, .defs = BIT(RAX)), /* sub %ebx, %eax */
> + T("21 d8", TEXT_OTHER, .defs = BIT(RAX)), /* and %ebx, %eax */
> + T("25 00 01 00 00", TEXT_OTHER, .defs = BIT(RAX)), /* and $256, %eax */
> + T("81 e3 00 01 00 00", TEXT_OTHER, .defs = BIT(RBX)), /* and $256, %ebx */
> + T("48 81 e3 00 01 00 00", TEXT_OTHER, .defs = BIT(RBX)), /* and $256, %rbx */
> + T("09 d8", TEXT_OTHER, .defs = BIT(RAX)), /* or %ebx, %eax */
> + T("48 63 03", TEXT_OTHER, .defs = BIT(RAX)), /* movslq (%rbx), %rax */
> + T("48 c1 e8 02", TEXT_OTHER, .defs = BIT(RAX)), /* shr $2, %rax */
> + T("48 c1 f8 3f", TEXT_OTHER, .defs = BIT(RAX)), /* sar $63, %rax */
> + T("0f 44 0b", TEXT_OTHER, .defs = BIT(RCX)), /* cmove (%rbx), %ecx */
> + T("48 0f 44 0b", TEXT_OTHER, .defs = BIT(RCX)), /* cmove (%rbx), %rcx */
> + T("83 d8 00", TEXT_OTHER, .defs = BIT(RAX)), /* sbb $0, %eax */
> + T("83 d0 00", TEXT_OTHER, .defs = BIT(RAX)), /* adc $0, %eax */
> + T("0f bd c3", TEXT_OTHER, .defs = BIT(RAX)), /* bsr %ebx, %eax */
> + T("48 0f bd c3", TEXT_OTHER, .defs = BIT(RAX)), /* bsr %rbx, %rax */
> + T("80 e3 01", TEXT_OTHER, .defs = BIT(RBX)), /* and $1, %bl */
> + T("48 0f c1 13", TEXT_OTHER, .defs = BIT(RDX)), /* xadd %rdx, (%rbx) */
> + /* The control flow */
> + T("eb 10", TEXT_BRANCH, .imm = MC_TEST_ADDR + 0x12), /* jmp */
> + T("ff e0", TEXT_BRANCH), /* jmp *%rax */
> + T("0f 84 00 01 00 00", TEXT_COND_BRANCH, .imm = MC_TEST_ADDR + 0x106), /* je */
> + T("e8 00 01 00 00", TEXT_CALL, .imm = MC_TEST_ADDR + 0x105), /* call */
> + T("c3", TEXT_RETURN), /* ret */
> + T("48 cf", TEXT_OTHER, .defs = TEXT_ALL_REGS), /* iretq */
> + {}
> +};
> +
> +const struct mc_arch mc_arch = {
> + .arch = {
> + .retval = RAX,
> + .caller_saved = BIT(RAX) | BIT(RCX) | BIT(RDX) | BIT(RSI) | BIT(RDI) |
> + BIT(R8) | BIT(R9) | BIT(R10) | BIT(R11),
> + .return_thunk = "__x86_return_thunk",
> + },
> + .machine = EM_X86_64,
> + .triple = "x86_64-unknown-linux-gnu",
> + .features = "",
> + .regs = "RAX RCX RDX RBX RSP RBP RSI RDI R8 R9 R10 R11 R12 R13 R14 R15",
> + .regs32 = "EAX ECX EDX EBX ESP EBP ESI EDI R8D R9D R10D R11D R12D R13D R14D R15D",
> + .patterns = x86_patterns,
> + .tests = x86_tests,
> +};
> --
> 2.55.0
>
next prev parent reply other threads:[~2026-11-13 11:33 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 5:08 [PATCH v2 bpf-next 00/13] bpf: fmod_ret for each function that returns errors Alexei Starovoitov
2026-11-13 5:08 ` [PATCH v2 bpf-next 01/13] selftests/bpf: Don't depend on the width of a BTF id in verifier_live_stack Alexei Starovoitov
2026-11-13 11:13 ` Anton Protopopov
2026-11-13 5:08 ` [PATCH v2 bpf-next 02/13] resolve_btfids: Read the code of an ELF file by address Alexei Starovoitov
2026-11-13 5:08 ` [PATCH v2 bpf-next 03/13] resolve_btfids: Describe machine instructions with LLVM's MC layer Alexei Starovoitov
2026-11-13 11:45 ` Anton Protopopov [this message]
2026-11-13 5:08 ` [PATCH v2 bpf-next 04/13] resolve_btfids: Find the definitions that reach the return value Alexei Starovoitov
2026-11-13 5:08 ` [PATCH v2 bpf-next 05/13] resolve_btfids: Tag the functions that return errors Alexei Starovoitov
2026-11-13 5:08 ` [PATCH v2 bpf-next 06/13] resolve_btfids: Fold the constants of a select without a branch Alexei Starovoitov
2026-11-13 11:59 ` Anton Protopopov
2026-11-13 5:08 ` [PATCH v2 bpf-next 07/13] resolve_btfids: Say which registers an instruction reads Alexei Starovoitov
2026-11-13 12:03 ` Anton Protopopov
2026-11-13 5:08 ` [PATCH v2 bpf-next 08/13] resolve_btfids: Don't list an error that follows work on an argument Alexei Starovoitov
2026-11-13 5:52 ` bot+bpf-ci
2026-11-13 5:08 ` [PATCH v2 bpf-next 09/13] bpf: Allow fmod_ret on tagged functions and check the return value Alexei Starovoitov
2026-11-13 5:52 ` bot+bpf-ci
2026-11-13 5:08 ` [PATCH v2 bpf-next 10/13] selftests/bpf: Test fmod_ret on tagged functions Alexei Starovoitov
2026-11-13 5:08 ` [PATCH v2 bpf-next 11/13] bpf: Mark the arguments of fmod_ret progs as PTR_UNTRUSTED Alexei Starovoitov
2026-11-13 11:09 ` Anton Protopopov
2026-11-13 5:09 ` [PATCH v2 bpf-next 12/13] bpf: Treat the type of an untrusted pointer as a hint Alexei Starovoitov
2026-11-13 5:52 ` bot+bpf-ci
2026-11-13 5:09 ` [PATCH v2 bpf-next 13/13] selftests/bpf: Test untrusted pointers in fmod_ret progs Alexei Starovoitov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=avb4R6DFgjdDI17L@mail.gmail.com \
--to=a.s.protopopov@gmail.com \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=memxor@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox