]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/asm: improve detector for incorrect R15 usage when dynamic linking
authorKeith Randall <khr@golang.org>
Tue, 14 Mar 2023 18:34:51 +0000 (11:34 -0700)
committerKeith Randall <khr@google.com>
Wed, 15 Mar 2023 20:45:41 +0000 (20:45 +0000)
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>

src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s
src/cmd/internal/obj/x86/obj6.go

index e110ee8118583048e962e35a8b4bc1d4d8cf3cc6..4bf58a39a4326615b5cf94875dce0faa7f316bf0 100644 (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
index fc0a9c44848bfa42cd29d1b72afc7d4ac72dbcaf..6b6aa8809a6b7714251b6b914b2bd35d1922afe6 100644 (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)