Skip to content

Commit

Permalink
cmd/asm: improve detector for incorrect R15 usage when dynamic linking
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
randall77 committed Mar 15, 2023
1 parent 70308d1 commit 455168d
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 32 deletions.
97 changes: 97 additions & 0 deletions src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s
Expand Up @@ -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
149 changes: 117 additions & 32 deletions src/cmd/internal/obj/x86/obj6.go
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -1320,22 +1405,22 @@ 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)
}
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)
Expand Down

0 comments on commit 455168d

Please sign in to comment.