mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-02 18:53:22 +00:00
856 lines
30 KiB
Scheme
856 lines
30 KiB
Scheme
;; Linux BPF CPU description -*- Scheme -*-
|
|
;; Copyright (C) 2019 Free Software Foundation, Inc.
|
|
;;
|
|
;; Contributed by Oracle Inc.
|
|
;;
|
|
;; This file is part of the GNU Binutils and of GDB.
|
|
;;
|
|
;; This program is free software; you can redistribute it and/or
|
|
;; modify it under the terms of the GNU General Public License as
|
|
;; published by the Free Software Foundation; either version 3 of the
|
|
;; License, or (at your option) any later version.
|
|
;;
|
|
;; This program is distributed in the hope that it will be useful, but
|
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
;; General Public License for more details.
|
|
;;
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program; if not, write to the Free Software
|
|
;; Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
|
|
;; 02110-1301, USA.
|
|
|
|
;; This file contains a CGEN CPU description for the Linux kernel eBPF
|
|
;; instruction set. eBPF is documented in the linux kernel source
|
|
;; tree. See linux/Documentation/networking/filter.txt, and also the
|
|
;; sources in the networking subsystem, notably
|
|
;; linux/net/core/filter.c.
|
|
|
|
(include "simplify.inc")
|
|
|
|
(define-arch
|
|
(name bpf)
|
|
(comment "Linux kernel BPF")
|
|
(insn-lsb0? #t)
|
|
;; XXX explain the default-alignment setting is for the simulator.
|
|
;; It is confusing that the simulator follows the emulated memory
|
|
;; access conventions for fetching instructions by pieces...
|
|
(default-alignment unaligned)
|
|
(machs bpf xbpf)
|
|
(isas ebpfle ebpfbe xbpfle xbpfbe))
|
|
|
|
;;;; The ISAs
|
|
|
|
;; Logically, eBPF comforms a single instruction set featuring two
|
|
;; kind of instructions: 64-bit instructions and 128-bit instructions.
|
|
;;
|
|
;; The 64-bit instructions have the form:
|
|
;;
|
|
;; code:8 regs:8 offset:16 imm:32
|
|
;;
|
|
;; Whereas the 128-bit instructions (at the moment there is only one
|
|
;; of such instructions, lddw) have the form:
|
|
;;
|
|
;; code:8 regs:8 offset:16 imm:32 unused:32 imm:32
|
|
;;
|
|
;; In both formats `regs' is itself composed by two fields:
|
|
;;
|
|
;; dst:4 src:4
|
|
;;
|
|
;; The ISA is supposed to be orthogonal to endianness: the endianness
|
|
;; of the instruction fields follow the endianness of the host running
|
|
;; the eBPF program, and that's all. However, this is not entirely
|
|
;; true. The definition of an eBPF code in the Linux kernel is:
|
|
;;
|
|
;; struct bpf_insn {
|
|
;; __u8 code; /* opcode */
|
|
;; __u8 dst_reg:4; /* dest register */
|
|
;; __u8 src_reg:4; /* source register */
|
|
;; __s16 off; /* signed offset */
|
|
;; __s32 imm; /* signed immediate constant */
|
|
;; };
|
|
;;
|
|
;; Since the ordering of fields in C bitmaps is defined by the
|
|
;; implementation, the impact of endianness in the encoding of eBPF
|
|
;; instructions is effectively defined by GCC. In particular, GCC
|
|
;; places dst_reg before src_reg in little-endian code, and the other
|
|
;; way around in big-endian code.
|
|
;;
|
|
;; So, in reality, eBPF comprises two instruction sets: one for
|
|
;; little-endian with instructions like:
|
|
;;
|
|
;; code:8 src:4 dst:4 offset:16 imm:32 [unused:32 imm:32]
|
|
;;
|
|
;; and another for big-endian with instructions like:
|
|
;;
|
|
;; code:8 dst:4 src:4 offset:16 imm:32 [unused:32 imm:32]
|
|
;;
|
|
;; where `offset' and the immediate fields are encoded in
|
|
;; little-endian and big-endian byte-order, respectively.
|
|
|
|
(define-pmacro (define-bpf-isa x-endian)
|
|
(define-isa
|
|
(name (.sym ebpf x-endian))
|
|
(comment "The eBPF instruction set")
|
|
;; Default length to record in ifields. This is used in
|
|
;; calculations involving bit numbers.
|
|
(default-insn-word-bitsize 64)
|
|
;; Length of an unknown instruction. Used by disassembly and by the
|
|
;; simulator's invalid insn handler.
|
|
(default-insn-bitsize 64)
|
|
;; Number of bits of insn that can be initially fetched. This is
|
|
;; the size of the smallest insn.
|
|
(base-insn-bitsize 64)))
|
|
|
|
(define-bpf-isa le)
|
|
(define-bpf-isa be)
|
|
|
|
(define-pmacro (define-xbpf-isa x-endian)
|
|
(define-isa
|
|
(name (.sym xbpf x-endian))
|
|
(comment "The xBPF instruction set")
|
|
(default-insn-word-bitsize 64)
|
|
(default-insn-bitsize 64)
|
|
(base-insn-bitsize 64)))
|
|
|
|
(define-xbpf-isa le)
|
|
(define-xbpf-isa be)
|
|
|
|
(define-pmacro all-isas () (ISA ebpfle,ebpfbe,xbpfle,xbpfbe))
|
|
(define-pmacro xbpf-isas () (ISA xbpfle,xbpfbe))
|
|
|
|
(define-pmacro (endian-isas x-endian)
|
|
((ISA (.sym ebpf x-endian) (.sym xbpf x-endian))))
|
|
|
|
;;;; Hardware Hierarchy
|
|
|
|
;;
|
|
;; bpf architecture
|
|
;; |
|
|
;; bpfbf cpu-family
|
|
;; / \
|
|
;; bpf xbpf machine
|
|
;; | |
|
|
;; bpf-def xbpf-def model
|
|
|
|
(define-cpu
|
|
(name bpfbf)
|
|
(comment "Linux kernel eBPF virtual CPU")
|
|
(insn-endian big)
|
|
(word-bitsize 64))
|
|
|
|
(define-mach
|
|
(name bpf)
|
|
(comment "Linux eBPF")
|
|
(cpu bpfbf)
|
|
(isas ebpfle ebpfbe))
|
|
|
|
(define-model
|
|
(name bpf-def)
|
|
(comment "Linux eBPF default model")
|
|
(mach bpf)
|
|
(unit u-exec "execution unit" ()
|
|
1 ; issue
|
|
1 ; done
|
|
() ; state
|
|
() ; inputs
|
|
() ; outputs
|
|
() ; profile action (default)
|
|
))
|
|
|
|
(define-mach
|
|
(name xbpf)
|
|
(comment "Experimental BPF")
|
|
(cpu bpfbf)
|
|
(isas ebpfle ebpfbe xbpfle xbpfbe))
|
|
|
|
(define-model
|
|
(name xbpf-def)
|
|
(comment "xBPF default model")
|
|
(mach xbpf)
|
|
(unit u-exec "execution unit" ()
|
|
1 ; issue
|
|
1 ; done
|
|
() ; state
|
|
() ; inputs
|
|
() ; outputs
|
|
() ; profile action (default)
|
|
))
|
|
|
|
;;;; Hardware Elements
|
|
|
|
;; eBPF programs can access 10 general-purpose registers which are
|
|
;; 64-bit.
|
|
|
|
(define-hardware
|
|
(name h-gpr)
|
|
(comment "General Purpose Registers")
|
|
(attrs all-isas (MACH bpf xbpf))
|
|
(type register DI (16))
|
|
(indices keyword "%"
|
|
;; XXX the frame pointer fp is read-only, so it should
|
|
;; go in a different hardware.
|
|
(;; ABI names. Take priority when disassembling.
|
|
(r0 0) (r1 1) (r2 2) (r3 3) (r4 4) (r5 5) (r6 6)
|
|
(r7 7) (r8 8) (r9 9) (fp 10)
|
|
;; Additional names recognized when assembling.
|
|
(r0 0) (r6 6) (r10 10))))
|
|
|
|
;; The program counter. CGEN requires it, even if it is not visible
|
|
;; to eBPF programs.
|
|
|
|
(define-hardware
|
|
(name h-pc)
|
|
(comment "program counter")
|
|
(attrs PC PROFILE all-isas)
|
|
(type pc UDI)
|
|
(get () (raw-reg h-pc))
|
|
(set (newval) (set (raw-reg h-pc) newval)))
|
|
|
|
;; A 64-bit h-sint to be used by the imm64 operand below. XXX this
|
|
;; shouldn't be needed, as h-sint is supposed to be able to hold
|
|
;; 64-bit values. However, in practice CGEN limits h-sint to 32 bits
|
|
;; in 32-bit hosts. To be fixed in CGEN.
|
|
|
|
(dnh h-sint64 "signed 64-bit integer" (all-isas) (immediate DI)
|
|
() () ())
|
|
|
|
;;;; The Instruction Sets
|
|
|
|
;;; Fields and Opcodes
|
|
|
|
;; Convenience macro to shorten the definition of the fields below.
|
|
(define-pmacro (dwf x-name x-comment x-attrs
|
|
x-word-offset x-word-length x-start x-length
|
|
x-mode)
|
|
"Define a field including its containing word."
|
|
(define-ifield
|
|
(name x-name)
|
|
(comment x-comment)
|
|
(.splice attrs (.unsplice x-attrs))
|
|
(word-offset x-word-offset)
|
|
(word-length x-word-length)
|
|
(start x-start)
|
|
(length x-length)
|
|
(mode x-mode)))
|
|
|
|
;; For arithmetic and jump instructions the 8-bit code field is
|
|
;; subdivided in:
|
|
;;
|
|
;; op-code:4 op-src:1 op-class:3
|
|
|
|
(dwf f-op-code "eBPF opcode code" (all-isas) 0 8 7 4 UINT)
|
|
(dwf f-op-src "eBPF opcode source" (all-isas) 0 8 3 1 UINT)
|
|
(dwf f-op-class "eBPF opcode instruction class" (all-isas) 0 8 2 3 UINT)
|
|
|
|
(define-normal-insn-enum insn-op-code-alu "eBPF instruction codes"
|
|
(all-isas) OP_CODE_ f-op-code
|
|
(;; Codes for OP_CLASS_ALU and OP_CLASS_ALU64
|
|
(ADD #x0) (SUB #x1) (MUL #x2) (DIV #x3) (OR #x4) (AND #x5)
|
|
(LSH #x6) (RSH #x7) (NEG #x8) (MOD #x9) (XOR #xa) (MOV #xb)
|
|
(ARSH #xc) (END #xd)
|
|
;; xBPF-only: signed div, signed mod
|
|
(SDIV #xe) (SMOD #xf)
|
|
;; Codes for OP_CLASS_JMP
|
|
(JA #x0) (JEQ #x1) (JGT #x2) (JGE #x3) (JSET #x4)
|
|
(JNE #x5) (JSGT #x6) (JSGE #x7) (CALL #x8) (EXIT #x9)
|
|
(JLT #xa) (JLE #xb) (JSLT #xc) (JSLE #xd)))
|
|
|
|
(define-normal-insn-enum insn-op-src "eBPF instruction source"
|
|
(all-isas) OP_SRC_ f-op-src
|
|
;; X => use `src' as source operand.
|
|
;; K => use `imm32' as source operand.
|
|
((K #b0) (X #b1)))
|
|
|
|
(define-normal-insn-enum insn-op-class "eBPF instruction class"
|
|
(all-isas) OP_CLASS_ f-op-class
|
|
((LD #b000) (LDX #b001) (ST #b010) (STX #b011)
|
|
(ALU #b100) (JMP #b101) (JMP32 #b110) (ALU64 #b111)))
|
|
|
|
;; For load/store instructions, the 8-bit code field is subdivided in:
|
|
;;
|
|
;; op-mode:3 op-size:2 op-class:3
|
|
|
|
(dwf f-op-mode "eBPF opcode mode" (all-isas) 0 8 7 3 UINT)
|
|
(dwf f-op-size "eBPF opcode size" (all-isas) 0 8 4 2 UINT)
|
|
|
|
(define-normal-insn-enum insn-op-mode "eBPF load/store instruction modes"
|
|
(all-isas) OP_MODE_ f-op-mode
|
|
((IMM #b000) (ABS #b001) (IND #b010) (MEM #b011)
|
|
;; #b100 and #b101 are used in classic BPF only, reserved in eBPF.
|
|
(XADD #b110)))
|
|
|
|
(define-normal-insn-enum insn-op-size "eBPF load/store instruction sizes"
|
|
(all-isas) OP_SIZE_ f-op-size
|
|
((W #b00) ;; Word: 4 byte
|
|
(H #b01) ;; Half-word: 2 byte
|
|
(B #b10) ;; Byte: 1 byte
|
|
(DW #b11))) ;; Double-word: 8 byte
|
|
|
|
;; The fields for the source and destination registers are a bit
|
|
;; tricky. Due to the bizarre nibble swap between little-endian and
|
|
;; big-endian ISAs we need to keep different variants of the fields.
|
|
;;
|
|
;; Note that f-regs is used in the format spec of instructions that do
|
|
;; NOT use registers, where endianness is irrelevant i.e. f-regs is a
|
|
;; constant 0 opcode.
|
|
|
|
(dwf f-dstle "eBPF dst register field" ((ISA ebpfle xbpfle)) 8 8 3 4 UINT)
|
|
(dwf f-srcle "eBPF source register field" ((ISA ebpfle xbpfle)) 8 8 7 4 UINT)
|
|
|
|
(dwf f-dstbe "eBPF dst register field" ((ISA ebpfbe xbpfbe)) 8 8 7 4 UINT)
|
|
(dwf f-srcbe "eBPF source register field" ((ISA ebpfbe xbpfbe)) 8 8 3 4 UINT)
|
|
|
|
(dwf f-regs "eBPF registers field" (all-isas) 8 8 7 8 UINT)
|
|
|
|
;; Finally, the fields for the immediates.
|
|
;;
|
|
;; The 16-bit offsets and 32-bit immediates do not present any special
|
|
;; difficulty: we put them in their own instruction word so the
|
|
;; byte-endianness will be properly applied.
|
|
|
|
(dwf f-offset16 "eBPF offset field" (all-isas) 16 16 15 16 HI)
|
|
(dwf f-imm32 "eBPF 32-bit immediate field" (all-isas) 32 32 31 32 INT)
|
|
|
|
;; For the disjoint 64-bit signed immediate, however, we need to use a
|
|
;; multi-ifield.
|
|
|
|
(dwf f-imm64-a "eBPF 64-bit immediate a" (all-isas) 32 32 31 32 UINT)
|
|
(dwf f-imm64-b "eBPF 64-bit immediate b" (all-isas) 64 32 31 32 UINT)
|
|
(dwf f-imm64-c "eBPF 64-bit immediate c" (all-isas) 96 32 31 32 UINT)
|
|
|
|
(define-multi-ifield
|
|
(name f-imm64)
|
|
(comment "eBPF 64-bit immediate field")
|
|
(attrs all-isas)
|
|
(mode DI)
|
|
(subfields f-imm64-a f-imm64-b f-imm64-c)
|
|
(insert (sequence ()
|
|
(set (ifield f-imm64-b) (const 0))
|
|
(set (ifield f-imm64-c) (srl (ifield f-imm64) (const 32)))
|
|
(set (ifield f-imm64-a) (and (ifield f-imm64) (const #xffffffff)))))
|
|
(extract (sequence ()
|
|
(set (ifield f-imm64)
|
|
(or (sll UDI (zext UDI (ifield f-imm64-c)) (const 32))
|
|
(zext UDI (ifield f-imm64-a)))))))
|
|
|
|
;;; Operands
|
|
|
|
;; A couple of source and destination register operands are defined
|
|
;; for each ISA: ebpfle and ebpfbe.
|
|
|
|
(dno dstle "destination register" ((ISA ebpfle xbpfle)) h-gpr f-dstle)
|
|
(dno srcle "source register" ((ISA ebpfle xbpfle)) h-gpr f-srcle)
|
|
|
|
(dno dstbe "destination register" ((ISA ebpfbe xbpfbe)) h-gpr f-dstbe)
|
|
(dno srcbe "source register" ((ISA ebpfbe xbpfbe)) h-gpr f-srcbe)
|
|
|
|
;; Jump instructions have a 16-bit PC-relative address.
|
|
;; CALL instructions have a 32-bit PC-relative address.
|
|
|
|
(dno disp16 "16-bit PC-relative address" (all-isas PCREL-ADDR) h-sint
|
|
f-offset16)
|
|
(dno disp32 "32-bit PC-relative address" (all-isas PCREL-ADDR) h-sint
|
|
f-imm32)
|
|
|
|
;; Immediate operands in eBPF are signed, and we want the disassembler
|
|
;; to print negative values in a sane way. Therefore we use the macro
|
|
;; below to register a printer, which is itself defined as a C
|
|
;; function in bpf.opc.
|
|
|
|
;; define-normal-signed-immediate-operand
|
|
(define-pmacro (dnsio x-name x-comment x-attrs x-type x-index)
|
|
(define-operand
|
|
(name x-name)
|
|
(comment x-comment)
|
|
(.splice attrs (.unsplice x-attrs))
|
|
(type x-type)
|
|
(index x-index)
|
|
(handlers (print "immediate"))))
|
|
|
|
(dnsio imm32 "32-bit immediate" (all-isas) h-sint f-imm32)
|
|
(dnsio offset16 "16-bit offset" (all-isas) h-sint f-offset16)
|
|
|
|
;; The 64-bit immediate cannot use the default
|
|
;; cgen_parse_signed_integer, because it assumes operands are at much
|
|
;; 32-bit wide. Use our own.
|
|
|
|
(define-operand
|
|
(name imm64)
|
|
(comment "64-bit immediate")
|
|
(attrs all-isas)
|
|
(type h-sint64)
|
|
(index f-imm64)
|
|
(handlers (parse "imm64") (print "immediate")))
|
|
|
|
;; The endle/endbe instructions take an operand to specify the word
|
|
;; width in endianness conversions. We use both a parser and printer,
|
|
;; which are defined as C functions in bpf.opc.
|
|
|
|
(define-operand
|
|
(name endsize)
|
|
(comment "endianness size immediate: 16, 32 or 64")
|
|
(attrs all-isas)
|
|
(type h-uint)
|
|
(index f-imm32)
|
|
(handlers (parse "endsize") (print "endsize")))
|
|
|
|
;;; ALU instructions
|
|
|
|
;; For each opcode in insn-op-code-alu representing and integer
|
|
;; arithmetic instruction (ADD, SUB, etc) we define a bunch of
|
|
;; instruction variants:
|
|
;;
|
|
;; ADD[32]{i,r}le for the little-endian ISA
|
|
;; ADD[32]{i,r}be for the big-endian ISA
|
|
;;
|
|
;; The `i' variants perform `dst OP imm32 -> dst' operations.
|
|
;; The `r' variants perform `dst OP src -> dst' operations.
|
|
;;
|
|
;; The variants with 32 in their name are of ALU class. Otherwise
|
|
;; they are ALU64 class.
|
|
|
|
(define-pmacro (define-alu-insn-un x-basename x-suffix x-op-class x-op-code
|
|
x-endian x-mode x-semop)
|
|
(dni (.sym x-basename x-suffix x-endian)
|
|
(.str x-basename x-suffix)
|
|
(endian-isas x-endian)
|
|
(.str x-basename x-suffix " $dst" x-endian)
|
|
(+ (f-imm32 0) (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
x-op-class OP_SRC_K x-op-code)
|
|
(set x-mode (.sym dst x-endian) (x-semop x-mode (.sym dst x-endian)))
|
|
()))
|
|
|
|
(define-pmacro (define-alu-insn-bin x-basename x-suffix x-op-class x-op-code
|
|
x-endian x-mode x-semop x-isas)
|
|
(begin
|
|
;; dst = dst OP immediate
|
|
(dni (.sym x-basename x-suffix "i" x-endian)
|
|
(.str x-basename x-suffix " immediate")
|
|
(.splice (.unsplice x-isas))
|
|
(.str x-basename x-suffix " $dst" x-endian ",$imm32")
|
|
(+ imm32 (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
x-op-class OP_SRC_K x-op-code)
|
|
(set x-mode (.sym dst x-endian) (x-semop x-mode (.sym dst x-endian) imm32))
|
|
())
|
|
;; dst = dst OP src
|
|
(dni (.sym x-basename x-suffix "r" x-endian)
|
|
(.str x-basename x-suffix " register")
|
|
(.splice (.unsplice x-isas))
|
|
(.str x-basename x-suffix " $dst" x-endian ",$src" x-endian)
|
|
(+ (f-imm32 0) (f-offset16 0) (.sym src x-endian) (.sym dst x-endian)
|
|
x-op-class OP_SRC_X x-op-code)
|
|
(set x-mode (.sym dst x-endian)
|
|
(x-semop x-mode (.sym dst x-endian) (.sym src x-endian)))
|
|
())))
|
|
|
|
(define-pmacro (define-alu-insn-mov x-basename x-suffix x-op-class x-op-code
|
|
x-endian x-mode)
|
|
(begin
|
|
(dni (.sym mov x-suffix "i" x-endian)
|
|
(.str mov x-suffix " immediate")
|
|
(endian-isas x-endian)
|
|
(.str x-basename x-suffix " $dst" x-endian ",$imm32")
|
|
(+ imm32 (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
x-op-class OP_SRC_K x-op-code)
|
|
(set x-mode (.sym dst x-endian) imm32)
|
|
())
|
|
(dni (.sym mov x-suffix "r" x-endian)
|
|
(.str mov x-suffix " register")
|
|
(endian-isas x-endian)
|
|
(.str x-basename x-suffix " $dst" x-endian ",$src" x-endian)
|
|
(+ (f-imm32 0) (f-offset16 0) (.sym src x-endian) (.sym dst x-endian)
|
|
x-op-class OP_SRC_X x-op-code)
|
|
(set x-mode (.sym dst x-endian) (.sym src x-endian))
|
|
())))
|
|
|
|
|
|
;; Unary ALU instructions (neg)
|
|
(define-pmacro (daiu x-basename x-op-code x-endian x-semop)
|
|
(begin
|
|
(define-alu-insn-un x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI x-semop)
|
|
(define-alu-insn-un x-basename "32" OP_CLASS_ALU x-op-code x-endian USI x-semop)))
|
|
|
|
;; Binary ALU instructions (all the others)
|
|
;; For ALU32: DST = (u32) DST OP (u32) SRC is correct semantics
|
|
(define-pmacro (daib x-basename x-op-code x-endian x-semop x-isas)
|
|
(begin
|
|
(define-alu-insn-bin x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI x-semop x-isas)
|
|
(define-alu-insn-bin x-basename "32" OP_CLASS_ALU x-op-code x-endian USI x-semop x-isas)))
|
|
|
|
;; Move ALU instructions (mov)
|
|
(define-pmacro (daim x-basename x-op-code x-endian)
|
|
(begin
|
|
(define-alu-insn-mov x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI)
|
|
(define-alu-insn-mov x-basename "32" OP_CLASS_ALU x-op-code x-endian USI)))
|
|
|
|
(define-pmacro (define-alu-instructions x-endian)
|
|
(begin
|
|
(daib add OP_CODE_ADD x-endian add (endian-isas x-endian))
|
|
(daib sub OP_CODE_SUB x-endian sub (endian-isas x-endian))
|
|
(daib mul OP_CODE_MUL x-endian mul (endian-isas x-endian))
|
|
(daib div OP_CODE_DIV x-endian udiv (endian-isas x-endian))
|
|
(daib or OP_CODE_OR x-endian or (endian-isas x-endian))
|
|
(daib and OP_CODE_AND x-endian and (endian-isas x-endian))
|
|
(daib lsh OP_CODE_LSH x-endian sll (endian-isas x-endian))
|
|
(daib rsh OP_CODE_RSH x-endian srl (endian-isas x-endian))
|
|
(daib mod OP_CODE_MOD x-endian umod (endian-isas x-endian))
|
|
(daib xor OP_CODE_XOR x-endian xor (endian-isas x-endian))
|
|
(daib arsh OP_CODE_ARSH x-endian sra (endian-isas x-endian))
|
|
(daib sdiv OP_CODE_SDIV x-endian div ((ISA (.sym xbpf x-endian))))
|
|
(daib smod OP_CODE_SMOD x-endian mod ((ISA (.sym xbpf x-endian))))
|
|
(daiu neg OP_CODE_NEG x-endian neg)
|
|
(daim mov OP_CODE_MOV x-endian)))
|
|
|
|
(define-alu-instructions le)
|
|
(define-alu-instructions be)
|
|
|
|
;;; Endianness conversion instructions
|
|
|
|
;; The endianness conversion instructions come in several variants:
|
|
;;
|
|
;; END{le,be}le for the little-endian ISA
|
|
;; END{le,be}be for the big-endian ISA
|
|
;;
|
|
;; Please do not be confused by the repeated `be' and `le' here. Each
|
|
;; ISA has both endle and endbe instructions. It is the disposition
|
|
;; of the source and destination register fields that change between
|
|
;; ISAs, not the semantics of the instructions themselves (see section
|
|
;; "The ISAs" above in this very file.)
|
|
|
|
(define-pmacro (define-endian-insn x-suffix x-op-src x-endian)
|
|
(dni (.sym "end" x-suffix x-endian)
|
|
(.str "end" x-suffix " register")
|
|
(endian-isas x-endian)
|
|
(.str "end" x-suffix " $dst" x-endian ",$endsize")
|
|
(+ (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian) endsize
|
|
OP_CLASS_ALU x-op-src OP_CODE_END)
|
|
(set (.sym dst x-endian)
|
|
(c-call DI (.str "bpfbf_end" x-suffix) (.sym dst x-endian) endsize))
|
|
()))
|
|
|
|
(define-endian-insn "le" OP_SRC_K le)
|
|
(define-endian-insn "be" OP_SRC_X le)
|
|
(define-endian-insn "le" OP_SRC_K be)
|
|
(define-endian-insn "be" OP_SRC_X be)
|
|
|
|
;;; Load/Store instructions
|
|
|
|
;; The lddw instruction takes a 64-bit immediate as an operand. Since
|
|
;; this instruction also takes a `dst' operand, we need to define a
|
|
;; variant for each ISA:
|
|
;;
|
|
;; LDDWle for the little-endian ISA
|
|
;; LDDWbe for the big-endian ISA
|
|
|
|
(define-pmacro (define-lddw x-endian)
|
|
(dni (.sym lddw x-endian)
|
|
(.str "lddw" x-endian)
|
|
(endian-isas x-endian)
|
|
(.str "lddw $dst" x-endian ",$imm64")
|
|
(+ imm64 (f-offset16 0) ((.sym f-src x-endian) 0)
|
|
(.sym dst x-endian)
|
|
OP_CLASS_LD OP_SIZE_DW OP_MODE_IMM)
|
|
(set DI (.sym dst x-endian) imm64)
|
|
()))
|
|
|
|
(define-lddw le)
|
|
(define-lddw be)
|
|
|
|
;; The absolute load instructions are non-generic loads designed to be
|
|
;; used in socket filters. They come in several variants:
|
|
;;
|
|
;; LDABS{w,h,b,dw}
|
|
|
|
(define-pmacro (dlabs x-suffix x-size x-smode)
|
|
(dni (.sym "ldabs" x-suffix)
|
|
(.str "ldabs" x-suffix)
|
|
(all-isas)
|
|
(.str "ldabs" x-suffix " $imm32")
|
|
(+ imm32 (f-offset16 0) (f-regs 0)
|
|
OP_CLASS_LD OP_MODE_ABS (.sym OP_SIZE_ x-size))
|
|
(set x-smode
|
|
(reg x-smode h-gpr 0)
|
|
(mem x-smode
|
|
(add DI
|
|
(mem DI
|
|
(add DI
|
|
(reg DI h-gpr 6) ;; Pointer to struct sk_buff
|
|
(c-call "bpfbf_skb_data_offset")))
|
|
imm32)))
|
|
;; XXX this clobbers R1-R5
|
|
()))
|
|
|
|
(dlabs "w" W SI)
|
|
(dlabs "h" H HI)
|
|
(dlabs "b" B QI)
|
|
(dlabs "dw" DW DI)
|
|
|
|
;; The indirect load instructions are non-generic loads designed to be
|
|
;; used in socket filters. They come in several variants:
|
|
;;
|
|
;; LDIND{w,h,b,dw}le for the little-endian ISA
|
|
;; LDIND[w,h,b,dw}be for the big-endian ISA
|
|
|
|
(define-pmacro (dlind x-suffix x-size x-endian x-smode)
|
|
(dni (.sym "ldind" x-suffix x-endian)
|
|
(.str "ldind" x-suffix)
|
|
(endian-isas x-endian)
|
|
(.str "ldind" x-suffix " $src" x-endian ",$imm32")
|
|
(+ imm32 (f-offset16 0) ((.sym f-dst x-endian) 0) (.sym src x-endian)
|
|
OP_CLASS_LD OP_MODE_IND (.sym OP_SIZE_ x-size))
|
|
(set x-smode
|
|
(reg x-smode h-gpr 0)
|
|
(mem x-smode
|
|
(add DI
|
|
(mem DI
|
|
(add DI
|
|
(reg DI h-gpr 6) ;; Pointer to struct sk_buff
|
|
(c-call "bpfbf_skb_data_offset")))
|
|
(add DI
|
|
(.sym src x-endian)
|
|
imm32))))
|
|
;; XXX this clobbers R1-R5
|
|
()))
|
|
|
|
(define-pmacro (define-ldind x-endian)
|
|
(begin
|
|
(dlind "w" W x-endian SI)
|
|
(dlind "h" H x-endian HI)
|
|
(dlind "b" B x-endian QI)
|
|
(dlind "dw" DW x-endian DI)))
|
|
|
|
(define-ldind le)
|
|
(define-ldind be)
|
|
|
|
;; Generic load and store instructions are provided for several word
|
|
;; sizes. They come in several variants:
|
|
;;
|
|
;; LDX{b,h,w,dw}le, STX{b,h,w,dw}le for the little-endian ISA
|
|
;;
|
|
;; LDX{b,h,w,dw}be, STX{b,h,w,dw}be for the big-endian ISA
|
|
;;
|
|
;; Loads operate on [$SRC+-OFFSET] -> $DST
|
|
;; Stores operate on $SRC -> [$DST+-OFFSET]
|
|
|
|
(define-pmacro (dxli x-basename x-suffix x-size x-endian x-mode)
|
|
(dni (.sym x-basename x-suffix x-endian)
|
|
(.str x-basename x-suffix)
|
|
(endian-isas x-endian)
|
|
(.str x-basename x-suffix " $dst" x-endian ",[$src" x-endian "+$offset16]")
|
|
(+ (f-imm32 0) offset16 (.sym src x-endian) (.sym dst x-endian)
|
|
OP_CLASS_LDX (.sym OP_SIZE_ x-size) OP_MODE_MEM)
|
|
(set x-mode
|
|
(.sym dst x-endian)
|
|
(mem x-mode (add DI (.sym src x-endian) offset16)))
|
|
()))
|
|
|
|
(define-pmacro (dxsi x-basename x-suffix x-size x-endian x-mode)
|
|
(dni (.sym x-basename x-suffix x-endian)
|
|
(.str x-basename x-suffix)
|
|
(endian-isas x-endian)
|
|
(.str x-basename x-suffix " [$dst" x-endian "+$offset16],$src" x-endian)
|
|
(+ (f-imm32 0) offset16 (.sym src x-endian) (.sym dst x-endian)
|
|
OP_CLASS_STX (.sym OP_SIZE_ x-size) OP_MODE_MEM)
|
|
(set x-mode
|
|
(mem x-mode (add DI (.sym dst x-endian) offset16))
|
|
(.sym src x-endian)) ;; XXX address is section-relative
|
|
()))
|
|
|
|
(define-pmacro (define-ldstx-insns x-endian)
|
|
(begin
|
|
(dxli "ldx" "w" W x-endian SI)
|
|
(dxli "ldx" "h" H x-endian HI)
|
|
(dxli "ldx" "b" B x-endian QI)
|
|
(dxli "ldx" "dw" DW x-endian DI)
|
|
|
|
(dxsi "stx" "w" W x-endian SI)
|
|
(dxsi "stx" "h" H x-endian HI)
|
|
(dxsi "stx" "b" B x-endian QI)
|
|
(dxsi "stx" "dw" DW x-endian DI)))
|
|
|
|
(define-ldstx-insns le)
|
|
(define-ldstx-insns be)
|
|
|
|
;; Generic store instructions of the form IMM32 -> [$DST+OFFSET] are
|
|
;; provided in several variants:
|
|
;;
|
|
;; ST{b,h,w,dw}le for the little-endian ISA
|
|
;; ST{b,h,w,dw}be for the big-endian ISA
|
|
|
|
(define-pmacro (dsti x-suffix x-size x-endian x-mode)
|
|
(dni (.sym "st" x-suffix x-endian)
|
|
(.str "st" x-suffix)
|
|
(endian-isas x-endian)
|
|
(.str "st" x-suffix " [$dst" x-endian "+$offset16],$imm32")
|
|
(+ imm32 offset16 ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
OP_CLASS_ST (.sym OP_SIZE_ x-size) OP_MODE_MEM)
|
|
(set x-mode
|
|
(mem x-mode (add DI (.sym dst x-endian) offset16))
|
|
imm32) ;; XXX address is section-relative
|
|
()))
|
|
|
|
(define-pmacro (define-st-insns x-endian)
|
|
(begin
|
|
(dsti "b" B x-endian QI)
|
|
(dsti "h" H x-endian HI)
|
|
(dsti "w" W x-endian SI)
|
|
(dsti "dw" DW x-endian DI)))
|
|
|
|
(define-st-insns le)
|
|
(define-st-insns be)
|
|
|
|
;;; Jump instructions
|
|
|
|
;; Compare-and-jump instructions, on the other hand, make use of
|
|
;; registers. Therefore, we need to define several variants in both
|
|
;; ISAs:
|
|
;;
|
|
;; J{eq,gt,ge,lt,le,set,ne,sgt,sge,slt,sle}[32]{i,r}le for the
|
|
;; little-endian ISA.
|
|
;; J{eq,gt,ge,lt,le,set,ne.sgt,sge,slt,sle}[32]{i,r}be for the
|
|
;; big-endian ISA.
|
|
|
|
(define-pmacro (define-cond-jump-insn x-cond x-suffix x-op-class x-op-code x-endian x-mode x-semop)
|
|
(begin
|
|
(dni (.sym j x-cond x-suffix i x-endian)
|
|
(.str j x-cond x-suffix " i")
|
|
(endian-isas x-endian)
|
|
(.str "j" x-cond x-suffix " $dst" x-endian ",$imm32,$disp16")
|
|
(+ imm32 disp16 ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
x-op-class OP_SRC_K (.sym OP_CODE_ x-op-code))
|
|
(if VOID (x-semop x-mode (.sym dst x-endian) imm32)
|
|
(set DI
|
|
(reg DI h-pc) (add DI (reg DI h-pc)
|
|
(mul DI (add HI disp16 1) 8))))
|
|
())
|
|
(dni (.sym j x-cond x-suffix r x-endian)
|
|
(.str j x-cond x-suffix " r")
|
|
(endian-isas x-endian)
|
|
(.str "j" x-cond x-suffix " $dst" x-endian ",$src" x-endian ",$disp16")
|
|
(+ (f-imm32 0) disp16 (.sym src x-endian) (.sym dst x-endian)
|
|
x-op-class OP_SRC_X (.sym OP_CODE_ x-op-code))
|
|
(if VOID (x-semop x-mode (.sym dst x-endian) (.sym src x-endian))
|
|
(set DI
|
|
(reg DI h-pc) (add DI (reg DI h-pc)
|
|
(mul DI (add HI disp16 1) 8))))
|
|
())))
|
|
|
|
(define-pmacro (dcji x-cond x-op-code x-endian x-semop)
|
|
(begin
|
|
(define-cond-jump-insn x-cond "" OP_CLASS_JMP x-op-code x-endian DI x-semop)
|
|
(define-cond-jump-insn x-cond "32" OP_CLASS_JMP32 x-op-code x-endian SI x-semop )))
|
|
|
|
(define-pmacro (define-condjump-insns x-endian)
|
|
(begin
|
|
(dcji "eq" JEQ x-endian eq)
|
|
(dcji "gt" JGT x-endian gtu)
|
|
(dcji "ge" JGE x-endian geu)
|
|
(dcji "lt" JLT x-endian ltu)
|
|
(dcji "le" JLE x-endian leu)
|
|
(dcji "set" JSET x-endian and)
|
|
(dcji "ne" JNE x-endian ne)
|
|
(dcji "sgt" JSGT x-endian gt)
|
|
(dcji "sge" JSGE x-endian ge)
|
|
(dcji "slt" JSLT x-endian lt)
|
|
(dcji "sle" JSLE x-endian le)))
|
|
|
|
(define-condjump-insns le)
|
|
(define-condjump-insns be)
|
|
|
|
;; The `call' instruction doesn't make use of registers, but the
|
|
;; semantic routine should have access to the src register in order to
|
|
;; properly interpret the meaning of disp32. Therefore we need one
|
|
;; version per ISA.
|
|
|
|
(define-pmacro (define-call-insn x-endian)
|
|
(dni (.sym call x-endian)
|
|
"call"
|
|
(endian-isas x-endian)
|
|
"call $disp32"
|
|
(+ disp32 (f-offset16 0) (f-regs 0)
|
|
OP_CLASS_JMP OP_SRC_K OP_CODE_CALL)
|
|
(c-call VOID
|
|
"bpfbf_call" disp32 (ifield (.sym f-src x-endian)))
|
|
()))
|
|
|
|
(define-call-insn le)
|
|
(define-call-insn be)
|
|
|
|
(define-pmacro (define-callr-insn x-endian)
|
|
(dni (.sym callr x-endian)
|
|
"callr"
|
|
((ISA (.sym xbpf x-endian)))
|
|
(.str "call $dst" x-endian)
|
|
(+ (f-imm32 0) (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
|
|
OP_CLASS_JMP OP_SRC_X OP_CODE_CALL)
|
|
(c-call VOID
|
|
"bpfbf_callr" (ifield (.sym f-dst x-endian)))
|
|
()))
|
|
|
|
(define-callr-insn le)
|
|
(define-callr-insn be)
|
|
|
|
;; The jump-always and `exit' instructions dont make use of either
|
|
;; source nor destination registers, so only one variant per
|
|
;; instruction is defined.
|
|
|
|
(dni ja "ja" (all-isas) "ja $disp16"
|
|
(+ (f-imm32 0) disp16 (f-regs 0)
|
|
OP_CLASS_JMP OP_SRC_K OP_CODE_JA)
|
|
(set DI (reg DI h-pc) (add DI (reg DI h-pc)
|
|
(mul DI (add HI disp16 1) 8)))
|
|
())
|
|
|
|
(dni "exit" "exit" (all-isas) "exit"
|
|
(+ (f-imm32 0) (f-offset16 0) (f-regs 0)
|
|
OP_CLASS_JMP (f-op-src 0) OP_CODE_EXIT)
|
|
(c-call VOID "bpfbf_exit")
|
|
())
|
|
|
|
;;; Atomic instructions
|
|
|
|
;; The atomic exchange-and-add instructions come in two flavors: one
|
|
;; for swapping 64-bit quantities and another for 32-bit quantities.
|
|
|
|
(define-pmacro (sem-exchange-and-add x-endian x-mode)
|
|
(sequence VOID ((x-mode tmp))
|
|
;; XXX acquire lock in simulator... as a hardware element?
|
|
(set x-mode tmp (mem x-mode (add DI (.sym dst x-endian) offset16)))
|
|
(set x-mode
|
|
(mem x-mode (add DI (.sym dst x-endian) offset16))
|
|
(add x-mode tmp (.sym src x-endian)))))
|
|
|
|
(define-pmacro (define-atomic-insns x-endian)
|
|
(begin
|
|
(dni (.str "xadddw" x-endian)
|
|
"xadddw"
|
|
(endian-isas x-endian)
|
|
(.str "xadddw [$dst" x-endian "+$offset16],$src" x-endian)
|
|
(+ (f-imm32 0) (.sym src x-endian) (.sym dst x-endian)
|
|
offset16 OP_MODE_XADD OP_SIZE_DW OP_CLASS_STX)
|
|
(sem-exchange-and-add x-endian DI)
|
|
())
|
|
(dni (.str "xaddw" x-endian)
|
|
"xaddw"
|
|
(endian-isas x-endian)
|
|
(.str "xaddw [$dst" x-endian "+$offset16],$src" x-endian)
|
|
(+ (f-imm32 0) (.sym src x-endian) (.sym dst x-endian)
|
|
offset16 OP_MODE_XADD OP_SIZE_W OP_CLASS_STX)
|
|
(sem-exchange-and-add x-endian SI)
|
|
())))
|
|
|
|
(define-atomic-insns le)
|
|
(define-atomic-insns be)
|
|
|
|
;;; Breakpoint instruction
|
|
|
|
;; The brkpt instruction is used by the BPF simulator and it doesn't
|
|
;; really belong to the eBPF instruction set.
|
|
|
|
(dni "brkpt" "brkpt" (all-isas) "brkpt"
|
|
(+ (f-imm32 0) (f-offset16 0) (f-regs 0)
|
|
OP_CLASS_ALU OP_SRC_X OP_CODE_NEG)
|
|
(c-call VOID "bpfbf_breakpoint")
|
|
())
|