| --[[ | |
| Copyright 2016 Marek Vavrusa <mvavrusa@cloudflare.com> | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| ]] | |
| local ffi = require('ffi') | |
| local bit = require('bit') | |
| local cdef = require('bpf.cdef') | |
| local BPF, HELPER = ffi.typeof('struct bpf'), ffi.typeof('struct bpf_func_id') | |
| local const_width = { | |
| [1] = BPF.B, [2] = BPF.H, [4] = BPF.W, [8] = BPF.DW, | |
| } | |
| local const_width_type = { | |
| [1] = ffi.typeof('uint8_t'), [2] = ffi.typeof('uint16_t'), [4] = ffi.typeof('uint32_t'), [8] = ffi.typeof('uint64_t'), | |
| } | |
| -- Built-ins that will be translated into BPF instructions | |
| -- i.e. bit.bor(0xf0, 0x0f) becomes {'alu64, or, k', reg(0xf0), reg(0x0f), 0, 0} | |
| local builtins = { | |
| [bit.lshift] = 'LSH', | |
| [bit.rshift] = 'RSH', | |
| [bit.band] = 'AND', | |
| [bit.bnot] = 'NEG', | |
| [bit.bor] = 'OR', | |
| [bit.bxor] = 'XOR', | |
| [bit.arshift] = 'ARSH', | |
| -- Extensions and intrinsics | |
| } | |
| local function width_type(w) | |
| -- Note: ffi.typeof doesn't accept '?' as template | |
| return const_width_type[w] or ffi.typeof(string.format('uint8_t [%d]', w)) | |
| end | |
| builtins.width_type = width_type | |
| -- Return struct member size/type (requires LuaJIT 2.1+) | |
| -- I am ashamed that there's no easier way around it. | |
| local function sizeofattr(ct, name) | |
| if not ffi.typeinfo then error('LuaJIT 2.1+ is required for ffi.typeinfo') end | |
| local cinfo = ffi.typeinfo(ct) | |
| while true do | |
| cinfo = ffi.typeinfo(cinfo.sib) | |
| if not cinfo then return end | |
| if cinfo.name == name then break end | |
| end | |
| local size = math.max(1, ffi.typeinfo(cinfo.sib or ct).size - cinfo.size) | |
| -- Guess type name | |
| return size, builtins.width_type(size) | |
| end | |
| builtins.sizeofattr = sizeofattr | |
| -- Byte-order conversions for little endian | |
| local function ntoh(x, w) | |
| if w then x = ffi.cast(const_width_type[w/8], x) end | |
| return bit.bswap(x) | |
| end | |
| local function hton(x, w) return ntoh(x, w) end | |
| builtins.ntoh = ntoh | |
| builtins.hton = hton | |
| builtins[ntoh] = function (e, dst, a, w) | |
| -- This is trickery, but TO_LE means cpu_to_le(), | |
| -- and we want exactly the opposite as network is always 'be' | |
| w = w or ffi.sizeof(e.V[a].type)*8 | |
| if w == 8 then return end -- NOOP | |
| assert(w <= 64, 'NYI: hton(a[, width]) - operand larger than register width') | |
| -- Allocate registers and execute | |
| e.vcopy(dst, a) | |
| e.emit(BPF.ALU + BPF.END + BPF.TO_BE, e.vreg(dst), 0, 0, w) | |
| end | |
| builtins[hton] = function (e, dst, a, w) | |
| w = w or ffi.sizeof(e.V[a].type)*8 | |
| if w == 8 then return end -- NOOP | |
| assert(w <= 64, 'NYI: hton(a[, width]) - operand larger than register width') | |
| -- Allocate registers and execute | |
| e.vcopy(dst, a) | |
| e.emit(BPF.ALU + BPF.END + BPF.TO_LE, e.vreg(dst), 0, 0, w) | |
| end | |
| -- Byte-order conversions for big endian are no-ops | |
| if ffi.abi('be') then | |
| ntoh = function (x, w) | |
| return w and ffi.cast(const_width_type[w/8], x) or x | |
| end | |
| hton = ntoh | |
| builtins[ntoh] = function(_, _, _) return end | |
| builtins[hton] = function(_, _, _) return end | |
| end | |
| -- Other built-ins | |
| local function xadd() error('NYI') end | |
| builtins.xadd = xadd | |
| builtins[xadd] = function (e, ret, a, b, off) | |
| local vinfo = e.V[a].const | |
| assert(vinfo and vinfo.__dissector, 'xadd(a, b[, offset]) called on non-pointer') | |
| local w = ffi.sizeof(vinfo.__dissector) | |
| -- Calculate structure attribute offsets | |
| if e.V[off] and type(e.V[off].const) == 'string' then | |
| local ct, field = vinfo.__dissector, e.V[off].const | |
| off = ffi.offsetof(ct, field) | |
| assert(off, 'xadd(a, b, offset) - offset is not valid in given structure') | |
| w = sizeofattr(ct, field) | |
| end | |
| assert(w == 4 or w == 8, 'NYI: xadd() - 1 and 2 byte atomic increments are not supported') | |
| -- Allocate registers and execute | |
| local src_reg = e.vreg(b) | |
| local dst_reg = e.vreg(a) | |
| -- Set variable for return value and call | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) | |
| -- Optimize the NULL check away if provably not NULL | |
| if not e.V[a].source or e.V[a].source:find('_or_null', 1, true) then | |
| e.emit(BPF.JMP + BPF.JEQ + BPF.K, dst_reg, 0, 1, 0) -- if (dst != NULL) | |
| end | |
| e.emit(BPF.XADD + BPF.STX + const_width[w], dst_reg, src_reg, off or 0, 0) | |
| end | |
| local function probe_read() error('NYI') end | |
| builtins.probe_read = probe_read | |
| builtins[probe_read] = function (e, ret, dst, src, vtype, ofs) | |
| e.reg_alloc(e.tmpvar, 1) | |
| -- Load stack pointer to dst, since only load to stack memory is supported | |
| -- we have to use allocated stack memory or create a new allocation and convert | |
| -- to pointer type | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0) | |
| if not e.V[dst].const or not e.V[dst].const.__base > 0 then | |
| builtins[ffi.new](e, dst, vtype) -- Allocate stack memory | |
| end | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base) | |
| -- Set stack memory maximum size bound | |
| e.reg_alloc(e.tmpvar, 2) | |
| if not vtype then | |
| vtype = cdef.typename(e.V[dst].type) | |
| -- Dereference pointer type to pointed type for size calculation | |
| if vtype:sub(-1) == '*' then vtype = vtype:sub(0, -2) end | |
| end | |
| local w = ffi.sizeof(vtype) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, w) | |
| -- Set source pointer | |
| if e.V[src].reg then | |
| e.reg_alloc(e.tmpvar, 3) -- Copy from original register | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, e.V[src].reg, 0, 0) | |
| else | |
| e.vreg(src, 3) | |
| e.reg_spill(src) -- Spill to avoid overwriting | |
| end | |
| if ofs and ofs > 0 then | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 3, 0, 0, ofs) | |
| end | |
| -- Call probe read helper | |
| ret = ret or e.tmpvar | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.probe_read) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| builtins[ffi.cast] = function (e, dst, ct, x) | |
| assert(e.V[ct].const, 'ffi.cast(ctype, x) called with bad ctype') | |
| e.vcopy(dst, x) | |
| if e.V[x].const and type(e.V[x].const) == 'table' then | |
| e.V[dst].const.__dissector = ffi.typeof(e.V[ct].const) | |
| end | |
| e.V[dst].type = ffi.typeof(e.V[ct].const) | |
| -- Specific types also encode source of the data | |
| -- This is because BPF has different helpers for reading | |
| -- different data sources, so variables must track origins. | |
| -- struct pt_regs - source of the data is probe | |
| -- struct skb - source of the data is socket buffer | |
| -- struct X - source of the data is probe/tracepoint | |
| if ffi.typeof(e.V[ct].const) == ffi.typeof('struct pt_regs') then | |
| e.V[dst].source = 'ptr_to_probe' | |
| end | |
| end | |
| builtins[ffi.new] = function (e, dst, ct, x) | |
| if type(ct) == 'number' then | |
| ct = ffi.typeof(e.V[ct].const) -- Get ctype from variable | |
| end | |
| assert(not x, 'NYI: ffi.new(ctype, ...) - initializer is not supported') | |
| assert(not cdef.isptr(ct, true), 'NYI: ffi.new(ctype, ...) - ctype MUST NOT be a pointer') | |
| e.vset(dst, nil, ct) | |
| e.V[dst].source = 'ptr_to_stack' | |
| e.V[dst].const = {__base = e.valloc(ffi.sizeof(ct), true), __dissector = ct} | |
| -- Set array dissector if created an array | |
| -- e.g. if ct is 'char [2]', then dissector is 'char' | |
| local elem_type = tostring(ct):match('ctype<(.+)%s%[(%d+)%]>') | |
| if elem_type then | |
| e.V[dst].const.__dissector = ffi.typeof(elem_type) | |
| end | |
| end | |
| builtins[ffi.copy] = function (e, ret, dst, src) | |
| assert(cdef.isptr(e.V[dst].type), 'ffi.copy(dst, src) - dst MUST be a pointer type') | |
| assert(cdef.isptr(e.V[src].type), 'ffi.copy(dst, src) - src MUST be a pointer type') | |
| -- Specific types also encode source of the data | |
| -- struct pt_regs - source of the data is probe | |
| -- struct skb - source of the data is socket buffer | |
| if e.V[src].source and e.V[src].source:find('ptr_to_probe', 1, true) then | |
| e.reg_alloc(e.tmpvar, 1) | |
| -- Load stack pointer to dst, since only load to stack memory is supported | |
| -- we have to either use spilled variable or allocated stack memory offset | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0) | |
| if e.V[dst].spill then | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].spill) | |
| elseif e.V[dst].const.__base then | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base) | |
| else error('ffi.copy(dst, src) - can\'t get stack offset of dst') end | |
| -- Set stack memory maximum size bound | |
| local dst_tname = cdef.typename(e.V[dst].type) | |
| if dst_tname:sub(-1) == '*' then dst_tname = dst_tname:sub(0, -2) end | |
| e.reg_alloc(e.tmpvar, 2) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(dst_tname)) | |
| -- Set source pointer | |
| if e.V[src].reg then | |
| e.reg_alloc(e.tmpvar, 3) -- Copy from original register | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, e.V[src].reg, 0, 0) | |
| else | |
| e.vreg(src, 3) | |
| e.reg_spill(src) -- Spill to avoid overwriting | |
| end | |
| -- Call probe read helper | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.probe_read) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| elseif e.V[src].const and e.V[src].const.__map then | |
| error('NYI: ffi.copy(dst, src) - src is backed by BPF map') | |
| elseif e.V[src].const and e.V[src].const.__dissector then | |
| error('NYI: ffi.copy(dst, src) - src is backed by socket buffer') | |
| else | |
| -- TODO: identify cheap register move | |
| -- TODO: identify copy to/from stack | |
| error('NYI: ffi.copy(dst, src) - src is neither BPF map/socket buffer or probe') | |
| end | |
| end | |
| -- print(format, ...) builtin changes semantics from Lua print(...) | |
| -- the first parameter has to be format and only reduced set of conversion specificers | |
| -- is allowed: %d %u %x %ld %lu %lx %lld %llu %llx %p %s | |
| builtins[print] = function (e, ret, fmt, a1, a2, a3) | |
| -- Load format string and length | |
| e.reg_alloc(e.V[e.tmpvar], 1) | |
| e.reg_alloc(e.V[e.tmpvar+1], 1) | |
| if type(e.V[fmt].const) == 'string' then | |
| local src = e.V[fmt].const | |
| local len = #src + 1 | |
| local dst = e.valloc(len, src) | |
| -- TODO: this is materialize step | |
| e.V[fmt].const = {__base=dst} | |
| e.V[fmt].type = ffi.typeof('char ['..len..']') | |
| elseif e.V[fmt].const.__base then -- luacheck: ignore | |
| -- NOP | |
| else error('NYI: print(fmt, ...) - format variable is not literal/stack memory') end | |
| -- Prepare helper call | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0) | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[fmt].const.__base) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(e.V[fmt].type)) | |
| if a1 then | |
| local args = {a1, a2, a3} | |
| assert(#args <= 3, 'print(fmt, ...) - maximum of 3 arguments supported') | |
| for i, arg in ipairs(args) do | |
| e.vcopy(e.tmpvar, arg) -- Copy variable | |
| e.vreg(e.tmpvar, 3+i-1) -- Materialize it in arg register | |
| end | |
| end | |
| -- Call helper | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) -- Return is integer | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.trace_printk) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| -- Implements bpf_perf_event_output(ctx, map, flags, var, vlen) on perf event map | |
| local function perf_submit(e, dst, map_var, src) | |
| -- Set R2 = map fd (indirect load) | |
| local map = e.V[map_var].const | |
| e.vcopy(e.tmpvar, map_var) | |
| e.vreg(e.tmpvar, 2, true, ffi.typeof('uint64_t')) | |
| e.LD_IMM_X(2, BPF.PSEUDO_MAP_FD, map.fd, ffi.sizeof('uint64_t')) | |
| -- Set R1 = ctx | |
| e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy | |
| -- Set R3 = flags | |
| e.vset(e.tmpvar, nil, 0) -- BPF_F_CURRENT_CPU | |
| e.vreg(e.tmpvar, 3, false, ffi.typeof('uint64_t')) | |
| -- Set R4 = pointer to src on stack | |
| assert(e.V[src].const.__base, 'NYI: submit(map, var) - variable is not on stack') | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 4, 10, 0, 0) | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 4, 0, 0, -e.V[src].const.__base) | |
| -- Set R5 = src length | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 5, 0, 0, ffi.sizeof(e.V[src].type)) | |
| -- Set R0 = ret and call | |
| e.vset(dst) | |
| e.vreg(dst, 0, true, ffi.typeof('int32_t')) -- Return is integer | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.perf_event_output) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| -- Implements bpf_skb_load_bytes(ctx, off, var, vlen) on skb->data | |
| local function load_bytes(e, dst, off, var) | |
| -- Set R2 = offset | |
| e.vset(e.tmpvar, nil, off) | |
| e.vreg(e.tmpvar, 2, false, ffi.typeof('uint64_t')) | |
| -- Set R1 = ctx | |
| e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy | |
| -- Set R3 = pointer to var on stack | |
| assert(e.V[var].const.__base, 'NYI: load_bytes(off, var, len) - variable is not on stack') | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 3, 10, 0, 0) | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 3, 0, 0, -e.V[var].const.__base) | |
| -- Set R4 = var length | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 4, 0, 0, ffi.sizeof(e.V[var].type)) | |
| -- Set R0 = ret and call | |
| e.vset(dst) | |
| e.vreg(dst, 0, true, ffi.typeof('int32_t')) -- Return is integer | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.skb_load_bytes) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| -- Implements bpf_get_stack_id() | |
| local function stack_id(e, ret, map_var, key) | |
| -- Set R2 = map fd (indirect load) | |
| local map = e.V[map_var].const | |
| e.vcopy(e.tmpvar, map_var) | |
| e.vreg(e.tmpvar, 2, true, ffi.typeof('uint64_t')) | |
| e.LD_IMM_X(2, BPF.PSEUDO_MAP_FD, map.fd, ffi.sizeof('uint64_t')) | |
| -- Set R1 = ctx | |
| e.reg_alloc(e.tmpvar, 1) -- Spill anything in R1 (unnamed tmp variable) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 6, 0, 0) -- CTX is always in R6, copy | |
| -- Load flags in R2 (immediate value or key) | |
| local imm = e.V[key].const | |
| assert(tonumber(imm), 'NYI: stack_id(map, var), var must be constant number') | |
| e.reg_alloc(e.tmpvar, 3) -- Spill anything in R2 (unnamed tmp variable) | |
| e.LD_IMM_X(3, 0, imm, 8) | |
| -- Return R0 as signed integer | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.get_stackid) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| -- table.insert(table, value) keeps semantics with the exception of BPF maps | |
| -- map `perf_event` -> submit inserted value | |
| builtins[table.insert] = function (e, dst, map_var, value) | |
| assert(e.V[map_var].const.__map, 'NYI: table.insert() supported only on BPF maps') | |
| return perf_submit(e, dst, map_var, value) | |
| end | |
| -- bpf_get_current_comm(buffer) - write current process name to byte buffer | |
| local function comm() error('NYI') end | |
| builtins[comm] = function (e, ret, dst) | |
| -- Set R1 = buffer | |
| assert(e.V[dst].const.__base, 'NYI: comm(buffer) - buffer variable is not on stack') | |
| e.reg_alloc(e.tmpvar, 1) -- Spill | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.X, 1, 10, 0, 0) | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, 1, 0, 0, -e.V[dst].const.__base) | |
| -- Set R2 = length | |
| e.reg_alloc(e.tmpvar, 2) -- Spill | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, 2, 0, 0, ffi.sizeof(e.V[dst].type)) | |
| -- Return is integer | |
| e.vset(ret) | |
| e.vreg(ret, 0, true, ffi.typeof('int32_t')) | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, HELPER.get_current_comm) | |
| e.V[e.tmpvar].reg = nil -- Free temporary registers | |
| end | |
| -- Math library built-ins | |
| math.log2 = function () error('NYI') end | |
| builtins[math.log2] = function (e, dst, x) | |
| -- Classic integer bits subdivison algorithm to find the position | |
| -- of the highest bit set, adapted for BPF bytecode-friendly operations. | |
| -- https://graphics.stanford.edu/~seander/bithacks.html | |
| -- r = 0 | |
| local r = e.vreg(dst, nil, true) | |
| e.emit(BPF.ALU64 + BPF.MOV + BPF.K, r, 0, 0, 0) | |
| -- v = x | |
| e.vcopy(e.tmpvar, x) | |
| local v = e.vreg(e.tmpvar, 2) | |
| if cdef.isptr(e.V[x].const) then -- No pointer arithmetics, dereference | |
| e.vderef(v, v, {const = {__dissector=ffi.typeof('uint64_t')}}) | |
| end | |
| -- Invert value to invert all tests, otherwise we would need and+jnz | |
| e.emit(BPF.ALU64 + BPF.NEG + BPF.K, v, 0, 0, 0) -- v = ~v | |
| -- Unrolled test cases, converted masking to arithmetic as we don't have "if !(a & b)" | |
| -- As we're testing inverted value, we have to use arithmetic shift to copy MSB | |
| for i=4,0,-1 do | |
| local k = bit.lshift(1, i) | |
| e.emit(BPF.JMP + BPF.JGT + BPF.K, v, 0, 2, bit.bnot(bit.lshift(1, k))) -- if !upper_half(x) | |
| e.emit(BPF.ALU64 + BPF.ARSH + BPF.K, v, 0, 0, k) -- v >>= k | |
| e.emit(BPF.ALU64 + BPF.OR + BPF.K, r, 0, 0, k) -- r |= k | |
| end | |
| -- No longer constant, cleanup tmpvars | |
| e.V[dst].const = nil | |
| e.V[e.tmpvar].reg = nil | |
| end | |
| builtins[math.log10] = function (e, dst, x) | |
| -- Compute log2(x) and transform | |
| builtins[math.log2](e, dst, x) | |
| -- Relationship: log10(v) = log2(v) / log2(10) | |
| local r = e.V[dst].reg | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, r, 0, 0, 1) -- Compensate round-down | |
| e.emit(BPF.ALU64 + BPF.MUL + BPF.K, r, 0, 0, 1233) -- log2(10) ~ 1233>>12 | |
| e.emit(BPF.ALU64 + BPF.RSH + BPF.K, r, 0, 0, 12) | |
| end | |
| builtins[math.log] = function (e, dst, x) | |
| -- Compute log2(x) and transform | |
| builtins[math.log2](e, dst, x) | |
| -- Relationship: ln(v) = log2(v) / log2(e) | |
| local r = e.V[dst].reg | |
| e.emit(BPF.ALU64 + BPF.ADD + BPF.K, r, 0, 0, 1) -- Compensate round-down | |
| e.emit(BPF.ALU64 + BPF.MUL + BPF.K, r, 0, 0, 2839) -- log2(e) ~ 2839>>12 | |
| e.emit(BPF.ALU64 + BPF.RSH + BPF.K, r, 0, 0, 12) | |
| end | |
| -- Call-type helpers | |
| local function call_helper(e, dst, h, vtype) | |
| e.vset(dst) | |
| e.vreg(dst, 0, true, vtype or ffi.typeof('uint64_t')) | |
| e.emit(BPF.JMP + BPF.CALL, 0, 0, 0, h) | |
| e.V[dst].const = nil -- Target is not a function anymore | |
| end | |
| local function cpu() error('NYI') end | |
| local function rand() error('NYI') end | |
| local function time() error('NYI') end | |
| local function pid_tgid() error('NYI') end | |
| local function uid_gid() error('NYI') end | |
| -- Export helpers and builtin variants | |
| builtins.cpu = cpu | |
| builtins.time = time | |
| builtins.pid_tgid = pid_tgid | |
| builtins.uid_gid = uid_gid | |
| builtins.comm = comm | |
| builtins.perf_submit = perf_submit | |
| builtins.stack_id = stack_id | |
| builtins.load_bytes = load_bytes | |
| builtins[cpu] = function (e, dst) return call_helper(e, dst, HELPER.get_smp_processor_id) end | |
| builtins[rand] = function (e, dst) return call_helper(e, dst, HELPER.get_prandom_u32, ffi.typeof('uint32_t')) end | |
| builtins[time] = function (e, dst) return call_helper(e, dst, HELPER.ktime_get_ns) end | |
| builtins[pid_tgid] = function (e, dst) return call_helper(e, dst, HELPER.get_current_pid_tgid) end | |
| builtins[uid_gid] = function (e, dst) return call_helper(e, dst, HELPER.get_current_uid_gid) end | |
| builtins[perf_submit] = function (e, dst, map, value) return perf_submit(e, dst, map, value) end | |
| builtins[stack_id] = function (e, dst, map, key) return stack_id(e, dst, map, key) end | |
| builtins[load_bytes] = function (e, dst, off, var, len) return load_bytes(e, dst, off, var, len) end | |
| return builtins |