cmd/asm: improve detector for incorrect R15 usage when dynamic linking

Fixes #58632

Change-Id: Idb19af2ac693ea5920da57c1808f1bc02702929d
Reviewed-on: https://go-review.googlesource.com/c/go/+/476295
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
This commit is contained in:
Keith Randall 2023-03-14 11:34:51 -07:00 committed by Keith Randall
parent 70308d1375
commit 455168d113
2 changed files with 214 additions and 32 deletions

View File

@ -72,3 +72,100 @@ one:
TEXT ·a13(SB), 0, $0-0
MULXQ runtime·writeBarrier(SB), AX, CX
RET
// Various special cases in the use-R15-after-global-access-when-dynlinking check.
// See issue 58632.
TEXT ·a14(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MULXQ R15, AX, BX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a15(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MULXQ AX, R15, BX
ADDQ $1, R15
RET
TEXT ·a16(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MULXQ AX, BX, R15
ADDQ $1, R15
RET
TEXT ·a17(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MOVQ (R15), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a18(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MOVQ (CX)(R15*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a19(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MOVQ AX, (R15) // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a20(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MOVQ AX, (CX)(R15*1) // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a21(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
MOVBLSX AX, R15
ADDQ $1, R15
RET
TEXT ·a22(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
PMOVMSKB X0, R15
ADDQ $1, R15
RET
TEXT ·a23(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
LEAQ (AX)(CX*1), R15
RET
TEXT ·a24(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
LEAQ (R15)(AX*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a25(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
LEAQ (AX)(R15*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a26(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
IMUL3Q $33, AX, R15
ADDQ $1, R15
RET
TEXT ·a27(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
IMUL3Q $33, R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a28(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
PEXTRD $0, X0, R15
ADDQ $1, R15
RET
TEXT ·a29(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
VPEXTRD $0, X0, R15
ADDQ $1, R15
RET
TEXT ·a30(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
BSFQ R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a31(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
BSFQ AX, R15
ADDQ $1, R15
RET
TEXT ·a32(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
POPCNTL R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here"
RET
TEXT ·a33(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
POPCNTL AX, R15
ADDQ $1, R15
RET
TEXT ·a34(SB), 0, $0-0
CMPL runtime·writeBarrier(SB), $0
SHLXQ AX, CX, R15
ADDQ $1, R15
RET

View File

@ -1254,29 +1254,6 @@ func progMentionsR15(p *obj.Prog) bool {
return addrMentionsR15(&p.From) || addrMentionsR15(&p.To) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3())
}
// progOverwritesR15 reports whether p writes to R15 and does not depend on
// the previous value of R15.
func progOverwritesR15(p *obj.Prog) bool {
if !(p.To.Type == obj.TYPE_REG && isR15(p.To.Reg)) {
// Not writing to R15.
return false
}
if (p.As == AXORL || p.As == AXORQ) && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) {
// These look like uses of R15, but aren't, so we must detect these
// before the use check below.
return true
}
if addrMentionsR15(&p.From) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3()) {
// use before overwrite
return false
}
if p.As == AMOVL || p.As == AMOVQ || p.As == APOPQ {
return true
// TODO: MOVB might be ok if we only ever use R15B.
}
return false
}
func addrUsesGlobal(a *obj.Addr) bool {
if a == nil {
return false
@ -1296,6 +1273,114 @@ func progUsesGlobal(p *obj.Prog) bool {
return addrUsesGlobal(&p.From) || addrUsesGlobal(&p.To) || addrUsesGlobal(p.GetFrom3())
}
type rwMask int
const (
readFrom rwMask = 1 << iota
readTo
readReg
readFrom3
writeFrom
writeTo
writeReg
writeFrom3
)
// progRW returns a mask describing the effects of the instruction p.
// Note: this isn't exhaustively accurate. It is only currently used for detecting
// reads/writes to R15, so SSE register behavior isn't fully correct, and
// other weird cases (e.g. writes to DX by CLD) also aren't captured.
func progRW(p *obj.Prog) rwMask {
var m rwMask
// Default for most instructions
if p.From.Type != obj.TYPE_NONE {
m |= readFrom
}
if p.To.Type != obj.TYPE_NONE {
// Most x86 instructions update the To value
m |= readTo | writeTo
}
if p.Reg != 0 {
m |= readReg
}
if p.GetFrom3() != nil {
m |= readFrom3
}
// Lots of exceptions to the above defaults.
name := p.As.String()
if strings.HasPrefix(name, "MOV") || strings.HasPrefix(name, "PMOV") {
// MOV instructions don't read To.
m &^= readTo
}
switch p.As {
case APOPW, APOPL, APOPQ,
ALEAL, ALEAQ,
AIMUL3W, AIMUL3L, AIMUL3Q,
APEXTRB, APEXTRW, APEXTRD, APEXTRQ, AVPEXTRB, AVPEXTRW, AVPEXTRD, AVPEXTRQ, AEXTRACTPS,
ABSFW, ABSFL, ABSFQ, ABSRW, ABSRL, ABSRQ, APOPCNTW, APOPCNTL, APOPCNTQ, ALZCNTW, ALZCNTL, ALZCNTQ,
ASHLXL, ASHLXQ, ASHRXL, ASHRXQ, ASARXL, ASARXQ:
// These instructions are pure writes to To. They don't use its old value.
m &^= readTo
case AXORL, AXORQ:
// Register-clearing idiom doesn't read previous value.
if p.From.Type == obj.TYPE_REG && p.To.Type == obj.TYPE_REG && p.From.Reg == p.To.Reg {
m &^= readFrom | readTo
}
case AMULXL, AMULXQ:
// These are write-only to both To and From3.
m &^= readTo | readFrom3
m |= writeFrom3
}
return m
}
// progReadsR15 reports whether p reads the register R15.
func progReadsR15(p *obj.Prog) bool {
m := progRW(p)
if m&readFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) {
return true
}
if m&readTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) {
return true
}
if m&readReg != 0 && isR15(p.Reg) {
return true
}
if m&readFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) {
return true
}
// reads of the index registers
if p.From.Type == obj.TYPE_MEM && (isR15(p.From.Reg) || isR15(p.From.Index)) {
return true
}
if p.To.Type == obj.TYPE_MEM && (isR15(p.To.Reg) || isR15(p.To.Index)) {
return true
}
if f3 := p.GetFrom3(); f3 != nil && f3.Type == obj.TYPE_MEM && (isR15(f3.Reg) || isR15(f3.Index)) {
return true
}
return false
}
// progWritesR15 reports whether p writes the register R15.
func progWritesR15(p *obj.Prog) bool {
m := progRW(p)
if m&writeFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) {
return true
}
if m&writeTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) {
return true
}
if m&writeReg != 0 && isR15(p.Reg) {
return true
}
if m&writeFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) {
return true
}
return false
}
func errorCheck(ctxt *obj.Link, s *obj.LSym) {
// When dynamic linking, R15 is used to access globals. Reject code that
// uses R15 after a global variable access.
@ -1320,6 +1405,15 @@ func errorCheck(ctxt *obj.Link, s *obj.LSym) {
for len(work) > 0 {
p := work[len(work)-1]
work = work[:len(work)-1]
if progReadsR15(p) {
pos := ctxt.PosTable.Pos(p.Pos)
ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p)
break // only report one error
}
if progWritesR15(p) {
// R15 is overwritten by this instruction. Its value is not junk any more.
continue
}
if q := p.To.Target(); q != nil && q.Mark&markBit == 0 {
q.Mark |= markBit
work = append(work, q)
@ -1327,15 +1421,6 @@ func errorCheck(ctxt *obj.Link, s *obj.LSym) {
if p.As == obj.AJMP || p.As == obj.ARET {
continue // no fallthrough
}
if progMentionsR15(p) {
if progOverwritesR15(p) {
// R15 is overwritten by this instruction. Its value is not junk any more.
continue
}
pos := ctxt.PosTable.Pos(p.Pos)
ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p)
break // only report one error
}
if q := p.Link; q != nil && q.Mark&markBit == 0 {
q.Mark |= markBit
work = append(work, q)