feat(lint): abi0 register-args rule and go-asm width model
This commit is contained in:
+33
-14
@@ -77,6 +77,7 @@ const (
|
||||
CodeInvalidFlag = "invalid-textflag"
|
||||
CodeStackImbalance = "stack-imbalance"
|
||||
CodeRegisterWidthMismatch = "register-width-mismatch"
|
||||
CodeABI0RegisterArgs = "abi0-register-args"
|
||||
)
|
||||
|
||||
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
|
||||
@@ -438,6 +439,16 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
||||
}
|
||||
}
|
||||
|
||||
// ABI0 argument-read check: every parameter the // func signature
|
||||
// declares must be read from the frame (name+offset(FP)). A kernel that
|
||||
// declares parameters but never touches their frame slots is almost
|
||||
// certainly reading its arguments from registers (AX, BX, …), which is
|
||||
// the classic ABI0 port bug: Go pushes the arguments on the stack and
|
||||
// the register content is whatever the caller left behind.
|
||||
if cfg.Arch == arch.AMD64 && !cfg.Disable[CodeABI0RegisterArgs] && !hasMacro && instrCount > 0 {
|
||||
out = append(out, checkABI0Args(t)...)
|
||||
}
|
||||
|
||||
// FUNCDATA / PCDATA structural validation.
|
||||
out = append(out, checkFuncdata(t, cfg)...)
|
||||
|
||||
@@ -730,9 +741,15 @@ func isSPReg(op *ast.Operand, a arch.Arch) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// checkRegisterWidth detects amd64 register-width mismatches: a Q-suffix
|
||||
// instruction (64-bit) using a 32-bit register, or an L/W/B-suffix
|
||||
// instruction using a 64-bit register.
|
||||
// checkRegisterWidth detects amd64 register-width mismatches. The naming
|
||||
// truth of the Go assembler governs: AX, BX, CX, DX, SI, DI, BP, SP and
|
||||
// R8–R15 ARE the 64-bit register names (there are no separate EAX/RAX
|
||||
// spellings in go tool asm), and AL–DH are the byte forms. The width comes
|
||||
// from the opcode suffix, so an L/W operation over a canonical 64-bit name is
|
||||
// the normal, correct spelling — flagging it is pure noise on real kernels.
|
||||
// What remains worth flagging: a Q (64-bit) operation over a narrower spelled
|
||||
// register (EAX under the gasm alias extension, or a byte form), and byte
|
||||
// registers in L/W operations.
|
||||
func checkRegisterWidth(mnem string, ops []*ast.Operand) string {
|
||||
// Determine expected width from mnemonic suffix.
|
||||
var expected int // 0=unknown, 8/4/2/1=bytes
|
||||
@@ -757,28 +774,30 @@ func checkRegisterWidth(mnem string, ops []*ast.Operand) string {
|
||||
if regWidth == 0 {
|
||||
continue // not a register or unknown
|
||||
}
|
||||
if expected == 8 && regWidth == 4 {
|
||||
return fmt.Sprintf("%s uses 32-bit register %s (expected 64-bit)", mnem, op.Addr.Sym.Name)
|
||||
if expected == 8 && regWidth < 8 {
|
||||
return fmt.Sprintf("%s uses %d-bit register %s (expected 64-bit)", mnem, regWidth*8, op.Addr.Sym.Name)
|
||||
}
|
||||
if expected == 4 && regWidth == 8 {
|
||||
return fmt.Sprintf("%s uses 64-bit register %s (expected 32-bit)", mnem, op.Addr.Sym.Name)
|
||||
if (expected == 4 || expected == 2) && regWidth == 1 {
|
||||
return fmt.Sprintf("%s uses 8-bit register %s (expected a wider register)", mnem, op.Addr.Sym.Name)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// amd64RegWidth returns the width in bytes of an amd64 register name.
|
||||
// amd64RegWidth returns the width in bytes of an amd64 register name under
|
||||
// the Go assembler's naming model: the canonical word names (AX…SP, R8–R15)
|
||||
// are 64-bit, AL–DH are the 8-bit forms, and the R/E-prefixed spellings are
|
||||
// the gasm alias extension with their intuitive widths.
|
||||
func amd64RegWidth(name string) int {
|
||||
switch name {
|
||||
case "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp",
|
||||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15":
|
||||
case "ax", "bx", "cx", "dx", "si", "di", "bp", "sp",
|
||||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
||||
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp":
|
||||
return 8
|
||||
case "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp":
|
||||
return 4
|
||||
case "ax", "bx", "cx", "dx", "si", "di", "bp", "sp":
|
||||
return 2
|
||||
case "al", "bl", "cl", "dl", "ah", "bh", "ch", "dh":
|
||||
return 1
|
||||
case "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp":
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user