whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package asm
|
||||
|
||||
//go:generate stringer -output alu_string.go -type=Source,Endianness,ALUOp
|
||||
|
||||
// Source of ALU / ALU64 / Branch operations
|
||||
//
|
||||
// msb lsb
|
||||
// +----+-+---+
|
||||
// |op |S|cls|
|
||||
// +----+-+---+
|
||||
type Source uint8
|
||||
|
||||
const sourceMask OpCode = 0x08
|
||||
|
||||
// Source bitmask
|
||||
const (
|
||||
// InvalidSource is returned by getters when invoked
|
||||
// on non ALU / branch OpCodes.
|
||||
InvalidSource Source = 0xff
|
||||
// ImmSource src is from constant
|
||||
ImmSource Source = 0x00
|
||||
// RegSource src is from register
|
||||
RegSource Source = 0x08
|
||||
)
|
||||
|
||||
// The Endianness of a byte swap instruction.
|
||||
type Endianness uint8
|
||||
|
||||
const endianMask = sourceMask
|
||||
|
||||
// Endian flags
|
||||
const (
|
||||
InvalidEndian Endianness = 0xff
|
||||
// Convert to little endian
|
||||
LE Endianness = 0x00
|
||||
// Convert to big endian
|
||||
BE Endianness = 0x08
|
||||
)
|
||||
|
||||
// ALUOp are ALU / ALU64 operations
|
||||
//
|
||||
// msb lsb
|
||||
// +----+-+---+
|
||||
// |OP |s|cls|
|
||||
// +----+-+---+
|
||||
type ALUOp uint8
|
||||
|
||||
const aluMask OpCode = 0xf0
|
||||
|
||||
const (
|
||||
// InvalidALUOp is returned by getters when invoked
|
||||
// on non ALU OpCodes
|
||||
InvalidALUOp ALUOp = 0xff
|
||||
// Add - addition
|
||||
Add ALUOp = 0x00
|
||||
// Sub - subtraction
|
||||
Sub ALUOp = 0x10
|
||||
// Mul - multiplication
|
||||
Mul ALUOp = 0x20
|
||||
// Div - division
|
||||
Div ALUOp = 0x30
|
||||
// Or - bitwise or
|
||||
Or ALUOp = 0x40
|
||||
// And - bitwise and
|
||||
And ALUOp = 0x50
|
||||
// LSh - bitwise shift left
|
||||
LSh ALUOp = 0x60
|
||||
// RSh - bitwise shift right
|
||||
RSh ALUOp = 0x70
|
||||
// Neg - sign/unsign signing bit
|
||||
Neg ALUOp = 0x80
|
||||
// Mod - modulo
|
||||
Mod ALUOp = 0x90
|
||||
// Xor - bitwise xor
|
||||
Xor ALUOp = 0xa0
|
||||
// Mov - move value from one place to another
|
||||
Mov ALUOp = 0xb0
|
||||
// ArSh - arithmatic shift
|
||||
ArSh ALUOp = 0xc0
|
||||
// Swap - endian conversions
|
||||
Swap ALUOp = 0xd0
|
||||
)
|
||||
|
||||
// HostTo converts from host to another endianness.
|
||||
func HostTo(endian Endianness, dst Register, size Size) Instruction {
|
||||
var imm int64
|
||||
switch size {
|
||||
case Half:
|
||||
imm = 16
|
||||
case Word:
|
||||
imm = 32
|
||||
case DWord:
|
||||
imm = 64
|
||||
default:
|
||||
return Instruction{OpCode: InvalidOpCode}
|
||||
}
|
||||
|
||||
return Instruction{
|
||||
OpCode: OpCode(ALUClass).SetALUOp(Swap).SetSource(Source(endian)),
|
||||
Dst: dst,
|
||||
Constant: imm,
|
||||
}
|
||||
}
|
||||
|
||||
// Op returns the OpCode for an ALU operation with a given source.
|
||||
func (op ALUOp) Op(source Source) OpCode {
|
||||
return OpCode(ALU64Class).SetALUOp(op).SetSource(source)
|
||||
}
|
||||
|
||||
// Reg emits `dst (op) src`.
|
||||
func (op ALUOp) Reg(dst, src Register) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.Op(RegSource),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
}
|
||||
}
|
||||
|
||||
// Imm emits `dst (op) value`.
|
||||
func (op ALUOp) Imm(dst Register, value int32) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.Op(ImmSource),
|
||||
Dst: dst,
|
||||
Constant: int64(value),
|
||||
}
|
||||
}
|
||||
|
||||
// Op32 returns the OpCode for a 32-bit ALU operation with a given source.
|
||||
func (op ALUOp) Op32(source Source) OpCode {
|
||||
return OpCode(ALUClass).SetALUOp(op).SetSource(source)
|
||||
}
|
||||
|
||||
// Reg32 emits `dst (op) src`, zeroing the upper 32 bit of dst.
|
||||
func (op ALUOp) Reg32(dst, src Register) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.Op32(RegSource),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
}
|
||||
}
|
||||
|
||||
// Imm32 emits `dst (op) value`, zeroing the upper 32 bit of dst.
|
||||
func (op ALUOp) Imm32(dst Register, value int32) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.Op32(ImmSource),
|
||||
Dst: dst,
|
||||
Constant: int64(value),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Code generated by "stringer -output alu_string.go -type=Source,Endianness,ALUOp"; DO NOT EDIT.
|
||||
|
||||
package asm
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidSource-255]
|
||||
_ = x[ImmSource-0]
|
||||
_ = x[RegSource-8]
|
||||
}
|
||||
|
||||
const (
|
||||
_Source_name_0 = "ImmSource"
|
||||
_Source_name_1 = "RegSource"
|
||||
_Source_name_2 = "InvalidSource"
|
||||
)
|
||||
|
||||
func (i Source) String() string {
|
||||
switch {
|
||||
case i == 0:
|
||||
return _Source_name_0
|
||||
case i == 8:
|
||||
return _Source_name_1
|
||||
case i == 255:
|
||||
return _Source_name_2
|
||||
default:
|
||||
return "Source(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidEndian-255]
|
||||
_ = x[LE-0]
|
||||
_ = x[BE-8]
|
||||
}
|
||||
|
||||
const (
|
||||
_Endianness_name_0 = "LE"
|
||||
_Endianness_name_1 = "BE"
|
||||
_Endianness_name_2 = "InvalidEndian"
|
||||
)
|
||||
|
||||
func (i Endianness) String() string {
|
||||
switch {
|
||||
case i == 0:
|
||||
return _Endianness_name_0
|
||||
case i == 8:
|
||||
return _Endianness_name_1
|
||||
case i == 255:
|
||||
return _Endianness_name_2
|
||||
default:
|
||||
return "Endianness(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidALUOp-255]
|
||||
_ = x[Add-0]
|
||||
_ = x[Sub-16]
|
||||
_ = x[Mul-32]
|
||||
_ = x[Div-48]
|
||||
_ = x[Or-64]
|
||||
_ = x[And-80]
|
||||
_ = x[LSh-96]
|
||||
_ = x[RSh-112]
|
||||
_ = x[Neg-128]
|
||||
_ = x[Mod-144]
|
||||
_ = x[Xor-160]
|
||||
_ = x[Mov-176]
|
||||
_ = x[ArSh-192]
|
||||
_ = x[Swap-208]
|
||||
}
|
||||
|
||||
const _ALUOp_name = "AddSubMulDivOrAndLShRShNegModXorMovArShSwapInvalidALUOp"
|
||||
|
||||
var _ALUOp_map = map[ALUOp]string{
|
||||
0: _ALUOp_name[0:3],
|
||||
16: _ALUOp_name[3:6],
|
||||
32: _ALUOp_name[6:9],
|
||||
48: _ALUOp_name[9:12],
|
||||
64: _ALUOp_name[12:14],
|
||||
80: _ALUOp_name[14:17],
|
||||
96: _ALUOp_name[17:20],
|
||||
112: _ALUOp_name[20:23],
|
||||
128: _ALUOp_name[23:26],
|
||||
144: _ALUOp_name[26:29],
|
||||
160: _ALUOp_name[29:32],
|
||||
176: _ALUOp_name[32:35],
|
||||
192: _ALUOp_name[35:39],
|
||||
208: _ALUOp_name[39:43],
|
||||
255: _ALUOp_name[43:55],
|
||||
}
|
||||
|
||||
func (i ALUOp) String() string {
|
||||
if str, ok := _ALUOp_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "ALUOp(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package asm is an assembler for eBPF bytecode.
|
||||
package asm
|
||||
@@ -0,0 +1,46 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDSL(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
have Instruction
|
||||
want Instruction
|
||||
}{
|
||||
{"Call", FnMapLookupElem.Call(), Instruction{OpCode: 0x85, Constant: 1}},
|
||||
{"Exit", Return(), Instruction{OpCode: 0x95}},
|
||||
{"LoadAbs", LoadAbs(2, Byte), Instruction{OpCode: 0x30, Constant: 2}},
|
||||
{"Store", StoreMem(RFP, -4, R0, Word), Instruction{
|
||||
OpCode: 0x63,
|
||||
Dst: RFP,
|
||||
Src: R0,
|
||||
Offset: -4,
|
||||
}},
|
||||
{"Add.Imm", Add.Imm(R1, 22), Instruction{OpCode: 0x07, Dst: R1, Constant: 22}},
|
||||
{"Add.Reg", Add.Reg(R1, R2), Instruction{OpCode: 0x0f, Dst: R1, Src: R2}},
|
||||
{"Add.Imm32", Add.Imm32(R1, 22), Instruction{
|
||||
OpCode: 0x04, Dst: R1, Constant: 22,
|
||||
}},
|
||||
{"JSGT.Imm", JSGT.Imm(R1, 4, "foo"), Instruction{
|
||||
OpCode: 0x65, Dst: R1, Constant: 4, Offset: -1,
|
||||
}.WithReference("foo")},
|
||||
{"JSGT.Imm32", JSGT.Imm32(R1, -2, "foo"), Instruction{
|
||||
OpCode: 0x66, Dst: R1, Constant: -2, Offset: -1,
|
||||
}.WithReference("foo")},
|
||||
{"JSLT.Reg", JSLT.Reg(R1, R2, "foo"), Instruction{
|
||||
OpCode: 0xcd, Dst: R1, Src: R2, Offset: -1,
|
||||
}.WithReference("foo")},
|
||||
{"JSLT.Reg32", JSLT.Reg32(R1, R3, "foo"), Instruction{
|
||||
OpCode: 0xce, Dst: R1, Src: R3, Offset: -1,
|
||||
}.WithReference("foo")},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
if !tc.have.equal(tc.want) {
|
||||
t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package asm
|
||||
|
||||
//go:generate stringer -output func_string.go -type=BuiltinFunc
|
||||
|
||||
// BuiltinFunc is a built-in eBPF function.
|
||||
type BuiltinFunc int32
|
||||
|
||||
func (_ BuiltinFunc) Max() BuiltinFunc {
|
||||
return maxBuiltinFunc - 1
|
||||
}
|
||||
|
||||
// eBPF built-in functions
|
||||
//
|
||||
// You can regenerate this list using the following gawk script:
|
||||
//
|
||||
// /FN\(.+\),/ {
|
||||
// match($1, /\(([a-z_0-9]+),/, r)
|
||||
// split(r[1], p, "_")
|
||||
// printf "Fn"
|
||||
// for (i in p) {
|
||||
// printf "%s%s", toupper(substr(p[i], 1, 1)), substr(p[i], 2)
|
||||
// }
|
||||
// print ""
|
||||
// }
|
||||
//
|
||||
// The script expects include/uapi/linux/bpf.h as it's input.
|
||||
const (
|
||||
FnUnspec BuiltinFunc = iota
|
||||
FnMapLookupElem
|
||||
FnMapUpdateElem
|
||||
FnMapDeleteElem
|
||||
FnProbeRead
|
||||
FnKtimeGetNs
|
||||
FnTracePrintk
|
||||
FnGetPrandomU32
|
||||
FnGetSmpProcessorId
|
||||
FnSkbStoreBytes
|
||||
FnL3CsumReplace
|
||||
FnL4CsumReplace
|
||||
FnTailCall
|
||||
FnCloneRedirect
|
||||
FnGetCurrentPidTgid
|
||||
FnGetCurrentUidGid
|
||||
FnGetCurrentComm
|
||||
FnGetCgroupClassid
|
||||
FnSkbVlanPush
|
||||
FnSkbVlanPop
|
||||
FnSkbGetTunnelKey
|
||||
FnSkbSetTunnelKey
|
||||
FnPerfEventRead
|
||||
FnRedirect
|
||||
FnGetRouteRealm
|
||||
FnPerfEventOutput
|
||||
FnSkbLoadBytes
|
||||
FnGetStackid
|
||||
FnCsumDiff
|
||||
FnSkbGetTunnelOpt
|
||||
FnSkbSetTunnelOpt
|
||||
FnSkbChangeProto
|
||||
FnSkbChangeType
|
||||
FnSkbUnderCgroup
|
||||
FnGetHashRecalc
|
||||
FnGetCurrentTask
|
||||
FnProbeWriteUser
|
||||
FnCurrentTaskUnderCgroup
|
||||
FnSkbChangeTail
|
||||
FnSkbPullData
|
||||
FnCsumUpdate
|
||||
FnSetHashInvalid
|
||||
FnGetNumaNodeId
|
||||
FnSkbChangeHead
|
||||
FnXdpAdjustHead
|
||||
FnProbeReadStr
|
||||
FnGetSocketCookie
|
||||
FnGetSocketUid
|
||||
FnSetHash
|
||||
FnSetsockopt
|
||||
FnSkbAdjustRoom
|
||||
FnRedirectMap
|
||||
FnSkRedirectMap
|
||||
FnSockMapUpdate
|
||||
FnXdpAdjustMeta
|
||||
FnPerfEventReadValue
|
||||
FnPerfProgReadValue
|
||||
FnGetsockopt
|
||||
FnOverrideReturn
|
||||
FnSockOpsCbFlagsSet
|
||||
FnMsgRedirectMap
|
||||
FnMsgApplyBytes
|
||||
FnMsgCorkBytes
|
||||
FnMsgPullData
|
||||
FnBind
|
||||
FnXdpAdjustTail
|
||||
FnSkbGetXfrmState
|
||||
FnGetStack
|
||||
FnSkbLoadBytesRelative
|
||||
FnFibLookup
|
||||
FnSockHashUpdate
|
||||
FnMsgRedirectHash
|
||||
FnSkRedirectHash
|
||||
FnLwtPushEncap
|
||||
FnLwtSeg6StoreBytes
|
||||
FnLwtSeg6AdjustSrh
|
||||
FnLwtSeg6Action
|
||||
FnRcRepeat
|
||||
FnRcKeydown
|
||||
FnSkbCgroupId
|
||||
FnGetCurrentCgroupId
|
||||
FnGetLocalStorage
|
||||
FnSkSelectReuseport
|
||||
FnSkbAncestorCgroupId
|
||||
FnSkLookupTcp
|
||||
FnSkLookupUdp
|
||||
FnSkRelease
|
||||
FnMapPushElem
|
||||
FnMapPopElem
|
||||
FnMapPeekElem
|
||||
FnMsgPushData
|
||||
FnMsgPopData
|
||||
FnRcPointerRel
|
||||
FnSpinLock
|
||||
FnSpinUnlock
|
||||
FnSkFullsock
|
||||
FnTcpSock
|
||||
FnSkbEcnSetCe
|
||||
FnGetListenerSock
|
||||
FnSkcLookupTcp
|
||||
FnTcpCheckSyncookie
|
||||
FnSysctlGetName
|
||||
FnSysctlGetCurrentValue
|
||||
FnSysctlGetNewValue
|
||||
FnSysctlSetNewValue
|
||||
FnStrtol
|
||||
FnStrtoul
|
||||
FnSkStorageGet
|
||||
FnSkStorageDelete
|
||||
FnSendSignal
|
||||
FnTcpGenSyncookie
|
||||
FnSkbOutput
|
||||
FnProbeReadUser
|
||||
FnProbeReadKernel
|
||||
FnProbeReadUserStr
|
||||
FnProbeReadKernelStr
|
||||
FnTcpSendAck
|
||||
FnSendSignalThread
|
||||
FnJiffies64
|
||||
FnReadBranchRecords
|
||||
FnGetNsCurrentPidTgid
|
||||
FnXdpOutput
|
||||
FnGetNetnsCookie
|
||||
FnGetCurrentAncestorCgroupId
|
||||
FnSkAssign
|
||||
FnKtimeGetBootNs
|
||||
FnSeqPrintf
|
||||
FnSeqWrite
|
||||
FnSkCgroupId
|
||||
FnSkAncestorCgroupId
|
||||
FnRingbufOutput
|
||||
FnRingbufReserve
|
||||
FnRingbufSubmit
|
||||
FnRingbufDiscard
|
||||
FnRingbufQuery
|
||||
FnCsumLevel
|
||||
FnSkcToTcp6Sock
|
||||
FnSkcToTcpSock
|
||||
FnSkcToTcpTimewaitSock
|
||||
FnSkcToTcpRequestSock
|
||||
FnSkcToUdp6Sock
|
||||
FnGetTaskStack
|
||||
FnLoadHdrOpt
|
||||
FnStoreHdrOpt
|
||||
FnReserveHdrOpt
|
||||
FnInodeStorageGet
|
||||
FnInodeStorageDelete
|
||||
FnDPath
|
||||
FnCopyFromUser
|
||||
FnSnprintfBtf
|
||||
FnSeqPrintfBtf
|
||||
FnSkbCgroupClassid
|
||||
FnRedirectNeigh
|
||||
FnPerCpuPtr
|
||||
FnThisCpuPtr
|
||||
FnRedirectPeer
|
||||
FnTaskStorageGet
|
||||
FnTaskStorageDelete
|
||||
FnGetCurrentTaskBtf
|
||||
FnBprmOptsSet
|
||||
FnKtimeGetCoarseNs
|
||||
FnImaInodeHash
|
||||
FnSockFromFile
|
||||
FnCheckMtu
|
||||
FnForEachMapElem
|
||||
FnSnprintf
|
||||
FnSysBpf
|
||||
FnBtfFindByNameKind
|
||||
FnSysClose
|
||||
FnTimerInit
|
||||
FnTimerSetCallback
|
||||
FnTimerStart
|
||||
FnTimerCancel
|
||||
FnGetFuncIp
|
||||
FnGetAttachCookie
|
||||
FnTaskPtRegs
|
||||
FnGetBranchSnapshot
|
||||
FnTraceVprintk
|
||||
FnSkcToUnixSock
|
||||
FnKallsymsLookupName
|
||||
FnFindVma
|
||||
FnLoop
|
||||
FnStrncmp
|
||||
FnGetFuncArg
|
||||
FnGetFuncRet
|
||||
FnGetFuncArgCnt
|
||||
FnGetRetval
|
||||
FnSetRetval
|
||||
FnXdpGetBuffLen
|
||||
FnXdpLoadBytes
|
||||
FnXdpStoreBytes
|
||||
FnCopyFromUserTask
|
||||
FnSkbSetTstamp
|
||||
FnImaFileHash
|
||||
FnKptrXchg
|
||||
FnMapLookupPercpuElem
|
||||
FnSkcToMptcpSock
|
||||
FnDynptrFromMem
|
||||
FnRingbufReserveDynptr
|
||||
FnRingbufSubmitDynptr
|
||||
FnRingbufDiscardDynptr
|
||||
FnDynptrRead
|
||||
FnDynptrWrite
|
||||
FnDynptrData
|
||||
FnTcpRawGenSyncookieIpv4
|
||||
FnTcpRawGenSyncookieIpv6
|
||||
FnTcpRawCheckSyncookieIpv4
|
||||
FnTcpRawCheckSyncookieIpv6
|
||||
FnKtimeGetTaiNs
|
||||
FnUserRingbufDrain
|
||||
FnCgrpStorageGet
|
||||
FnCgrpStorageDelete
|
||||
|
||||
maxBuiltinFunc
|
||||
)
|
||||
|
||||
// Call emits a function call.
|
||||
func (fn BuiltinFunc) Call() Instruction {
|
||||
return Instruction{
|
||||
OpCode: OpCode(JumpClass).SetJumpOp(Call),
|
||||
Constant: int64(fn),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Code generated by "stringer -output func_string.go -type=BuiltinFunc"; DO NOT EDIT.
|
||||
|
||||
package asm
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[FnUnspec-0]
|
||||
_ = x[FnMapLookupElem-1]
|
||||
_ = x[FnMapUpdateElem-2]
|
||||
_ = x[FnMapDeleteElem-3]
|
||||
_ = x[FnProbeRead-4]
|
||||
_ = x[FnKtimeGetNs-5]
|
||||
_ = x[FnTracePrintk-6]
|
||||
_ = x[FnGetPrandomU32-7]
|
||||
_ = x[FnGetSmpProcessorId-8]
|
||||
_ = x[FnSkbStoreBytes-9]
|
||||
_ = x[FnL3CsumReplace-10]
|
||||
_ = x[FnL4CsumReplace-11]
|
||||
_ = x[FnTailCall-12]
|
||||
_ = x[FnCloneRedirect-13]
|
||||
_ = x[FnGetCurrentPidTgid-14]
|
||||
_ = x[FnGetCurrentUidGid-15]
|
||||
_ = x[FnGetCurrentComm-16]
|
||||
_ = x[FnGetCgroupClassid-17]
|
||||
_ = x[FnSkbVlanPush-18]
|
||||
_ = x[FnSkbVlanPop-19]
|
||||
_ = x[FnSkbGetTunnelKey-20]
|
||||
_ = x[FnSkbSetTunnelKey-21]
|
||||
_ = x[FnPerfEventRead-22]
|
||||
_ = x[FnRedirect-23]
|
||||
_ = x[FnGetRouteRealm-24]
|
||||
_ = x[FnPerfEventOutput-25]
|
||||
_ = x[FnSkbLoadBytes-26]
|
||||
_ = x[FnGetStackid-27]
|
||||
_ = x[FnCsumDiff-28]
|
||||
_ = x[FnSkbGetTunnelOpt-29]
|
||||
_ = x[FnSkbSetTunnelOpt-30]
|
||||
_ = x[FnSkbChangeProto-31]
|
||||
_ = x[FnSkbChangeType-32]
|
||||
_ = x[FnSkbUnderCgroup-33]
|
||||
_ = x[FnGetHashRecalc-34]
|
||||
_ = x[FnGetCurrentTask-35]
|
||||
_ = x[FnProbeWriteUser-36]
|
||||
_ = x[FnCurrentTaskUnderCgroup-37]
|
||||
_ = x[FnSkbChangeTail-38]
|
||||
_ = x[FnSkbPullData-39]
|
||||
_ = x[FnCsumUpdate-40]
|
||||
_ = x[FnSetHashInvalid-41]
|
||||
_ = x[FnGetNumaNodeId-42]
|
||||
_ = x[FnSkbChangeHead-43]
|
||||
_ = x[FnXdpAdjustHead-44]
|
||||
_ = x[FnProbeReadStr-45]
|
||||
_ = x[FnGetSocketCookie-46]
|
||||
_ = x[FnGetSocketUid-47]
|
||||
_ = x[FnSetHash-48]
|
||||
_ = x[FnSetsockopt-49]
|
||||
_ = x[FnSkbAdjustRoom-50]
|
||||
_ = x[FnRedirectMap-51]
|
||||
_ = x[FnSkRedirectMap-52]
|
||||
_ = x[FnSockMapUpdate-53]
|
||||
_ = x[FnXdpAdjustMeta-54]
|
||||
_ = x[FnPerfEventReadValue-55]
|
||||
_ = x[FnPerfProgReadValue-56]
|
||||
_ = x[FnGetsockopt-57]
|
||||
_ = x[FnOverrideReturn-58]
|
||||
_ = x[FnSockOpsCbFlagsSet-59]
|
||||
_ = x[FnMsgRedirectMap-60]
|
||||
_ = x[FnMsgApplyBytes-61]
|
||||
_ = x[FnMsgCorkBytes-62]
|
||||
_ = x[FnMsgPullData-63]
|
||||
_ = x[FnBind-64]
|
||||
_ = x[FnXdpAdjustTail-65]
|
||||
_ = x[FnSkbGetXfrmState-66]
|
||||
_ = x[FnGetStack-67]
|
||||
_ = x[FnSkbLoadBytesRelative-68]
|
||||
_ = x[FnFibLookup-69]
|
||||
_ = x[FnSockHashUpdate-70]
|
||||
_ = x[FnMsgRedirectHash-71]
|
||||
_ = x[FnSkRedirectHash-72]
|
||||
_ = x[FnLwtPushEncap-73]
|
||||
_ = x[FnLwtSeg6StoreBytes-74]
|
||||
_ = x[FnLwtSeg6AdjustSrh-75]
|
||||
_ = x[FnLwtSeg6Action-76]
|
||||
_ = x[FnRcRepeat-77]
|
||||
_ = x[FnRcKeydown-78]
|
||||
_ = x[FnSkbCgroupId-79]
|
||||
_ = x[FnGetCurrentCgroupId-80]
|
||||
_ = x[FnGetLocalStorage-81]
|
||||
_ = x[FnSkSelectReuseport-82]
|
||||
_ = x[FnSkbAncestorCgroupId-83]
|
||||
_ = x[FnSkLookupTcp-84]
|
||||
_ = x[FnSkLookupUdp-85]
|
||||
_ = x[FnSkRelease-86]
|
||||
_ = x[FnMapPushElem-87]
|
||||
_ = x[FnMapPopElem-88]
|
||||
_ = x[FnMapPeekElem-89]
|
||||
_ = x[FnMsgPushData-90]
|
||||
_ = x[FnMsgPopData-91]
|
||||
_ = x[FnRcPointerRel-92]
|
||||
_ = x[FnSpinLock-93]
|
||||
_ = x[FnSpinUnlock-94]
|
||||
_ = x[FnSkFullsock-95]
|
||||
_ = x[FnTcpSock-96]
|
||||
_ = x[FnSkbEcnSetCe-97]
|
||||
_ = x[FnGetListenerSock-98]
|
||||
_ = x[FnSkcLookupTcp-99]
|
||||
_ = x[FnTcpCheckSyncookie-100]
|
||||
_ = x[FnSysctlGetName-101]
|
||||
_ = x[FnSysctlGetCurrentValue-102]
|
||||
_ = x[FnSysctlGetNewValue-103]
|
||||
_ = x[FnSysctlSetNewValue-104]
|
||||
_ = x[FnStrtol-105]
|
||||
_ = x[FnStrtoul-106]
|
||||
_ = x[FnSkStorageGet-107]
|
||||
_ = x[FnSkStorageDelete-108]
|
||||
_ = x[FnSendSignal-109]
|
||||
_ = x[FnTcpGenSyncookie-110]
|
||||
_ = x[FnSkbOutput-111]
|
||||
_ = x[FnProbeReadUser-112]
|
||||
_ = x[FnProbeReadKernel-113]
|
||||
_ = x[FnProbeReadUserStr-114]
|
||||
_ = x[FnProbeReadKernelStr-115]
|
||||
_ = x[FnTcpSendAck-116]
|
||||
_ = x[FnSendSignalThread-117]
|
||||
_ = x[FnJiffies64-118]
|
||||
_ = x[FnReadBranchRecords-119]
|
||||
_ = x[FnGetNsCurrentPidTgid-120]
|
||||
_ = x[FnXdpOutput-121]
|
||||
_ = x[FnGetNetnsCookie-122]
|
||||
_ = x[FnGetCurrentAncestorCgroupId-123]
|
||||
_ = x[FnSkAssign-124]
|
||||
_ = x[FnKtimeGetBootNs-125]
|
||||
_ = x[FnSeqPrintf-126]
|
||||
_ = x[FnSeqWrite-127]
|
||||
_ = x[FnSkCgroupId-128]
|
||||
_ = x[FnSkAncestorCgroupId-129]
|
||||
_ = x[FnRingbufOutput-130]
|
||||
_ = x[FnRingbufReserve-131]
|
||||
_ = x[FnRingbufSubmit-132]
|
||||
_ = x[FnRingbufDiscard-133]
|
||||
_ = x[FnRingbufQuery-134]
|
||||
_ = x[FnCsumLevel-135]
|
||||
_ = x[FnSkcToTcp6Sock-136]
|
||||
_ = x[FnSkcToTcpSock-137]
|
||||
_ = x[FnSkcToTcpTimewaitSock-138]
|
||||
_ = x[FnSkcToTcpRequestSock-139]
|
||||
_ = x[FnSkcToUdp6Sock-140]
|
||||
_ = x[FnGetTaskStack-141]
|
||||
_ = x[FnLoadHdrOpt-142]
|
||||
_ = x[FnStoreHdrOpt-143]
|
||||
_ = x[FnReserveHdrOpt-144]
|
||||
_ = x[FnInodeStorageGet-145]
|
||||
_ = x[FnInodeStorageDelete-146]
|
||||
_ = x[FnDPath-147]
|
||||
_ = x[FnCopyFromUser-148]
|
||||
_ = x[FnSnprintfBtf-149]
|
||||
_ = x[FnSeqPrintfBtf-150]
|
||||
_ = x[FnSkbCgroupClassid-151]
|
||||
_ = x[FnRedirectNeigh-152]
|
||||
_ = x[FnPerCpuPtr-153]
|
||||
_ = x[FnThisCpuPtr-154]
|
||||
_ = x[FnRedirectPeer-155]
|
||||
_ = x[FnTaskStorageGet-156]
|
||||
_ = x[FnTaskStorageDelete-157]
|
||||
_ = x[FnGetCurrentTaskBtf-158]
|
||||
_ = x[FnBprmOptsSet-159]
|
||||
_ = x[FnKtimeGetCoarseNs-160]
|
||||
_ = x[FnImaInodeHash-161]
|
||||
_ = x[FnSockFromFile-162]
|
||||
_ = x[FnCheckMtu-163]
|
||||
_ = x[FnForEachMapElem-164]
|
||||
_ = x[FnSnprintf-165]
|
||||
_ = x[FnSysBpf-166]
|
||||
_ = x[FnBtfFindByNameKind-167]
|
||||
_ = x[FnSysClose-168]
|
||||
_ = x[FnTimerInit-169]
|
||||
_ = x[FnTimerSetCallback-170]
|
||||
_ = x[FnTimerStart-171]
|
||||
_ = x[FnTimerCancel-172]
|
||||
_ = x[FnGetFuncIp-173]
|
||||
_ = x[FnGetAttachCookie-174]
|
||||
_ = x[FnTaskPtRegs-175]
|
||||
_ = x[FnGetBranchSnapshot-176]
|
||||
_ = x[FnTraceVprintk-177]
|
||||
_ = x[FnSkcToUnixSock-178]
|
||||
_ = x[FnKallsymsLookupName-179]
|
||||
_ = x[FnFindVma-180]
|
||||
_ = x[FnLoop-181]
|
||||
_ = x[FnStrncmp-182]
|
||||
_ = x[FnGetFuncArg-183]
|
||||
_ = x[FnGetFuncRet-184]
|
||||
_ = x[FnGetFuncArgCnt-185]
|
||||
_ = x[FnGetRetval-186]
|
||||
_ = x[FnSetRetval-187]
|
||||
_ = x[FnXdpGetBuffLen-188]
|
||||
_ = x[FnXdpLoadBytes-189]
|
||||
_ = x[FnXdpStoreBytes-190]
|
||||
_ = x[FnCopyFromUserTask-191]
|
||||
_ = x[FnSkbSetTstamp-192]
|
||||
_ = x[FnImaFileHash-193]
|
||||
_ = x[FnKptrXchg-194]
|
||||
_ = x[FnMapLookupPercpuElem-195]
|
||||
_ = x[FnSkcToMptcpSock-196]
|
||||
_ = x[FnDynptrFromMem-197]
|
||||
_ = x[FnRingbufReserveDynptr-198]
|
||||
_ = x[FnRingbufSubmitDynptr-199]
|
||||
_ = x[FnRingbufDiscardDynptr-200]
|
||||
_ = x[FnDynptrRead-201]
|
||||
_ = x[FnDynptrWrite-202]
|
||||
_ = x[FnDynptrData-203]
|
||||
_ = x[FnTcpRawGenSyncookieIpv4-204]
|
||||
_ = x[FnTcpRawGenSyncookieIpv6-205]
|
||||
_ = x[FnTcpRawCheckSyncookieIpv4-206]
|
||||
_ = x[FnTcpRawCheckSyncookieIpv6-207]
|
||||
_ = x[FnKtimeGetTaiNs-208]
|
||||
_ = x[FnUserRingbufDrain-209]
|
||||
_ = x[FnCgrpStorageGet-210]
|
||||
_ = x[FnCgrpStorageDelete-211]
|
||||
_ = x[maxBuiltinFunc-212]
|
||||
}
|
||||
|
||||
const _BuiltinFunc_name = "FnUnspecFnMapLookupElemFnMapUpdateElemFnMapDeleteElemFnProbeReadFnKtimeGetNsFnTracePrintkFnGetPrandomU32FnGetSmpProcessorIdFnSkbStoreBytesFnL3CsumReplaceFnL4CsumReplaceFnTailCallFnCloneRedirectFnGetCurrentPidTgidFnGetCurrentUidGidFnGetCurrentCommFnGetCgroupClassidFnSkbVlanPushFnSkbVlanPopFnSkbGetTunnelKeyFnSkbSetTunnelKeyFnPerfEventReadFnRedirectFnGetRouteRealmFnPerfEventOutputFnSkbLoadBytesFnGetStackidFnCsumDiffFnSkbGetTunnelOptFnSkbSetTunnelOptFnSkbChangeProtoFnSkbChangeTypeFnSkbUnderCgroupFnGetHashRecalcFnGetCurrentTaskFnProbeWriteUserFnCurrentTaskUnderCgroupFnSkbChangeTailFnSkbPullDataFnCsumUpdateFnSetHashInvalidFnGetNumaNodeIdFnSkbChangeHeadFnXdpAdjustHeadFnProbeReadStrFnGetSocketCookieFnGetSocketUidFnSetHashFnSetsockoptFnSkbAdjustRoomFnRedirectMapFnSkRedirectMapFnSockMapUpdateFnXdpAdjustMetaFnPerfEventReadValueFnPerfProgReadValueFnGetsockoptFnOverrideReturnFnSockOpsCbFlagsSetFnMsgRedirectMapFnMsgApplyBytesFnMsgCorkBytesFnMsgPullDataFnBindFnXdpAdjustTailFnSkbGetXfrmStateFnGetStackFnSkbLoadBytesRelativeFnFibLookupFnSockHashUpdateFnMsgRedirectHashFnSkRedirectHashFnLwtPushEncapFnLwtSeg6StoreBytesFnLwtSeg6AdjustSrhFnLwtSeg6ActionFnRcRepeatFnRcKeydownFnSkbCgroupIdFnGetCurrentCgroupIdFnGetLocalStorageFnSkSelectReuseportFnSkbAncestorCgroupIdFnSkLookupTcpFnSkLookupUdpFnSkReleaseFnMapPushElemFnMapPopElemFnMapPeekElemFnMsgPushDataFnMsgPopDataFnRcPointerRelFnSpinLockFnSpinUnlockFnSkFullsockFnTcpSockFnSkbEcnSetCeFnGetListenerSockFnSkcLookupTcpFnTcpCheckSyncookieFnSysctlGetNameFnSysctlGetCurrentValueFnSysctlGetNewValueFnSysctlSetNewValueFnStrtolFnStrtoulFnSkStorageGetFnSkStorageDeleteFnSendSignalFnTcpGenSyncookieFnSkbOutputFnProbeReadUserFnProbeReadKernelFnProbeReadUserStrFnProbeReadKernelStrFnTcpSendAckFnSendSignalThreadFnJiffies64FnReadBranchRecordsFnGetNsCurrentPidTgidFnXdpOutputFnGetNetnsCookieFnGetCurrentAncestorCgroupIdFnSkAssignFnKtimeGetBootNsFnSeqPrintfFnSeqWriteFnSkCgroupIdFnSkAncestorCgroupIdFnRingbufOutputFnRingbufReserveFnRingbufSubmitFnRingbufDiscardFnRingbufQueryFnCsumLevelFnSkcToTcp6SockFnSkcToTcpSockFnSkcToTcpTimewaitSockFnSkcToTcpRequestSockFnSkcToUdp6SockFnGetTaskStackFnLoadHdrOptFnStoreHdrOptFnReserveHdrOptFnInodeStorageGetFnInodeStorageDeleteFnDPathFnCopyFromUserFnSnprintfBtfFnSeqPrintfBtfFnSkbCgroupClassidFnRedirectNeighFnPerCpuPtrFnThisCpuPtrFnRedirectPeerFnTaskStorageGetFnTaskStorageDeleteFnGetCurrentTaskBtfFnBprmOptsSetFnKtimeGetCoarseNsFnImaInodeHashFnSockFromFileFnCheckMtuFnForEachMapElemFnSnprintfFnSysBpfFnBtfFindByNameKindFnSysCloseFnTimerInitFnTimerSetCallbackFnTimerStartFnTimerCancelFnGetFuncIpFnGetAttachCookieFnTaskPtRegsFnGetBranchSnapshotFnTraceVprintkFnSkcToUnixSockFnKallsymsLookupNameFnFindVmaFnLoopFnStrncmpFnGetFuncArgFnGetFuncRetFnGetFuncArgCntFnGetRetvalFnSetRetvalFnXdpGetBuffLenFnXdpLoadBytesFnXdpStoreBytesFnCopyFromUserTaskFnSkbSetTstampFnImaFileHashFnKptrXchgFnMapLookupPercpuElemFnSkcToMptcpSockFnDynptrFromMemFnRingbufReserveDynptrFnRingbufSubmitDynptrFnRingbufDiscardDynptrFnDynptrReadFnDynptrWriteFnDynptrDataFnTcpRawGenSyncookieIpv4FnTcpRawGenSyncookieIpv6FnTcpRawCheckSyncookieIpv4FnTcpRawCheckSyncookieIpv6FnKtimeGetTaiNsFnUserRingbufDrainFnCgrpStorageGetFnCgrpStorageDeletemaxBuiltinFunc"
|
||||
|
||||
var _BuiltinFunc_index = [...]uint16{0, 8, 23, 38, 53, 64, 76, 89, 104, 123, 138, 153, 168, 178, 193, 212, 230, 246, 264, 277, 289, 306, 323, 338, 348, 363, 380, 394, 406, 416, 433, 450, 466, 481, 497, 512, 528, 544, 568, 583, 596, 608, 624, 639, 654, 669, 683, 700, 714, 723, 735, 750, 763, 778, 793, 808, 828, 847, 859, 875, 894, 910, 925, 939, 952, 958, 973, 990, 1000, 1022, 1033, 1049, 1066, 1082, 1096, 1115, 1133, 1148, 1158, 1169, 1182, 1202, 1219, 1238, 1259, 1272, 1285, 1296, 1309, 1321, 1334, 1347, 1359, 1373, 1383, 1395, 1407, 1416, 1429, 1446, 1460, 1479, 1494, 1517, 1536, 1555, 1563, 1572, 1586, 1603, 1615, 1632, 1643, 1658, 1675, 1693, 1713, 1725, 1743, 1754, 1773, 1794, 1805, 1821, 1849, 1859, 1875, 1886, 1896, 1908, 1928, 1943, 1959, 1974, 1990, 2004, 2015, 2030, 2044, 2066, 2087, 2102, 2116, 2128, 2141, 2156, 2173, 2193, 2200, 2214, 2227, 2241, 2259, 2274, 2285, 2297, 2311, 2327, 2346, 2365, 2378, 2396, 2410, 2424, 2434, 2450, 2460, 2468, 2487, 2497, 2508, 2526, 2538, 2551, 2562, 2579, 2591, 2610, 2624, 2639, 2659, 2668, 2674, 2683, 2695, 2707, 2722, 2733, 2744, 2759, 2773, 2788, 2806, 2820, 2833, 2843, 2864, 2880, 2895, 2917, 2938, 2960, 2972, 2985, 2997, 3021, 3045, 3071, 3097, 3112, 3130, 3146, 3165, 3179}
|
||||
|
||||
func (i BuiltinFunc) String() string {
|
||||
if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) {
|
||||
return "BuiltinFunc(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// InstructionSize is the size of a BPF instruction in bytes
|
||||
const InstructionSize = 8
|
||||
|
||||
// RawInstructionOffset is an offset in units of raw BPF instructions.
|
||||
type RawInstructionOffset uint64
|
||||
|
||||
var ErrUnreferencedSymbol = errors.New("unreferenced symbol")
|
||||
var ErrUnsatisfiedMapReference = errors.New("unsatisfied map reference")
|
||||
var ErrUnsatisfiedProgramReference = errors.New("unsatisfied program reference")
|
||||
|
||||
// Bytes returns the offset of an instruction in bytes.
|
||||
func (rio RawInstructionOffset) Bytes() uint64 {
|
||||
return uint64(rio) * InstructionSize
|
||||
}
|
||||
|
||||
// Instruction is a single eBPF instruction.
|
||||
type Instruction struct {
|
||||
OpCode OpCode
|
||||
Dst Register
|
||||
Src Register
|
||||
Offset int16
|
||||
Constant int64
|
||||
|
||||
// Metadata contains optional metadata about this instruction.
|
||||
Metadata Metadata
|
||||
}
|
||||
|
||||
// Unmarshal decodes a BPF instruction.
|
||||
func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) {
|
||||
data := make([]byte, InstructionSize)
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ins.OpCode = OpCode(data[0])
|
||||
|
||||
regs := data[1]
|
||||
switch bo {
|
||||
case binary.LittleEndian:
|
||||
ins.Dst, ins.Src = Register(regs&0xF), Register(regs>>4)
|
||||
case binary.BigEndian:
|
||||
ins.Dst, ins.Src = Register(regs>>4), Register(regs&0xf)
|
||||
}
|
||||
|
||||
ins.Offset = int16(bo.Uint16(data[2:4]))
|
||||
// Convert to int32 before widening to int64
|
||||
// to ensure the signed bit is carried over.
|
||||
ins.Constant = int64(int32(bo.Uint32(data[4:8])))
|
||||
|
||||
if !ins.OpCode.IsDWordLoad() {
|
||||
return InstructionSize, nil
|
||||
}
|
||||
|
||||
// Pull another instruction from the stream to retrieve the second
|
||||
// half of the 64-bit immediate value.
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
// No Wrap, to avoid io.EOF clash
|
||||
return 0, errors.New("64bit immediate is missing second half")
|
||||
}
|
||||
|
||||
// Require that all fields other than the value are zero.
|
||||
if bo.Uint32(data[0:4]) != 0 {
|
||||
return 0, errors.New("64bit immediate has non-zero fields")
|
||||
}
|
||||
|
||||
cons1 := uint32(ins.Constant)
|
||||
cons2 := int32(bo.Uint32(data[4:8]))
|
||||
ins.Constant = int64(cons2)<<32 | int64(cons1)
|
||||
|
||||
return 2 * InstructionSize, nil
|
||||
}
|
||||
|
||||
// Marshal encodes a BPF instruction.
|
||||
func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
|
||||
if ins.OpCode == InvalidOpCode {
|
||||
return 0, errors.New("invalid opcode")
|
||||
}
|
||||
|
||||
isDWordLoad := ins.OpCode.IsDWordLoad()
|
||||
|
||||
cons := int32(ins.Constant)
|
||||
if isDWordLoad {
|
||||
// Encode least significant 32bit first for 64bit operations.
|
||||
cons = int32(uint32(ins.Constant))
|
||||
}
|
||||
|
||||
regs, err := newBPFRegisters(ins.Dst, ins.Src, bo)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't marshal registers: %s", err)
|
||||
}
|
||||
|
||||
data := make([]byte, InstructionSize)
|
||||
data[0] = byte(ins.OpCode)
|
||||
data[1] = byte(regs)
|
||||
bo.PutUint16(data[2:4], uint16(ins.Offset))
|
||||
bo.PutUint32(data[4:8], uint32(cons))
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !isDWordLoad {
|
||||
return InstructionSize, nil
|
||||
}
|
||||
|
||||
// The first half of the second part of a double-wide instruction
|
||||
// must be zero. The second half carries the value.
|
||||
bo.PutUint32(data[0:4], 0)
|
||||
bo.PutUint32(data[4:8], uint32(ins.Constant>>32))
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return 2 * InstructionSize, nil
|
||||
}
|
||||
|
||||
// AssociateMap associates a Map with this Instruction.
|
||||
//
|
||||
// Implicitly clears the Instruction's Reference field.
|
||||
//
|
||||
// Returns an error if the Instruction is not a map load.
|
||||
func (ins *Instruction) AssociateMap(m FDer) error {
|
||||
if !ins.IsLoadFromMap() {
|
||||
return errors.New("not a load from a map")
|
||||
}
|
||||
|
||||
ins.Metadata.Set(referenceMeta{}, nil)
|
||||
ins.Metadata.Set(mapMeta{}, m)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteMapPtr changes an instruction to use a new map fd.
|
||||
//
|
||||
// Returns an error if the instruction doesn't load a map.
|
||||
//
|
||||
// Deprecated: use AssociateMap instead. If you cannot provide a Map,
|
||||
// wrap an fd in a type implementing FDer.
|
||||
func (ins *Instruction) RewriteMapPtr(fd int) error {
|
||||
if !ins.IsLoadFromMap() {
|
||||
return errors.New("not a load from a map")
|
||||
}
|
||||
|
||||
ins.encodeMapFD(fd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ins *Instruction) encodeMapFD(fd int) {
|
||||
// Preserve the offset value for direct map loads.
|
||||
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
|
||||
rawFd := uint64(uint32(fd))
|
||||
ins.Constant = int64(offset | rawFd)
|
||||
}
|
||||
|
||||
// MapPtr returns the map fd for this instruction.
|
||||
//
|
||||
// The result is undefined if the instruction is not a load from a map,
|
||||
// see IsLoadFromMap.
|
||||
//
|
||||
// Deprecated: use Map() instead.
|
||||
func (ins *Instruction) MapPtr() int {
|
||||
// If there is a map associated with the instruction, return its FD.
|
||||
if fd := ins.Metadata.Get(mapMeta{}); fd != nil {
|
||||
return fd.(FDer).FD()
|
||||
}
|
||||
|
||||
// Fall back to the fd stored in the Constant field
|
||||
return ins.mapFd()
|
||||
}
|
||||
|
||||
// mapFd returns the map file descriptor stored in the 32 least significant
|
||||
// bits of ins' Constant field.
|
||||
func (ins *Instruction) mapFd() int {
|
||||
return int(int32(ins.Constant))
|
||||
}
|
||||
|
||||
// RewriteMapOffset changes the offset of a direct load from a map.
|
||||
//
|
||||
// Returns an error if the instruction is not a direct load.
|
||||
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
|
||||
if !ins.OpCode.IsDWordLoad() {
|
||||
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
|
||||
}
|
||||
|
||||
if ins.Src != PseudoMapValue {
|
||||
return errors.New("not a direct load from a map")
|
||||
}
|
||||
|
||||
fd := uint64(ins.Constant) & math.MaxUint32
|
||||
ins.Constant = int64(uint64(offset)<<32 | fd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ins *Instruction) mapOffset() uint32 {
|
||||
return uint32(uint64(ins.Constant) >> 32)
|
||||
}
|
||||
|
||||
// IsLoadFromMap returns true if the instruction loads from a map.
|
||||
//
|
||||
// This covers both loading the map pointer and direct map value loads.
|
||||
func (ins *Instruction) IsLoadFromMap() bool {
|
||||
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
|
||||
}
|
||||
|
||||
// IsFunctionCall returns true if the instruction calls another BPF function.
|
||||
//
|
||||
// This is not the same thing as a BPF helper call.
|
||||
func (ins *Instruction) IsFunctionCall() bool {
|
||||
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
|
||||
}
|
||||
|
||||
// IsKfuncCall returns true if the instruction calls a kfunc.
|
||||
//
|
||||
// This is not the same thing as a BPF helper call.
|
||||
func (ins *Instruction) IsKfuncCall() bool {
|
||||
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoKfuncCall
|
||||
}
|
||||
|
||||
// IsLoadOfFunctionPointer returns true if the instruction loads a function pointer.
|
||||
func (ins *Instruction) IsLoadOfFunctionPointer() bool {
|
||||
return ins.OpCode.IsDWordLoad() && ins.Src == PseudoFunc
|
||||
}
|
||||
|
||||
// IsFunctionReference returns true if the instruction references another BPF
|
||||
// function, either by invoking a Call jump operation or by loading a function
|
||||
// pointer.
|
||||
func (ins *Instruction) IsFunctionReference() bool {
|
||||
return ins.IsFunctionCall() || ins.IsLoadOfFunctionPointer()
|
||||
}
|
||||
|
||||
// IsBuiltinCall returns true if the instruction is a built-in call, i.e. BPF helper call.
|
||||
func (ins *Instruction) IsBuiltinCall() bool {
|
||||
return ins.OpCode.JumpOp() == Call && ins.Src == R0 && ins.Dst == R0
|
||||
}
|
||||
|
||||
// IsConstantLoad returns true if the instruction loads a constant of the
|
||||
// given size.
|
||||
func (ins *Instruction) IsConstantLoad(size Size) bool {
|
||||
return ins.OpCode == LoadImmOp(size) && ins.Src == R0 && ins.Offset == 0
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
func (ins Instruction) Format(f fmt.State, c rune) {
|
||||
if c != 'v' {
|
||||
fmt.Fprintf(f, "{UNRECOGNIZED: %c}", c)
|
||||
return
|
||||
}
|
||||
|
||||
op := ins.OpCode
|
||||
|
||||
if op == InvalidOpCode {
|
||||
fmt.Fprint(f, "INVALID")
|
||||
return
|
||||
}
|
||||
|
||||
// Omit trailing space for Exit
|
||||
if op.JumpOp() == Exit {
|
||||
fmt.Fprint(f, op)
|
||||
return
|
||||
}
|
||||
|
||||
if ins.IsLoadFromMap() {
|
||||
fd := ins.mapFd()
|
||||
m := ins.Map()
|
||||
switch ins.Src {
|
||||
case PseudoMapFD:
|
||||
if m != nil {
|
||||
fmt.Fprintf(f, "LoadMapPtr dst: %s map: %s", ins.Dst, m)
|
||||
} else {
|
||||
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
|
||||
}
|
||||
|
||||
case PseudoMapValue:
|
||||
if m != nil {
|
||||
fmt.Fprintf(f, "LoadMapValue dst: %s, map: %s off: %d", ins.Dst, m, ins.mapOffset())
|
||||
} else {
|
||||
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
|
||||
}
|
||||
}
|
||||
|
||||
goto ref
|
||||
}
|
||||
|
||||
fmt.Fprintf(f, "%v ", op)
|
||||
switch cls := op.Class(); {
|
||||
case cls.isLoadOrStore():
|
||||
switch op.Mode() {
|
||||
case ImmMode:
|
||||
fmt.Fprintf(f, "dst: %s imm: %d", ins.Dst, ins.Constant)
|
||||
case AbsMode:
|
||||
fmt.Fprintf(f, "imm: %d", ins.Constant)
|
||||
case IndMode:
|
||||
fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
|
||||
case MemMode:
|
||||
fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant)
|
||||
case XAddMode:
|
||||
fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src)
|
||||
}
|
||||
|
||||
case cls.IsALU():
|
||||
fmt.Fprintf(f, "dst: %s ", ins.Dst)
|
||||
if op.ALUOp() == Swap || op.Source() == ImmSource {
|
||||
fmt.Fprintf(f, "imm: %d", ins.Constant)
|
||||
} else {
|
||||
fmt.Fprintf(f, "src: %s", ins.Src)
|
||||
}
|
||||
|
||||
case cls.IsJump():
|
||||
switch jop := op.JumpOp(); jop {
|
||||
case Call:
|
||||
switch ins.Src {
|
||||
case PseudoCall:
|
||||
// bpf-to-bpf call
|
||||
fmt.Fprint(f, ins.Constant)
|
||||
case PseudoKfuncCall:
|
||||
// kfunc call
|
||||
fmt.Fprintf(f, "Kfunc(%d)", ins.Constant)
|
||||
default:
|
||||
fmt.Fprint(f, BuiltinFunc(ins.Constant))
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset)
|
||||
if op.Source() == ImmSource {
|
||||
fmt.Fprintf(f, "imm: %d", ins.Constant)
|
||||
} else {
|
||||
fmt.Fprintf(f, "src: %s", ins.Src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref:
|
||||
if ins.Reference() != "" {
|
||||
fmt.Fprintf(f, " <%s>", ins.Reference())
|
||||
}
|
||||
}
|
||||
|
||||
func (ins Instruction) equal(other Instruction) bool {
|
||||
return ins.OpCode == other.OpCode &&
|
||||
ins.Dst == other.Dst &&
|
||||
ins.Src == other.Src &&
|
||||
ins.Offset == other.Offset &&
|
||||
ins.Constant == other.Constant
|
||||
}
|
||||
|
||||
// Size returns the amount of bytes ins would occupy in binary form.
|
||||
func (ins Instruction) Size() uint64 {
|
||||
return uint64(InstructionSize * ins.OpCode.rawInstructions())
|
||||
}
|
||||
|
||||
// WithMetadata sets the given Metadata on the Instruction. e.g. to copy
|
||||
// Metadata from another Instruction when replacing it.
|
||||
func (ins Instruction) WithMetadata(meta Metadata) Instruction {
|
||||
ins.Metadata = meta
|
||||
return ins
|
||||
}
|
||||
|
||||
type symbolMeta struct{}
|
||||
|
||||
// WithSymbol marks the Instruction as a Symbol, which other Instructions
|
||||
// can point to using corresponding calls to WithReference.
|
||||
func (ins Instruction) WithSymbol(name string) Instruction {
|
||||
ins.Metadata.Set(symbolMeta{}, name)
|
||||
return ins
|
||||
}
|
||||
|
||||
// Sym creates a symbol.
|
||||
//
|
||||
// Deprecated: use WithSymbol instead.
|
||||
func (ins Instruction) Sym(name string) Instruction {
|
||||
return ins.WithSymbol(name)
|
||||
}
|
||||
|
||||
// Symbol returns the value ins has been marked with using WithSymbol,
|
||||
// otherwise returns an empty string. A symbol is often an Instruction
|
||||
// at the start of a function body.
|
||||
func (ins Instruction) Symbol() string {
|
||||
sym, _ := ins.Metadata.Get(symbolMeta{}).(string)
|
||||
return sym
|
||||
}
|
||||
|
||||
type referenceMeta struct{}
|
||||
|
||||
// WithReference makes ins reference another Symbol or map by name.
|
||||
func (ins Instruction) WithReference(ref string) Instruction {
|
||||
ins.Metadata.Set(referenceMeta{}, ref)
|
||||
return ins
|
||||
}
|
||||
|
||||
// Reference returns the Symbol or map name referenced by ins, if any.
|
||||
func (ins Instruction) Reference() string {
|
||||
ref, _ := ins.Metadata.Get(referenceMeta{}).(string)
|
||||
return ref
|
||||
}
|
||||
|
||||
type mapMeta struct{}
|
||||
|
||||
// Map returns the Map referenced by ins, if any.
|
||||
// An Instruction will contain a Map if e.g. it references an existing,
|
||||
// pinned map that was opened during ELF loading.
|
||||
func (ins Instruction) Map() FDer {
|
||||
fd, _ := ins.Metadata.Get(mapMeta{}).(FDer)
|
||||
return fd
|
||||
}
|
||||
|
||||
type sourceMeta struct{}
|
||||
|
||||
// WithSource adds source information about the Instruction.
|
||||
func (ins Instruction) WithSource(src fmt.Stringer) Instruction {
|
||||
ins.Metadata.Set(sourceMeta{}, src)
|
||||
return ins
|
||||
}
|
||||
|
||||
// Source returns source information about the Instruction. The field is
|
||||
// present when the compiler emits BTF line info about the Instruction and
|
||||
// usually contains the line of source code responsible for it.
|
||||
func (ins Instruction) Source() fmt.Stringer {
|
||||
str, _ := ins.Metadata.Get(sourceMeta{}).(fmt.Stringer)
|
||||
return str
|
||||
}
|
||||
|
||||
// A Comment can be passed to Instruction.WithSource to add a comment
|
||||
// to an instruction.
|
||||
type Comment string
|
||||
|
||||
func (s Comment) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// FDer represents a resource tied to an underlying file descriptor.
|
||||
// Used as a stand-in for e.g. ebpf.Map since that type cannot be
|
||||
// imported here and FD() is the only method we rely on.
|
||||
type FDer interface {
|
||||
FD() int
|
||||
}
|
||||
|
||||
// Instructions is an eBPF program.
|
||||
type Instructions []Instruction
|
||||
|
||||
// Unmarshal unmarshals an Instructions from a binary instruction stream.
|
||||
// All instructions in insns are replaced by instructions decoded from r.
|
||||
func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) error {
|
||||
if len(*insns) > 0 {
|
||||
*insns = nil
|
||||
}
|
||||
|
||||
var offset uint64
|
||||
for {
|
||||
var ins Instruction
|
||||
n, err := ins.Unmarshal(r, bo)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("offset %d: %w", offset, err)
|
||||
}
|
||||
|
||||
*insns = append(*insns, ins)
|
||||
offset += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the function insns belongs to, if any.
|
||||
func (insns Instructions) Name() string {
|
||||
if len(insns) == 0 {
|
||||
return ""
|
||||
}
|
||||
return insns[0].Symbol()
|
||||
}
|
||||
|
||||
func (insns Instructions) String() string {
|
||||
return fmt.Sprint(insns)
|
||||
}
|
||||
|
||||
// Size returns the amount of bytes insns would occupy in binary form.
|
||||
func (insns Instructions) Size() uint64 {
|
||||
var sum uint64
|
||||
for _, ins := range insns {
|
||||
sum += ins.Size()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// AssociateMap updates all Instructions that Reference the given symbol
|
||||
// to point to an existing Map m instead.
|
||||
//
|
||||
// Returns ErrUnreferencedSymbol error if no references to symbol are found
|
||||
// in insns. If symbol is anything else than the symbol name of map (e.g.
|
||||
// a bpf2bpf subprogram), an error is returned.
|
||||
func (insns Instructions) AssociateMap(symbol string, m FDer) error {
|
||||
if symbol == "" {
|
||||
return errors.New("empty symbol")
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := range insns {
|
||||
ins := &insns[i]
|
||||
if ins.Reference() != symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ins.AssociateMap(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteMapPtr rewrites all loads of a specific map pointer to a new fd.
|
||||
//
|
||||
// Returns ErrUnreferencedSymbol if the symbol isn't used.
|
||||
//
|
||||
// Deprecated: use AssociateMap instead.
|
||||
func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
|
||||
if symbol == "" {
|
||||
return errors.New("empty symbol")
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := range insns {
|
||||
ins := &insns[i]
|
||||
if ins.Reference() != symbol {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ins.IsLoadFromMap() {
|
||||
return errors.New("not a load from a map")
|
||||
}
|
||||
|
||||
ins.encodeMapFD(fd)
|
||||
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("symbol %s: %w", symbol, ErrUnreferencedSymbol)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymbolOffsets returns the set of symbols and their offset in
|
||||
// the instructions.
|
||||
func (insns Instructions) SymbolOffsets() (map[string]int, error) {
|
||||
offsets := make(map[string]int)
|
||||
|
||||
for i, ins := range insns {
|
||||
if ins.Symbol() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := offsets[ins.Symbol()]; ok {
|
||||
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol())
|
||||
}
|
||||
|
||||
offsets[ins.Symbol()] = i
|
||||
}
|
||||
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
// FunctionReferences returns a set of symbol names these Instructions make
|
||||
// bpf-to-bpf calls to.
|
||||
func (insns Instructions) FunctionReferences() []string {
|
||||
calls := make(map[string]struct{})
|
||||
for _, ins := range insns {
|
||||
if ins.Constant != -1 {
|
||||
// BPF-to-BPF calls have -1 constants.
|
||||
continue
|
||||
}
|
||||
|
||||
if ins.Reference() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ins.IsFunctionReference() {
|
||||
continue
|
||||
}
|
||||
|
||||
calls[ins.Reference()] = struct{}{}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(calls))
|
||||
for call := range calls {
|
||||
result = append(result, call)
|
||||
}
|
||||
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// ReferenceOffsets returns the set of references and their offset in
|
||||
// the instructions.
|
||||
func (insns Instructions) ReferenceOffsets() map[string][]int {
|
||||
offsets := make(map[string][]int)
|
||||
|
||||
for i, ins := range insns {
|
||||
if ins.Reference() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
offsets[ins.Reference()] = append(offsets[ins.Reference()], i)
|
||||
}
|
||||
|
||||
return offsets
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
//
|
||||
// You can control indentation of symbols by
|
||||
// specifying a width. Setting a precision controls the indentation of
|
||||
// instructions.
|
||||
// The default character is a tab, which can be overridden by specifying
|
||||
// the ' ' space flag.
|
||||
func (insns Instructions) Format(f fmt.State, c rune) {
|
||||
if c != 's' && c != 'v' {
|
||||
fmt.Fprintf(f, "{UNKNOWN FORMAT '%c'}", c)
|
||||
return
|
||||
}
|
||||
|
||||
// Precision is better in this case, because it allows
|
||||
// specifying 0 padding easily.
|
||||
padding, ok := f.Precision()
|
||||
if !ok {
|
||||
padding = 1
|
||||
}
|
||||
|
||||
indent := strings.Repeat("\t", padding)
|
||||
if f.Flag(' ') {
|
||||
indent = strings.Repeat(" ", padding)
|
||||
}
|
||||
|
||||
symPadding, ok := f.Width()
|
||||
if !ok {
|
||||
symPadding = padding - 1
|
||||
}
|
||||
if symPadding < 0 {
|
||||
symPadding = 0
|
||||
}
|
||||
|
||||
symIndent := strings.Repeat("\t", symPadding)
|
||||
if f.Flag(' ') {
|
||||
symIndent = strings.Repeat(" ", symPadding)
|
||||
}
|
||||
|
||||
// Guess how many digits we need at most, by assuming that all instructions
|
||||
// are double wide.
|
||||
highestOffset := len(insns) * 2
|
||||
offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
|
||||
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
if iter.Ins.Symbol() != "" {
|
||||
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol())
|
||||
}
|
||||
if src := iter.Ins.Source(); src != nil {
|
||||
line := strings.TrimSpace(src.String())
|
||||
if line != "" {
|
||||
fmt.Fprintf(f, "%s%*s; %s\n", indent, offsetWidth, " ", line)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal encodes a BPF program into the kernel format.
|
||||
//
|
||||
// insns may be modified if there are unresolved jumps or bpf2bpf calls.
|
||||
//
|
||||
// Returns ErrUnsatisfiedProgramReference if there is a Reference Instruction
|
||||
// without a matching Symbol Instruction within insns.
|
||||
func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
|
||||
if err := insns.encodeFunctionReferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := insns.encodeMapPointers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, ins := range insns {
|
||||
if _, err := ins.Marshal(w, bo); err != nil {
|
||||
return fmt.Errorf("instruction %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag calculates the kernel tag for a series of instructions.
|
||||
//
|
||||
// It mirrors bpf_prog_calc_tag in the kernel and so can be compared
|
||||
// to ProgramInfo.Tag to figure out whether a loaded program matches
|
||||
// certain instructions.
|
||||
func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
|
||||
h := sha1.New()
|
||||
for i, ins := range insns {
|
||||
if ins.IsLoadFromMap() {
|
||||
ins.Constant = 0
|
||||
}
|
||||
_, err := ins.Marshal(h, bo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("instruction %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
|
||||
}
|
||||
|
||||
// encodeFunctionReferences populates the Offset (or Constant, depending on
|
||||
// the instruction type) field of instructions with a Reference field to point
|
||||
// to the offset of the corresponding instruction with a matching Symbol field.
|
||||
//
|
||||
// Only Reference Instructions that are either jumps or BPF function references
|
||||
// (calls or function pointer loads) are populated.
|
||||
//
|
||||
// Returns ErrUnsatisfiedProgramReference if there is a Reference Instruction
|
||||
// without at least one corresponding Symbol Instruction within insns.
|
||||
func (insns Instructions) encodeFunctionReferences() error {
|
||||
// Index the offsets of instructions tagged as a symbol.
|
||||
symbolOffsets := make(map[string]RawInstructionOffset)
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
ins := iter.Ins
|
||||
|
||||
if ins.Symbol() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := symbolOffsets[ins.Symbol()]; ok {
|
||||
return fmt.Errorf("duplicate symbol %s", ins.Symbol())
|
||||
}
|
||||
|
||||
symbolOffsets[ins.Symbol()] = iter.Offset
|
||||
}
|
||||
|
||||
// Find all instructions tagged as references to other symbols.
|
||||
// Depending on the instruction type, populate their constant or offset
|
||||
// fields to point to the symbol they refer to within the insn stream.
|
||||
iter = insns.Iterate()
|
||||
for iter.Next() {
|
||||
i := iter.Index
|
||||
offset := iter.Offset
|
||||
ins := iter.Ins
|
||||
|
||||
if ins.Reference() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case ins.IsFunctionReference() && ins.Constant == -1:
|
||||
symOffset, ok := symbolOffsets[ins.Reference()]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
|
||||
}
|
||||
|
||||
ins.Constant = int64(symOffset - offset - 1)
|
||||
|
||||
case ins.OpCode.Class().IsJump() && ins.Offset == -1:
|
||||
symOffset, ok := symbolOffsets[ins.Reference()]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedProgramReference)
|
||||
}
|
||||
|
||||
ins.Offset = int16(symOffset - offset - 1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeMapPointers finds all Map Instructions and encodes their FDs
|
||||
// into their Constant fields.
|
||||
func (insns Instructions) encodeMapPointers() error {
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
ins := iter.Ins
|
||||
|
||||
if !ins.IsLoadFromMap() {
|
||||
continue
|
||||
}
|
||||
|
||||
m := ins.Map()
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fd := m.FD()
|
||||
if fd < 0 {
|
||||
return fmt.Errorf("map %s: %w", m, sys.ErrClosedFd)
|
||||
}
|
||||
|
||||
ins.encodeMapFD(m.FD())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate allows iterating a BPF program while keeping track of
|
||||
// various offsets.
|
||||
//
|
||||
// Modifying the instruction slice will lead to undefined behaviour.
|
||||
func (insns Instructions) Iterate() *InstructionIterator {
|
||||
return &InstructionIterator{insns: insns}
|
||||
}
|
||||
|
||||
// InstructionIterator iterates over a BPF program.
|
||||
type InstructionIterator struct {
|
||||
insns Instructions
|
||||
// The instruction in question.
|
||||
Ins *Instruction
|
||||
// The index of the instruction in the original instruction slice.
|
||||
Index int
|
||||
// The offset of the instruction in raw BPF instructions. This accounts
|
||||
// for double-wide instructions.
|
||||
Offset RawInstructionOffset
|
||||
}
|
||||
|
||||
// Next returns true as long as there are any instructions remaining.
|
||||
func (iter *InstructionIterator) Next() bool {
|
||||
if len(iter.insns) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if iter.Ins != nil {
|
||||
iter.Index++
|
||||
iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions())
|
||||
}
|
||||
iter.Ins = &iter.insns[0]
|
||||
iter.insns = iter.insns[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
type bpfRegisters uint8
|
||||
|
||||
func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
|
||||
switch bo {
|
||||
case binary.LittleEndian:
|
||||
return bpfRegisters((src << 4) | (dst & 0xF)), nil
|
||||
case binary.BigEndian:
|
||||
return bpfRegisters((dst << 4) | (src & 0xF)), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnreferencedSymbol returns true if err was caused by
|
||||
// an unreferenced symbol.
|
||||
//
|
||||
// Deprecated: use errors.Is(err, asm.ErrUnreferencedSymbol).
|
||||
func IsUnreferencedSymbol(err error) bool {
|
||||
return errors.Is(err, ErrUnreferencedSymbol)
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
var test64bitImmProg = []byte{
|
||||
// r0 = math.MinInt32 - 1
|
||||
0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
}
|
||||
|
||||
func TestRead64bitImmediate(t *testing.T) {
|
||||
var ins Instruction
|
||||
n, err := ins.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := uint64(InstructionSize * 2); n != want {
|
||||
t.Errorf("Expected %d bytes to be read, got %d", want, n)
|
||||
}
|
||||
|
||||
if c := ins.Constant; c != math.MinInt32-1 {
|
||||
t.Errorf("Expected immediate to be %v, got %v", int64(math.MinInt32)-1, c)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRead64bitImmediate(b *testing.B) {
|
||||
r := &bytes.Reader{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Reset(test64bitImmProg)
|
||||
|
||||
var ins Instruction
|
||||
if _, err := ins.Unmarshal(r, binary.LittleEndian); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite64bitImmediate(t *testing.T) {
|
||||
insns := Instructions{
|
||||
LoadImm(R0, math.MinInt32-1, DWord),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := insns.Marshal(&buf, binary.LittleEndian); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if prog := buf.Bytes(); !bytes.Equal(prog, test64bitImmProg) {
|
||||
t.Errorf("Marshalled program does not match:\n%s", hex.Dump(prog))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrite64BitImmediate(b *testing.B) {
|
||||
ins := LoadImm(R0, math.MinInt32-1, DWord)
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
|
||||
if _, err := ins.Marshal(&buf, binary.LittleEndian); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalInstructions(t *testing.T) {
|
||||
r := bytes.NewReader(test64bitImmProg)
|
||||
|
||||
var insns Instructions
|
||||
if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Unmarshaling into the same Instructions multiple times replaces
|
||||
// the instruction stream.
|
||||
r.Reset(test64bitImmProg)
|
||||
if err := insns.Unmarshal(r, binary.LittleEndian); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(insns) != 1 {
|
||||
t.Fatalf("Expected one instruction, got %d", len(insns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedJump(t *testing.T) {
|
||||
insns := Instructions{
|
||||
JSGT.Imm(R0, -1, "foo"),
|
||||
}
|
||||
|
||||
insns[0].Offset = 1
|
||||
|
||||
err := insns.Marshal(io.Discard, binary.LittleEndian)
|
||||
if err != nil {
|
||||
t.Error("Can't marshal signed jump:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionRewriteMapConstant(t *testing.T) {
|
||||
ins := LoadMapValue(R0, 123, 321)
|
||||
|
||||
qt.Assert(t, ins.MapPtr(), qt.Equals, 123)
|
||||
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321))
|
||||
|
||||
qt.Assert(t, ins.RewriteMapPtr(-1), qt.IsNil)
|
||||
qt.Assert(t, ins.MapPtr(), qt.Equals, -1)
|
||||
|
||||
qt.Assert(t, ins.RewriteMapPtr(1), qt.IsNil)
|
||||
qt.Assert(t, ins.MapPtr(), qt.Equals, 1)
|
||||
|
||||
// mapOffset should be unchanged after rewriting the pointer.
|
||||
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321))
|
||||
|
||||
qt.Assert(t, ins.RewriteMapOffset(123), qt.IsNil)
|
||||
qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(123))
|
||||
|
||||
// MapPtr should be unchanged.
|
||||
qt.Assert(t, ins.MapPtr(), qt.Equals, 1)
|
||||
|
||||
ins = Mov.Imm(R1, 32)
|
||||
if err := ins.RewriteMapPtr(1); err == nil {
|
||||
t.Error("RewriteMapPtr rewriting bogus instruction")
|
||||
}
|
||||
if err := ins.RewriteMapOffset(1); err == nil {
|
||||
t.Error("RewriteMapOffset rewriting bogus instruction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionLoadMapValue(t *testing.T) {
|
||||
ins := LoadMapValue(R0, 1, 123)
|
||||
if !ins.IsLoadFromMap() {
|
||||
t.Error("isLoadFromMap returns false")
|
||||
}
|
||||
if fd := ins.mapFd(); fd != 1 {
|
||||
t.Error("Expected map fd to be 1, got", fd)
|
||||
}
|
||||
if off := ins.mapOffset(); off != 123 {
|
||||
t.Fatal("Expected map offset to be 123 after changin the pointer, got", off)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionsRewriteMapPtr(t *testing.T) {
|
||||
insns := Instructions{
|
||||
LoadMapPtr(R1, 0).WithReference("good"),
|
||||
Return(),
|
||||
}
|
||||
|
||||
if err := insns.RewriteMapPtr("good", 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if insns[0].Constant != 1 {
|
||||
t.Error("Constant should be 1, have", insns[0].Constant)
|
||||
}
|
||||
|
||||
if err := insns.RewriteMapPtr("good", 2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if insns[0].Constant != 2 {
|
||||
t.Error("Constant should be 2, have", insns[0].Constant)
|
||||
}
|
||||
|
||||
if err := insns.RewriteMapPtr("bad", 1); !errors.Is(err, ErrUnreferencedSymbol) {
|
||||
t.Error("Rewriting unreferenced map doesn't return appropriate error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionWithMetadata(t *testing.T) {
|
||||
ins := LoadImm(R0, 123, DWord).WithSymbol("abc")
|
||||
ins2 := LoadImm(R0, 567, DWord).WithMetadata(ins.Metadata)
|
||||
|
||||
if want, got := "abc", ins2.Symbol(); want != got {
|
||||
t.Fatalf("unexpected Symbol value on ins2: want: %s, got: %s", want, got)
|
||||
}
|
||||
|
||||
if want, got := ins.Metadata, ins2.Metadata; want != got {
|
||||
t.Fatal("expected ins and isn2 Metadata to match")
|
||||
}
|
||||
}
|
||||
|
||||
// You can use format flags to change the way an eBPF
|
||||
// program is stringified.
|
||||
func ExampleInstructions_Format() {
|
||||
|
||||
insns := Instructions{
|
||||
FnMapLookupElem.Call().WithSymbol("my_func").WithSource(Comment("bpf_map_lookup_elem()")),
|
||||
LoadImm(R0, 42, DWord).WithSource(Comment("abc = 42")),
|
||||
Return(),
|
||||
}
|
||||
|
||||
fmt.Println("Default format:")
|
||||
fmt.Printf("%v\n", insns)
|
||||
|
||||
fmt.Println("Don't indent instructions:")
|
||||
fmt.Printf("%.0v\n", insns)
|
||||
|
||||
fmt.Println("Indent using spaces:")
|
||||
fmt.Printf("% v\n", insns)
|
||||
|
||||
fmt.Println("Control symbol indentation:")
|
||||
fmt.Printf("%2v\n", insns)
|
||||
|
||||
// Output: Default format:
|
||||
// my_func:
|
||||
// ; bpf_map_lookup_elem()
|
||||
// 0: Call FnMapLookupElem
|
||||
// ; abc = 42
|
||||
// 1: LdImmDW dst: r0 imm: 42
|
||||
// 3: Exit
|
||||
//
|
||||
// Don't indent instructions:
|
||||
// my_func:
|
||||
// ; bpf_map_lookup_elem()
|
||||
// 0: Call FnMapLookupElem
|
||||
// ; abc = 42
|
||||
// 1: LdImmDW dst: r0 imm: 42
|
||||
// 3: Exit
|
||||
//
|
||||
// Indent using spaces:
|
||||
// my_func:
|
||||
// ; bpf_map_lookup_elem()
|
||||
// 0: Call FnMapLookupElem
|
||||
// ; abc = 42
|
||||
// 1: LdImmDW dst: r0 imm: 42
|
||||
// 3: Exit
|
||||
//
|
||||
// Control symbol indentation:
|
||||
// my_func:
|
||||
// ; bpf_map_lookup_elem()
|
||||
// 0: Call FnMapLookupElem
|
||||
// ; abc = 42
|
||||
// 1: LdImmDW dst: r0 imm: 42
|
||||
// 3: Exit
|
||||
}
|
||||
|
||||
func TestReadSrcDst(t *testing.T) {
|
||||
testSrcDstProg := []byte{
|
||||
// on little-endian: r0 = r1
|
||||
// on big-endian: be: r1 = r0
|
||||
0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
bo binary.ByteOrder
|
||||
dst, src Register
|
||||
}{
|
||||
{binary.BigEndian, R1, R0},
|
||||
{binary.LittleEndian, R0, R1},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.bo.String(), func(t *testing.T) {
|
||||
var ins Instruction
|
||||
_, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ins.Dst != tc.dst {
|
||||
t.Errorf("Expected destination to be %v, got %v", tc.dst, ins.Dst)
|
||||
}
|
||||
if ins.Src != tc.src {
|
||||
t.Errorf("Expected source to be %v, got %v", tc.src, ins.Src)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstructionIterator(t *testing.T) {
|
||||
insns := Instructions{
|
||||
LoadImm(R0, 0, Word),
|
||||
LoadImm(R0, 0, DWord),
|
||||
Return(),
|
||||
}
|
||||
offsets := []RawInstructionOffset{0, 1, 3}
|
||||
|
||||
iter := insns.Iterate()
|
||||
for i := 0; i < len(insns); i++ {
|
||||
if !iter.Next() {
|
||||
t.Fatalf("Expected %dth call to Next to return true", i)
|
||||
}
|
||||
|
||||
if iter.Ins == nil {
|
||||
t.Errorf("Expected iter.Ins to be non-nil")
|
||||
}
|
||||
if iter.Index != i {
|
||||
t.Errorf("Expected iter.Index to be %d, got %d", i, iter.Index)
|
||||
}
|
||||
if iter.Offset != offsets[i] {
|
||||
t.Errorf("Expected iter.Offset to be %d, got %d", offsets[i], iter.Offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataCopyOnWrite(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
// Setting metadata should copy Instruction and modify the metadata pointer
|
||||
// of the new object without touching the old Instruction.
|
||||
|
||||
// Reference
|
||||
ins := Ja.Label("my_func")
|
||||
ins2 := ins.WithReference("my_func2")
|
||||
|
||||
c.Assert(ins.Reference(), qt.Equals, "my_func", qt.Commentf("WithReference updated ins"))
|
||||
c.Assert(ins2.Reference(), qt.Equals, "my_func2", qt.Commentf("WithReference didn't update ins2"))
|
||||
|
||||
// Symbol
|
||||
ins = Ja.Label("").WithSymbol("my_sym")
|
||||
ins2 = ins.WithSymbol("my_sym2")
|
||||
|
||||
c.Assert(ins.Symbol(), qt.Equals, "my_sym", qt.Commentf("WithSymbol updated ins"))
|
||||
c.Assert(ins2.Symbol(), qt.Equals, "my_sym2", qt.Commentf("WithSymbol didn't update ins2"))
|
||||
|
||||
// Map
|
||||
ins = LoadMapPtr(R1, 0)
|
||||
ins2 = ins
|
||||
|
||||
testMap := testFDer(1)
|
||||
c.Assert(ins2.AssociateMap(testMap), qt.IsNil, qt.Commentf("failed to associate map with ins2"))
|
||||
|
||||
c.Assert(ins.Map(), qt.IsNil, qt.Commentf("AssociateMap updated ins"))
|
||||
c.Assert(ins2.Map(), qt.Equals, testMap, qt.Commentf("AssociateMap didn't update ins2"))
|
||||
}
|
||||
|
||||
type testFDer int
|
||||
|
||||
func (t testFDer) FD() int {
|
||||
return int(t)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package asm
|
||||
|
||||
//go:generate stringer -output jump_string.go -type=JumpOp
|
||||
|
||||
// JumpOp affect control flow.
|
||||
//
|
||||
// msb lsb
|
||||
// +----+-+---+
|
||||
// |OP |s|cls|
|
||||
// +----+-+---+
|
||||
type JumpOp uint8
|
||||
|
||||
const jumpMask OpCode = aluMask
|
||||
|
||||
const (
|
||||
// InvalidJumpOp is returned by getters when invoked
|
||||
// on non branch OpCodes
|
||||
InvalidJumpOp JumpOp = 0xff
|
||||
// Ja jumps by offset unconditionally
|
||||
Ja JumpOp = 0x00
|
||||
// JEq jumps by offset if r == imm
|
||||
JEq JumpOp = 0x10
|
||||
// JGT jumps by offset if r > imm
|
||||
JGT JumpOp = 0x20
|
||||
// JGE jumps by offset if r >= imm
|
||||
JGE JumpOp = 0x30
|
||||
// JSet jumps by offset if r & imm
|
||||
JSet JumpOp = 0x40
|
||||
// JNE jumps by offset if r != imm
|
||||
JNE JumpOp = 0x50
|
||||
// JSGT jumps by offset if signed r > signed imm
|
||||
JSGT JumpOp = 0x60
|
||||
// JSGE jumps by offset if signed r >= signed imm
|
||||
JSGE JumpOp = 0x70
|
||||
// Call builtin or user defined function from imm
|
||||
Call JumpOp = 0x80
|
||||
// Exit ends execution, with value in r0
|
||||
Exit JumpOp = 0x90
|
||||
// JLT jumps by offset if r < imm
|
||||
JLT JumpOp = 0xa0
|
||||
// JLE jumps by offset if r <= imm
|
||||
JLE JumpOp = 0xb0
|
||||
// JSLT jumps by offset if signed r < signed imm
|
||||
JSLT JumpOp = 0xc0
|
||||
// JSLE jumps by offset if signed r <= signed imm
|
||||
JSLE JumpOp = 0xd0
|
||||
)
|
||||
|
||||
// Return emits an exit instruction.
|
||||
//
|
||||
// Requires a return value in R0.
|
||||
func Return() Instruction {
|
||||
return Instruction{
|
||||
OpCode: OpCode(JumpClass).SetJumpOp(Exit),
|
||||
}
|
||||
}
|
||||
|
||||
// Op returns the OpCode for a given jump source.
|
||||
func (op JumpOp) Op(source Source) OpCode {
|
||||
return OpCode(JumpClass).SetJumpOp(op).SetSource(source)
|
||||
}
|
||||
|
||||
// Imm compares 64 bit dst to 64 bit value (sign extended), and adjusts PC by offset if the condition is fulfilled.
|
||||
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.opCode(JumpClass, ImmSource),
|
||||
Dst: dst,
|
||||
Offset: -1,
|
||||
Constant: int64(value),
|
||||
}.WithReference(label)
|
||||
}
|
||||
|
||||
// Imm32 compares 32 bit dst to 32 bit value, and adjusts PC by offset if the condition is fulfilled.
|
||||
// Requires kernel 5.1.
|
||||
func (op JumpOp) Imm32(dst Register, value int32, label string) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.opCode(Jump32Class, ImmSource),
|
||||
Dst: dst,
|
||||
Offset: -1,
|
||||
Constant: int64(value),
|
||||
}.WithReference(label)
|
||||
}
|
||||
|
||||
// Reg compares 64 bit dst to 64 bit src, and adjusts PC by offset if the condition is fulfilled.
|
||||
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.opCode(JumpClass, RegSource),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Offset: -1,
|
||||
}.WithReference(label)
|
||||
}
|
||||
|
||||
// Reg32 compares 32 bit dst to 32 bit src, and adjusts PC by offset if the condition is fulfilled.
|
||||
// Requires kernel 5.1.
|
||||
func (op JumpOp) Reg32(dst, src Register, label string) Instruction {
|
||||
return Instruction{
|
||||
OpCode: op.opCode(Jump32Class, RegSource),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Offset: -1,
|
||||
}.WithReference(label)
|
||||
}
|
||||
|
||||
func (op JumpOp) opCode(class Class, source Source) OpCode {
|
||||
if op == Exit || op == Call || op == Ja {
|
||||
return InvalidOpCode
|
||||
}
|
||||
|
||||
return OpCode(class).SetJumpOp(op).SetSource(source)
|
||||
}
|
||||
|
||||
// Label adjusts PC to the address of the label.
|
||||
func (op JumpOp) Label(label string) Instruction {
|
||||
if op == Call {
|
||||
return Instruction{
|
||||
OpCode: OpCode(JumpClass).SetJumpOp(Call),
|
||||
Src: PseudoCall,
|
||||
Constant: -1,
|
||||
}.WithReference(label)
|
||||
}
|
||||
|
||||
return Instruction{
|
||||
OpCode: OpCode(JumpClass).SetJumpOp(op),
|
||||
Offset: -1,
|
||||
}.WithReference(label)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Code generated by "stringer -output jump_string.go -type=JumpOp"; DO NOT EDIT.
|
||||
|
||||
package asm
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidJumpOp-255]
|
||||
_ = x[Ja-0]
|
||||
_ = x[JEq-16]
|
||||
_ = x[JGT-32]
|
||||
_ = x[JGE-48]
|
||||
_ = x[JSet-64]
|
||||
_ = x[JNE-80]
|
||||
_ = x[JSGT-96]
|
||||
_ = x[JSGE-112]
|
||||
_ = x[Call-128]
|
||||
_ = x[Exit-144]
|
||||
_ = x[JLT-160]
|
||||
_ = x[JLE-176]
|
||||
_ = x[JSLT-192]
|
||||
_ = x[JSLE-208]
|
||||
}
|
||||
|
||||
const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp"
|
||||
|
||||
var _JumpOp_map = map[JumpOp]string{
|
||||
0: _JumpOp_name[0:2],
|
||||
16: _JumpOp_name[2:5],
|
||||
32: _JumpOp_name[5:8],
|
||||
48: _JumpOp_name[8:11],
|
||||
64: _JumpOp_name[11:15],
|
||||
80: _JumpOp_name[15:18],
|
||||
96: _JumpOp_name[18:22],
|
||||
112: _JumpOp_name[22:26],
|
||||
128: _JumpOp_name[26:30],
|
||||
144: _JumpOp_name[30:34],
|
||||
160: _JumpOp_name[34:37],
|
||||
176: _JumpOp_name[37:40],
|
||||
192: _JumpOp_name[40:44],
|
||||
208: _JumpOp_name[44:48],
|
||||
255: _JumpOp_name[48:61],
|
||||
}
|
||||
|
||||
func (i JumpOp) String() string {
|
||||
if str, ok := _JumpOp_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "JumpOp(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package asm
|
||||
|
||||
//go:generate stringer -output load_store_string.go -type=Mode,Size
|
||||
|
||||
// Mode for load and store operations
|
||||
//
|
||||
// msb lsb
|
||||
// +---+--+---+
|
||||
// |MDE|sz|cls|
|
||||
// +---+--+---+
|
||||
type Mode uint8
|
||||
|
||||
const modeMask OpCode = 0xe0
|
||||
|
||||
const (
|
||||
// InvalidMode is returned by getters when invoked
|
||||
// on non load / store OpCodes
|
||||
InvalidMode Mode = 0xff
|
||||
// ImmMode - immediate value
|
||||
ImmMode Mode = 0x00
|
||||
// AbsMode - immediate value + offset
|
||||
AbsMode Mode = 0x20
|
||||
// IndMode - indirect (imm+src)
|
||||
IndMode Mode = 0x40
|
||||
// MemMode - load from memory
|
||||
MemMode Mode = 0x60
|
||||
// XAddMode - add atomically across processors.
|
||||
XAddMode Mode = 0xc0
|
||||
)
|
||||
|
||||
// Size of load and store operations
|
||||
//
|
||||
// msb lsb
|
||||
// +---+--+---+
|
||||
// |mde|SZ|cls|
|
||||
// +---+--+---+
|
||||
type Size uint8
|
||||
|
||||
const sizeMask OpCode = 0x18
|
||||
|
||||
const (
|
||||
// InvalidSize is returned by getters when invoked
|
||||
// on non load / store OpCodes
|
||||
InvalidSize Size = 0xff
|
||||
// DWord - double word; 64 bits
|
||||
DWord Size = 0x18
|
||||
// Word - word; 32 bits
|
||||
Word Size = 0x00
|
||||
// Half - half-word; 16 bits
|
||||
Half Size = 0x08
|
||||
// Byte - byte; 8 bits
|
||||
Byte Size = 0x10
|
||||
)
|
||||
|
||||
// Sizeof returns the size in bytes.
|
||||
func (s Size) Sizeof() int {
|
||||
switch s {
|
||||
case DWord:
|
||||
return 8
|
||||
case Word:
|
||||
return 4
|
||||
case Half:
|
||||
return 2
|
||||
case Byte:
|
||||
return 1
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMemOp returns the OpCode to load a value of given size from memory.
|
||||
func LoadMemOp(size Size) OpCode {
|
||||
return OpCode(LdXClass).SetMode(MemMode).SetSize(size)
|
||||
}
|
||||
|
||||
// LoadMem emits `dst = *(size *)(src + offset)`.
|
||||
func LoadMem(dst, src Register, offset int16, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: LoadMemOp(size),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Offset: offset,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadImmOp returns the OpCode to load an immediate of given size.
|
||||
//
|
||||
// As of kernel 4.20, only DWord size is accepted.
|
||||
func LoadImmOp(size Size) OpCode {
|
||||
return OpCode(LdClass).SetMode(ImmMode).SetSize(size)
|
||||
}
|
||||
|
||||
// LoadImm emits `dst = (size)value`.
|
||||
//
|
||||
// As of kernel 4.20, only DWord size is accepted.
|
||||
func LoadImm(dst Register, value int64, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: LoadImmOp(size),
|
||||
Dst: dst,
|
||||
Constant: value,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMapPtr stores a pointer to a map in dst.
|
||||
func LoadMapPtr(dst Register, fd int) Instruction {
|
||||
if fd < 0 {
|
||||
return Instruction{OpCode: InvalidOpCode}
|
||||
}
|
||||
|
||||
return Instruction{
|
||||
OpCode: LoadImmOp(DWord),
|
||||
Dst: dst,
|
||||
Src: PseudoMapFD,
|
||||
Constant: int64(uint32(fd)),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMapValue stores a pointer to the value at a certain offset of a map.
|
||||
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
|
||||
if fd < 0 {
|
||||
return Instruction{OpCode: InvalidOpCode}
|
||||
}
|
||||
|
||||
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
|
||||
return Instruction{
|
||||
OpCode: LoadImmOp(DWord),
|
||||
Dst: dst,
|
||||
Src: PseudoMapValue,
|
||||
Constant: int64(fdAndOffset),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
|
||||
func LoadIndOp(size Size) OpCode {
|
||||
return OpCode(LdClass).SetMode(IndMode).SetSize(size)
|
||||
}
|
||||
|
||||
// LoadInd emits `dst = ntoh(*(size *)(((sk_buff *)R6)->data + src + offset))`.
|
||||
func LoadInd(dst, src Register, offset int32, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: LoadIndOp(size),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Constant: int64(offset),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAbsOp returns the OpCode for loading a value of given size from an sk_buff.
|
||||
func LoadAbsOp(size Size) OpCode {
|
||||
return OpCode(LdClass).SetMode(AbsMode).SetSize(size)
|
||||
}
|
||||
|
||||
// LoadAbs emits `r0 = ntoh(*(size *)(((sk_buff *)R6)->data + offset))`.
|
||||
func LoadAbs(offset int32, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: LoadAbsOp(size),
|
||||
Dst: R0,
|
||||
Constant: int64(offset),
|
||||
}
|
||||
}
|
||||
|
||||
// StoreMemOp returns the OpCode for storing a register of given size in memory.
|
||||
func StoreMemOp(size Size) OpCode {
|
||||
return OpCode(StXClass).SetMode(MemMode).SetSize(size)
|
||||
}
|
||||
|
||||
// StoreMem emits `*(size *)(dst + offset) = src`
|
||||
func StoreMem(dst Register, offset int16, src Register, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: StoreMemOp(size),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Offset: offset,
|
||||
}
|
||||
}
|
||||
|
||||
// StoreImmOp returns the OpCode for storing an immediate of given size in memory.
|
||||
func StoreImmOp(size Size) OpCode {
|
||||
return OpCode(StClass).SetMode(MemMode).SetSize(size)
|
||||
}
|
||||
|
||||
// StoreImm emits `*(size *)(dst + offset) = value`.
|
||||
func StoreImm(dst Register, offset int16, value int64, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: StoreImmOp(size),
|
||||
Dst: dst,
|
||||
Offset: offset,
|
||||
Constant: value,
|
||||
}
|
||||
}
|
||||
|
||||
// StoreXAddOp returns the OpCode to atomically add a register to a value in memory.
|
||||
func StoreXAddOp(size Size) OpCode {
|
||||
return OpCode(StXClass).SetMode(XAddMode).SetSize(size)
|
||||
}
|
||||
|
||||
// StoreXAdd atomically adds src to *dst.
|
||||
func StoreXAdd(dst, src Register, size Size) Instruction {
|
||||
return Instruction{
|
||||
OpCode: StoreXAddOp(size),
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Code generated by "stringer -output load_store_string.go -type=Mode,Size"; DO NOT EDIT.
|
||||
|
||||
package asm
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidMode-255]
|
||||
_ = x[ImmMode-0]
|
||||
_ = x[AbsMode-32]
|
||||
_ = x[IndMode-64]
|
||||
_ = x[MemMode-96]
|
||||
_ = x[XAddMode-192]
|
||||
}
|
||||
|
||||
const (
|
||||
_Mode_name_0 = "ImmMode"
|
||||
_Mode_name_1 = "AbsMode"
|
||||
_Mode_name_2 = "IndMode"
|
||||
_Mode_name_3 = "MemMode"
|
||||
_Mode_name_4 = "XAddMode"
|
||||
_Mode_name_5 = "InvalidMode"
|
||||
)
|
||||
|
||||
func (i Mode) String() string {
|
||||
switch {
|
||||
case i == 0:
|
||||
return _Mode_name_0
|
||||
case i == 32:
|
||||
return _Mode_name_1
|
||||
case i == 64:
|
||||
return _Mode_name_2
|
||||
case i == 96:
|
||||
return _Mode_name_3
|
||||
case i == 192:
|
||||
return _Mode_name_4
|
||||
case i == 255:
|
||||
return _Mode_name_5
|
||||
default:
|
||||
return "Mode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidSize-255]
|
||||
_ = x[DWord-24]
|
||||
_ = x[Word-0]
|
||||
_ = x[Half-8]
|
||||
_ = x[Byte-16]
|
||||
}
|
||||
|
||||
const (
|
||||
_Size_name_0 = "Word"
|
||||
_Size_name_1 = "Half"
|
||||
_Size_name_2 = "Byte"
|
||||
_Size_name_3 = "DWord"
|
||||
_Size_name_4 = "InvalidSize"
|
||||
)
|
||||
|
||||
func (i Size) String() string {
|
||||
switch {
|
||||
case i == 0:
|
||||
return _Size_name_0
|
||||
case i == 8:
|
||||
return _Size_name_1
|
||||
case i == 16:
|
||||
return _Size_name_2
|
||||
case i == 24:
|
||||
return _Size_name_3
|
||||
case i == 255:
|
||||
return _Size_name_4
|
||||
default:
|
||||
return "Size(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package asm
|
||||
|
||||
// Metadata contains metadata about an instruction.
|
||||
type Metadata struct {
|
||||
head *metaElement
|
||||
}
|
||||
|
||||
type metaElement struct {
|
||||
next *metaElement
|
||||
key, value interface{}
|
||||
}
|
||||
|
||||
// Find the element containing key.
|
||||
//
|
||||
// Returns nil if there is no such element.
|
||||
func (m *Metadata) find(key interface{}) *metaElement {
|
||||
for e := m.head; e != nil; e = e.next {
|
||||
if e.key == key {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove an element from the linked list.
|
||||
//
|
||||
// Copies as many elements of the list as necessary to remove r, but doesn't
|
||||
// perform a full copy.
|
||||
func (m *Metadata) remove(r *metaElement) {
|
||||
current := &m.head
|
||||
for e := m.head; e != nil; e = e.next {
|
||||
if e == r {
|
||||
// We've found the element we want to remove.
|
||||
*current = e.next
|
||||
|
||||
// No need to copy the tail.
|
||||
return
|
||||
}
|
||||
|
||||
// There is another element in front of the one we want to remove.
|
||||
// We have to copy it to be able to change metaElement.next.
|
||||
cpy := &metaElement{key: e.key, value: e.value}
|
||||
*current = cpy
|
||||
current = &cpy.next
|
||||
}
|
||||
}
|
||||
|
||||
// Set a key to a value.
|
||||
//
|
||||
// If value is nil, the key is removed. Avoids modifying old metadata by
|
||||
// copying if necessary.
|
||||
func (m *Metadata) Set(key, value interface{}) {
|
||||
if e := m.find(key); e != nil {
|
||||
if e.value == value {
|
||||
// Key is present and the value is the same. Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
// Key is present with a different value. Create a copy of the list
|
||||
// which doesn't have the element in it.
|
||||
m.remove(e)
|
||||
}
|
||||
|
||||
// m.head is now a linked list that doesn't contain key.
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.head = &metaElement{key: key, value: value, next: m.head}
|
||||
}
|
||||
|
||||
// Get the value of a key.
|
||||
//
|
||||
// Returns nil if no value with the given key is present.
|
||||
func (m *Metadata) Get(key interface{}) interface{} {
|
||||
if e := m.find(key); e != nil {
|
||||
return e.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestMetadata(t *testing.T) {
|
||||
var m Metadata
|
||||
|
||||
// Metadata should be the size of a pointer.
|
||||
qt.Assert(t, unsafe.Sizeof(m), qt.Equals, unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
// A lookup in a nil meta should return nil.
|
||||
qt.Assert(t, m.Get(bool(false)), qt.IsNil)
|
||||
|
||||
// We can look up anything we inserted.
|
||||
m.Set(bool(false), int(0))
|
||||
m.Set(int(1), int(1))
|
||||
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(0))
|
||||
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
|
||||
|
||||
// We have copy on write semantics
|
||||
old := m
|
||||
m.Set(bool(false), int(1))
|
||||
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(1))
|
||||
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
|
||||
qt.Assert(t, old.Get(bool(false)), qt.Equals, int(0))
|
||||
qt.Assert(t, old.Get(int(1)), qt.Equals, int(1))
|
||||
|
||||
// Newtypes are handled distinctly.
|
||||
type b bool
|
||||
m.Set(b(false), int(42))
|
||||
qt.Assert(t, m.Get(bool(false)), qt.Equals, int(1))
|
||||
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
|
||||
qt.Assert(t, m.Get(b(false)), qt.Equals, int(42))
|
||||
|
||||
// Setting nil removes a key.
|
||||
m.Set(bool(false), nil)
|
||||
qt.Assert(t, m.Get(bool(false)), qt.IsNil)
|
||||
qt.Assert(t, m.Get(int(1)), qt.Equals, int(1))
|
||||
qt.Assert(t, m.Get(b(false)), qt.Equals, int(42))
|
||||
}
|
||||
|
||||
func BenchmarkMetadata(b *testing.B) {
|
||||
// Assume that three bits of metadata on a single instruction is
|
||||
// our worst case.
|
||||
const worstCaseItems = 3
|
||||
|
||||
type t struct{}
|
||||
|
||||
b.Run("add first", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v Metadata
|
||||
v.Set(t{}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("add last", func(b *testing.B) {
|
||||
var m Metadata
|
||||
for i := 0; i < worstCaseItems-1; i++ {
|
||||
m.Set(i, i)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := m
|
||||
v.Set(t{}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("add existing", func(b *testing.B) {
|
||||
var m Metadata
|
||||
for i := 0; i < worstCaseItems-1; i++ {
|
||||
m.Set(i, i)
|
||||
}
|
||||
m.Set(t{}, 0)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := m
|
||||
v.Set(t{}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("get miss", func(b *testing.B) {
|
||||
var m Metadata
|
||||
for i := 0; i < worstCaseItems; i++ {
|
||||
m.Set(i, i)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m.Get(t{}) != nil {
|
||||
b.Fatal("got result from miss")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate stringer -output opcode_string.go -type=Class
|
||||
|
||||
// Class of operations
|
||||
//
|
||||
// msb lsb
|
||||
// +---+--+---+
|
||||
// | ?? |CLS|
|
||||
// +---+--+---+
|
||||
type Class uint8
|
||||
|
||||
const classMask OpCode = 0x07
|
||||
|
||||
const (
|
||||
// LdClass loads immediate values into registers.
|
||||
// Also used for non-standard load operations from cBPF.
|
||||
LdClass Class = 0x00
|
||||
// LdXClass loads memory into registers.
|
||||
LdXClass Class = 0x01
|
||||
// StClass stores immediate values to memory.
|
||||
StClass Class = 0x02
|
||||
// StXClass stores registers to memory.
|
||||
StXClass Class = 0x03
|
||||
// ALUClass describes arithmetic operators.
|
||||
ALUClass Class = 0x04
|
||||
// JumpClass describes jump operators.
|
||||
JumpClass Class = 0x05
|
||||
// Jump32Class describes jump operators with 32-bit comparisons.
|
||||
// Requires kernel 5.1.
|
||||
Jump32Class Class = 0x06
|
||||
// ALU64Class describes arithmetic operators in 64-bit mode.
|
||||
ALU64Class Class = 0x07
|
||||
)
|
||||
|
||||
// IsLoad checks if this is either LdClass or LdXClass.
|
||||
func (cls Class) IsLoad() bool {
|
||||
return cls == LdClass || cls == LdXClass
|
||||
}
|
||||
|
||||
// IsStore checks if this is either StClass or StXClass.
|
||||
func (cls Class) IsStore() bool {
|
||||
return cls == StClass || cls == StXClass
|
||||
}
|
||||
|
||||
func (cls Class) isLoadOrStore() bool {
|
||||
return cls.IsLoad() || cls.IsStore()
|
||||
}
|
||||
|
||||
// IsALU checks if this is either ALUClass or ALU64Class.
|
||||
func (cls Class) IsALU() bool {
|
||||
return cls == ALUClass || cls == ALU64Class
|
||||
}
|
||||
|
||||
// IsJump checks if this is either JumpClass or Jump32Class.
|
||||
func (cls Class) IsJump() bool {
|
||||
return cls == JumpClass || cls == Jump32Class
|
||||
}
|
||||
|
||||
func (cls Class) isJumpOrALU() bool {
|
||||
return cls.IsJump() || cls.IsALU()
|
||||
}
|
||||
|
||||
// OpCode is a packed eBPF opcode.
|
||||
//
|
||||
// Its encoding is defined by a Class value:
|
||||
//
|
||||
// msb lsb
|
||||
// +----+-+---+
|
||||
// | ???? |CLS|
|
||||
// +----+-+---+
|
||||
type OpCode uint8
|
||||
|
||||
// InvalidOpCode is returned by setters on OpCode
|
||||
const InvalidOpCode OpCode = 0xff
|
||||
|
||||
// rawInstructions returns the number of BPF instructions required
|
||||
// to encode this opcode.
|
||||
func (op OpCode) rawInstructions() int {
|
||||
if op.IsDWordLoad() {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (op OpCode) IsDWordLoad() bool {
|
||||
return op == LoadImmOp(DWord)
|
||||
}
|
||||
|
||||
// Class returns the class of operation.
|
||||
func (op OpCode) Class() Class {
|
||||
return Class(op & classMask)
|
||||
}
|
||||
|
||||
// Mode returns the mode for load and store operations.
|
||||
func (op OpCode) Mode() Mode {
|
||||
if !op.Class().isLoadOrStore() {
|
||||
return InvalidMode
|
||||
}
|
||||
return Mode(op & modeMask)
|
||||
}
|
||||
|
||||
// Size returns the size for load and store operations.
|
||||
func (op OpCode) Size() Size {
|
||||
if !op.Class().isLoadOrStore() {
|
||||
return InvalidSize
|
||||
}
|
||||
return Size(op & sizeMask)
|
||||
}
|
||||
|
||||
// Source returns the source for branch and ALU operations.
|
||||
func (op OpCode) Source() Source {
|
||||
if !op.Class().isJumpOrALU() || op.ALUOp() == Swap {
|
||||
return InvalidSource
|
||||
}
|
||||
return Source(op & sourceMask)
|
||||
}
|
||||
|
||||
// ALUOp returns the ALUOp.
|
||||
func (op OpCode) ALUOp() ALUOp {
|
||||
if !op.Class().IsALU() {
|
||||
return InvalidALUOp
|
||||
}
|
||||
return ALUOp(op & aluMask)
|
||||
}
|
||||
|
||||
// Endianness returns the Endianness for a byte swap instruction.
|
||||
func (op OpCode) Endianness() Endianness {
|
||||
if op.ALUOp() != Swap {
|
||||
return InvalidEndian
|
||||
}
|
||||
return Endianness(op & endianMask)
|
||||
}
|
||||
|
||||
// JumpOp returns the JumpOp.
|
||||
// Returns InvalidJumpOp if it doesn't encode a jump.
|
||||
func (op OpCode) JumpOp() JumpOp {
|
||||
if !op.Class().IsJump() {
|
||||
return InvalidJumpOp
|
||||
}
|
||||
|
||||
jumpOp := JumpOp(op & jumpMask)
|
||||
|
||||
// Some JumpOps are only supported by JumpClass, not Jump32Class.
|
||||
if op.Class() == Jump32Class && (jumpOp == Exit || jumpOp == Call || jumpOp == Ja) {
|
||||
return InvalidJumpOp
|
||||
}
|
||||
|
||||
return jumpOp
|
||||
}
|
||||
|
||||
// SetMode sets the mode on load and store operations.
|
||||
//
|
||||
// Returns InvalidOpCode if op is of the wrong class.
|
||||
func (op OpCode) SetMode(mode Mode) OpCode {
|
||||
if !op.Class().isLoadOrStore() || !valid(OpCode(mode), modeMask) {
|
||||
return InvalidOpCode
|
||||
}
|
||||
return (op & ^modeMask) | OpCode(mode)
|
||||
}
|
||||
|
||||
// SetSize sets the size on load and store operations.
|
||||
//
|
||||
// Returns InvalidOpCode if op is of the wrong class.
|
||||
func (op OpCode) SetSize(size Size) OpCode {
|
||||
if !op.Class().isLoadOrStore() || !valid(OpCode(size), sizeMask) {
|
||||
return InvalidOpCode
|
||||
}
|
||||
return (op & ^sizeMask) | OpCode(size)
|
||||
}
|
||||
|
||||
// SetSource sets the source on jump and ALU operations.
|
||||
//
|
||||
// Returns InvalidOpCode if op is of the wrong class.
|
||||
func (op OpCode) SetSource(source Source) OpCode {
|
||||
if !op.Class().isJumpOrALU() || !valid(OpCode(source), sourceMask) {
|
||||
return InvalidOpCode
|
||||
}
|
||||
return (op & ^sourceMask) | OpCode(source)
|
||||
}
|
||||
|
||||
// SetALUOp sets the ALUOp on ALU operations.
|
||||
//
|
||||
// Returns InvalidOpCode if op is of the wrong class.
|
||||
func (op OpCode) SetALUOp(alu ALUOp) OpCode {
|
||||
if !op.Class().IsALU() || !valid(OpCode(alu), aluMask) {
|
||||
return InvalidOpCode
|
||||
}
|
||||
return (op & ^aluMask) | OpCode(alu)
|
||||
}
|
||||
|
||||
// SetJumpOp sets the JumpOp on jump operations.
|
||||
//
|
||||
// Returns InvalidOpCode if op is of the wrong class.
|
||||
func (op OpCode) SetJumpOp(jump JumpOp) OpCode {
|
||||
if !op.Class().IsJump() || !valid(OpCode(jump), jumpMask) {
|
||||
return InvalidOpCode
|
||||
}
|
||||
|
||||
newOp := (op & ^jumpMask) | OpCode(jump)
|
||||
|
||||
// Check newOp is legal.
|
||||
if newOp.JumpOp() == InvalidJumpOp {
|
||||
return InvalidOpCode
|
||||
}
|
||||
|
||||
return newOp
|
||||
}
|
||||
|
||||
func (op OpCode) String() string {
|
||||
var f strings.Builder
|
||||
|
||||
switch class := op.Class(); {
|
||||
case class.isLoadOrStore():
|
||||
f.WriteString(strings.TrimSuffix(class.String(), "Class"))
|
||||
|
||||
mode := op.Mode()
|
||||
f.WriteString(strings.TrimSuffix(mode.String(), "Mode"))
|
||||
|
||||
switch op.Size() {
|
||||
case DWord:
|
||||
f.WriteString("DW")
|
||||
case Word:
|
||||
f.WriteString("W")
|
||||
case Half:
|
||||
f.WriteString("H")
|
||||
case Byte:
|
||||
f.WriteString("B")
|
||||
}
|
||||
|
||||
case class.IsALU():
|
||||
f.WriteString(op.ALUOp().String())
|
||||
|
||||
if op.ALUOp() == Swap {
|
||||
// Width for Endian is controlled by Constant
|
||||
f.WriteString(op.Endianness().String())
|
||||
} else {
|
||||
if class == ALUClass {
|
||||
f.WriteString("32")
|
||||
}
|
||||
|
||||
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
|
||||
}
|
||||
|
||||
case class.IsJump():
|
||||
f.WriteString(op.JumpOp().String())
|
||||
|
||||
if class == Jump32Class {
|
||||
f.WriteString("32")
|
||||
}
|
||||
|
||||
if jop := op.JumpOp(); jop != Exit && jop != Call {
|
||||
f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
|
||||
}
|
||||
|
||||
return f.String()
|
||||
}
|
||||
|
||||
// valid returns true if all bits in value are covered by mask.
|
||||
func valid(value, mask OpCode) bool {
|
||||
return value & ^mask == 0
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Code generated by "stringer -output opcode_string.go -type=Class"; DO NOT EDIT.
|
||||
|
||||
package asm
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[LdClass-0]
|
||||
_ = x[LdXClass-1]
|
||||
_ = x[StClass-2]
|
||||
_ = x[StXClass-3]
|
||||
_ = x[ALUClass-4]
|
||||
_ = x[JumpClass-5]
|
||||
_ = x[Jump32Class-6]
|
||||
_ = x[ALU64Class-7]
|
||||
}
|
||||
|
||||
const _Class_name = "LdClassLdXClassStClassStXClassALUClassJumpClassJump32ClassALU64Class"
|
||||
|
||||
var _Class_index = [...]uint8{0, 7, 15, 22, 30, 38, 47, 58, 68}
|
||||
|
||||
func (i Class) String() string {
|
||||
if i >= Class(len(_Class_index)-1) {
|
||||
return "Class(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Class_name[_Class_index[i]:_Class_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestGetSetJumpOp(t *testing.T) {
|
||||
test := func(class Class, op JumpOp, valid bool) {
|
||||
t.Run(fmt.Sprintf("%s-%s", class, op), func(t *testing.T) {
|
||||
opcode := OpCode(class).SetJumpOp(op)
|
||||
|
||||
if valid {
|
||||
qt.Assert(t, opcode, qt.Not(qt.Equals), InvalidOpCode)
|
||||
qt.Assert(t, opcode.JumpOp(), qt.Equals, op)
|
||||
} else {
|
||||
qt.Assert(t, opcode, qt.Equals, InvalidOpCode)
|
||||
qt.Assert(t, opcode.JumpOp(), qt.Equals, InvalidJumpOp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Exit, call and JA aren't allowed with Jump32
|
||||
test(Jump32Class, Exit, false)
|
||||
test(Jump32Class, Call, false)
|
||||
test(Jump32Class, Ja, false)
|
||||
|
||||
// But are with Jump
|
||||
test(JumpClass, Exit, true)
|
||||
test(JumpClass, Call, true)
|
||||
test(JumpClass, Ja, true)
|
||||
|
||||
// All other ops work
|
||||
for _, op := range []JumpOp{
|
||||
JEq,
|
||||
JGT,
|
||||
JGE,
|
||||
JSet,
|
||||
JNE,
|
||||
JSGT,
|
||||
JSGE,
|
||||
JLT,
|
||||
JLE,
|
||||
JSLT,
|
||||
JSLE,
|
||||
} {
|
||||
test(Jump32Class, op, true)
|
||||
test(JumpClass, op, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Register is the source or destination of most operations.
|
||||
type Register uint8
|
||||
|
||||
// R0 contains return values.
|
||||
const R0 Register = 0
|
||||
|
||||
// Registers for function arguments.
|
||||
const (
|
||||
R1 Register = R0 + 1 + iota
|
||||
R2
|
||||
R3
|
||||
R4
|
||||
R5
|
||||
)
|
||||
|
||||
// Callee saved registers preserved by function calls.
|
||||
const (
|
||||
R6 Register = R5 + 1 + iota
|
||||
R7
|
||||
R8
|
||||
R9
|
||||
)
|
||||
|
||||
// Read-only frame pointer to access stack.
|
||||
const (
|
||||
R10 Register = R9 + 1
|
||||
RFP = R10
|
||||
)
|
||||
|
||||
// Pseudo registers used by 64bit loads and jumps
|
||||
const (
|
||||
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
|
||||
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
|
||||
PseudoCall = R1 // BPF_PSEUDO_CALL
|
||||
PseudoFunc = R4 // BPF_PSEUDO_FUNC
|
||||
PseudoKfuncCall = R2 // BPF_PSEUDO_KFUNC_CALL
|
||||
)
|
||||
|
||||
func (r Register) String() string {
|
||||
v := uint8(r)
|
||||
if v == 10 {
|
||||
return "rfp"
|
||||
}
|
||||
return fmt.Sprintf("r%d", v)
|
||||
}
|
||||
Reference in New Issue
Block a user