mmcloughlin / avo

Generate x86 Assembly with Go
BSD 3-Clause "New" or "Revised" License
2.72k stars 89 forks source link

proposal: debugging support #126

Open mmcloughlin opened 4 years ago

mmcloughlin commented 4 years ago

It's currently very hard to debug complex avo programs. We could provide some simple functions to make this easier. This could even form a debug package in the hypothetical standard library #117. For example:

mmcloughlin commented 4 years ago

One useful feature would be the ability to define assertions in a "debug build" that would panic on failure. One way this could work is raising a panic from the assembly code. How does this actually work?

package main

//go:noinline
func raise() {
    panic("fail")
}

func main() {
    raise()
}

This compiles to:

TEXT main.raise(SB) /Users/michaelmcloughlin/Development/misc/panicasm/panic.go
  panic.go:4        0x104e580       65488b0c2530000000  MOVQ GS:0x30, CX            
  panic.go:4        0x104e589       483b6110        CMPQ 0x10(CX), SP           
  panic.go:4        0x104e58d       762c            JBE 0x104e5bb               
  panic.go:4        0x104e58f       4883ec18        SUBQ $0x18, SP              
  panic.go:4        0x104e593       48896c2410      MOVQ BP, 0x10(SP)           
  panic.go:4        0x104e598       488d6c2410      LEAQ 0x10(SP), BP           
  panic.go:5        0x104e59d       488d059cac0000      LEAQ runtime.types+44000(SB), AX    
  panic.go:5        0x104e5a4       48890424        MOVQ AX, 0(SP)              
  panic.go:5        0x104e5a8       488d05d1c40200      LEAQ main.statictmp_0(SB), AX       
  panic.go:5        0x104e5af       4889442408      MOVQ AX, 0x8(SP)            
  panic.go:5        0x104e5b4       e8a740fdff      CALL runtime.gopanic(SB)        
  panic.go:5        0x104e5b9       0f0b            UD2                 
  panic.go:4        0x104e5bb       e80085ffff      CALL runtime.morestack_noctxt(SB)   
  panic.go:4        0x104e5c0       ebbe            JMP main.raise(SB)          
  :-1           0x104e5c2       cc          INT $0x3                
...
mmcloughlin commented 4 years ago

gopanic takes an interface{}:

https://github.com/golang/go/blob/726b1bf9871f4905d85a53051301f636e8273328/src/runtime/asm_amd64.s#L1659

mmcloughlin commented 4 years ago

Discussed on Slack: https://gophers.slack.com/archives/C0VP8EF3R/p1562989032212400. Some great contributions from @acln0 @dgryski @agnivade. Ideas:

Check for any function that calls a `throw`. That panics.
// called from assembly
func badmcall(fn func(*g)) {
    throw("runtime: mcall called on m->g0 stack")
}

func badmcall2(fn func(*g)) {
    throw("runtime: mcall function returned")
}
These are called from assembly. And they are called on panic conditions

panicdivide called from here:

https://github.com/golang/go/blob/726b1bf9871f4905d85a53051301f636e8273328/src/runtime/vlop_arm.s#L119

runtime has many examples of calling badmcall. throw is defined here

https://github.com/golang/go/blob/726b1bf9871f4905d85a53051301f636e8273328/src/runtime/panic.go#L764

mmcloughlin commented 4 years ago

@zeebo also makes a good suggestion regarding breakpoints. This hypothetical avo/debug package could also have a Breakpoint() function. See:

https://golang.org/pkg/runtime/#Breakpoint

This is simply implemented as the INT3 instruction

https://github.com/golang/go/blob/21f548400650e4e7047202ee1894ee6ddd44de82/src/runtime/asm_amd64.s#L238-L240

Not clear to me right now why this is encoded with BYTE 0xCC. Does the Go assembler not support INT3? It seems the avo instruction database doesn't have INT3 or INT right now.

mmcloughlin commented 4 years ago

@klauspost shared some adhoc code he was using to help debug his S2 implementation https://gophers.slack.com/archives/C0VP8EF3R/p1579706207182000

var assertCounter int

// insert extra checks here and there.
const debug = false

// assert will insert code if debug is enabled.
// The code should jump to 'ok' is assertion is success.
func assert(fn func(ok LabelRef)) {
    if debug {
        caller := [100]uintptr{0}
        runtime.Callers(2, caller[:])
        frame, _ := runtime.CallersFrames(caller[:]).Next()

        ok := fmt.Sprintf("assert_check_%d_ok_srcline_%d", assertCounter, frame.Line)
        fn(LabelRef(ok))
        // Emit several since delve is imprecise.
        INT(Imm(3))
        INT(Imm(3))
        Label(ok)
        assertCounter++
    }
}

Example:

        assert(func(ok LabelRef) {
            // Check if somebody changed src
            tmp := GP64()
            MOVQ(srcBaseQ, tmp)
            CMPQ(tmp, src)
            JEQ(ok)
        })