golang中的panic反番,recover執(zhí)行過程?

上篇文章golang中defer的執(zhí)行過程是怎樣的沙热?介紹了一下defer的執(zhí)行過程,本篇是上一篇的引申罢缸,主要介紹panic篙贸、recover的底層分析,如果沒有讀過上一篇文章枫疆,可以先去讀一下在看這篇爵川。
總共分3部分講解:

1 panic

2 defer panic

3 defer panic recover

=======================

環(huán)境:go version go1.12.5 linux/amd64

1 panic

golang中的異常總共分為4中:

  • 編譯器捕獲的
  • 直接手動(dòng)panic
  • golang捕獲的
  • 系統(tǒng)捕獲的
編譯器捕獲的

1/0 我們知道被除數(shù)是不能等于0的息楔,所以這種錯(cuò)誤是編譯不過去的寝贡,會(huì)提示:
./main.go:7:8: division by zero

直接手動(dòng)panic

示例代碼:

package main
func main() {
    panic("panic error!值依!")
}

編譯成匯編代碼看panic函數(shù)會(huì)指向底層哪個(gè)函數(shù):
go tool compile -S main.go > main.s

0x0034 00052 (main.go:4)    CALL    runtime.gopanic(SB)

查看gopanic(SB)實(shí)現(xiàn)圃泡,先粗略看一下代碼的含義一些解釋在代碼中已經(jīng)注解:

func gopanic(e interface{}) {
    gp := getg() //獲取當(dāng)前的g

        ....省略不重要的

    var p _panic //_panic原型
    p.arg = e //將panic參數(shù)存入arg參數(shù)
    p.link = gp._panic  //將p.link綁定到當(dāng)前的g的_panic上。
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) //將p綁定到g的鏈表頭愿险。
      
    atomic.Xadd(&runningPanicDefers, 1)

    for {
        d := gp._defer
        if d == nil {
            break
        }

        if d.started {
            if d._panic != nil {
                d._panic.aborted = true
            }
            d._panic = nil
            d.fn = nil
            gp._defer = d.link
            freedefer(d)
            continue
        }

        d.started = true

    
        d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))//將p綁定到g的鏈表頭颇蜡。

        p.argp = unsafe.Pointer(getargp(0))
        reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) //調(diào)用g上的defer(源程序中如果沒有defer函數(shù),編譯器會(huì)生成一個(gè)并綁定到g._defer上)
        p.argp = nil

        if gp._defer != d {
            throw("bad defer entry in panic")
        }
              //脫鏈
        d._panic = nil
        d.fn = nil
        gp._defer = d.link 

        pc := d.pc
        sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
        freedefer(d)
        if p.recovered { //先忽略講到recover時(shí)候在說
            .....
        }
    }

    preprintpanics(gp._panic)
        //循環(huán)打印panic
    fatalpanic(gp._panic) // should not return
    *(*int)(nil) = 0      // not reached
}

我們發(fā)現(xiàn)panic的原型是_panic,去看一下定義:

type _panic struct {
    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    arg       interface{}    // argument to panic
    link      *_panic        // link to earlier panic
    recovered bool           // whether this panic is over
    aborted   bool           // the panic was aborted
}

發(fā)現(xiàn)是個(gè)結(jié)構(gòu)體類型辆亏,里面的類型我們?cè)谡{(diào)試代碼的時(shí)候在去探究具體的含義风秤。
接下來我們就用gdb跟蹤一下上面的源碼示例。

go build -o main
gdb main

進(jìn)入gdb界面并斷點(diǎn)到panic函數(shù)行見下圖:


圖1

按s進(jìn)入到gopanic(interface)中褒链。
發(fā)現(xiàn)這條語句:

gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

原來當(dāng)前的gp定義(由于不是講goroutine 這里就不貼gp的原型了)中有_panc字段作為鏈表頭,而_panic結(jié)構(gòu)體中有l(wèi)ink字段唁情。不難看出和defer同理:從goroutine._panic作為頭,然后用_painc.link作為鏈接組成了一個(gè)鏈表的數(shù)據(jù)結(jié)構(gòu)甫匹。之所以是鏈表是因?yàn)閞ecover到panic時(shí)候甸鸟,recover中也有可能有panic,例如見下方代碼:

if err := recover(); err != nil {
  panic("go on panic xitehip")
}

deferd函數(shù)也會(huì)繼續(xù)有panic兵迅。下方講到recover時(shí)候詳細(xì)講解抢韭。
執(zhí)行上面的語句此時(shí)的鏈表示意結(jié)構(gòu)見下方:
gp._panic => p.link => gp._panic(之前的鏈表頭)
繼續(xù)往下走:

圖2

運(yùn)行到reflectcall()函數(shù),發(fā)現(xiàn)這個(gè)函數(shù)總共有5個(gè)參數(shù):

func reflectcall(argtype *_type, fn, arg unsafe.Pointer, argsize uint32, retoffset uint32)

從第二個(gè)參數(shù)可知這個(gè)是函數(shù)指針恍箭,猜測(cè)這個(gè)reflectcall是調(diào)用我們實(shí)參unsafe.Point(d.fn)的刻恭。根據(jù)源碼中的定義 d := gp._defer可知變量d就是上文我們說的g._defer。那馬上有疑問了,這個(gè)例子里根本沒有用到defer關(guān)鍵字鳍贾,就不會(huì)調(diào)用deferproc(SB)生成defer鞍匾。那只有一種可能就是編譯器幫我們做了生成了一個(gè)defer函數(shù)然后綁定到了g._defer的鏈表頭上。
繼續(xù)看reflectcall函數(shù)見下圖x:

圖x

用disass命令查看一下匯編代碼骑科,綠線處的是即將調(diào)用的reflectcall函數(shù)橡淑。紅線處是它的下一條指令,記住它的地址0x0000000000423025咆爽,我們?nèi)タ匆幌聄eflectcall函數(shù)執(zhí)行完的返回值是如何指向到紅線處的指令的梁棠。
見下方匯編代碼:

//runtime/asm_amd64.s

TEXT ·reflectcall(SB), NOSPLIT, $0-32
    MOVLQZX argsize+24(FP), CX
    DISPATCH(runtime·call32, 32)
    DISPATCH(runtime·call64, 64)
        .....
    MOVQ    $runtime·badreflectcall(SB), AX
    JMP AX
//runtime/asm_amd64.s
#define DISPATCH(NAME,MAXSIZE)      \
    CMPQ    CX, $MAXSIZE;       \
    JA  3(PC);          \
    MOVQ    $NAME(SB), AX;      \
    JMP AX
//runtime/asm_amd64.s
#define CALLFN(NAME,MAXSIZE)            \
TEXT NAME(SB), WRAPPER, $MAXSIZE-32;        \
    NO_LOCAL_POINTERS;          \
    /* copy arguments to stack */       \
    MOVQ    argptr+16(FP), SI;      \
    MOVLQZX argsize+24(FP), CX;     \
    MOVQ    SP, DI;             \
    REP;MOVSB;              \
    /* call function */         \
    MOVQ    f+8(FP), DX;            \
    PCDATA  $PCDATA_StackMapIndex, $0;  \
    CALL    (DX);               \
    /* copy return values back */       \
    MOVQ    argtype+0(FP), DX;      \
    MOVQ    argptr+16(FP), DI;      \
    MOVLQZX argsize+24(FP), CX;     \
    MOVLQZX retoffset+28(FP), BX;       \
    MOVQ    SP, SI;             \
    ADDQ    BX, DI;             \
    ADDQ    BX, SI;             \
    SUBQ    BX, CX;             \
    CALL    callRet<>(SB);          \
    RET

是不是很亂,這些是啥斗埂?符糊?看不懂。用gdb跟蹤一下到:
運(yùn)行到下圖:


圖片.png

disass一下看一下CALLFN(. call32, 32)所指向的指令:


圖片.png

綠框處所對(duì)應(yīng)的的就是源文件中的代碼:
TEXT callRet<>(SB), NOSPLIT, $32-0

那紅框ret處就是reflectcall的返回呛凶。打到斷點(diǎn)到ret處男娄。
執(zhí)行到這里見下圖:

圖片.png

ret的作用是pop 棧頂?shù)絩ip,我們看一下rsp中的內(nèi)容是啥?
圖片.png

0x423025 所指向的內(nèi)容:
圖y
圖y和上面的圖x的地址一樣的把兔,就是reflectcall指令的下條指令沪伙。再看一下源文件下行代碼是啥?p.argp = nil
翻譯成匯編代碼就是圖y中的 mov QWROD PTR [rsp+0x58],0x0县好,就是變量賦值會(huì)把值存入棧中而不是寄存器中围橡。
圖片.png

執(zhí)行完d.fn,將d脫鏈:

d._panic = nil
d.fn = nil
gp._defer = d.link

運(yùn)行到:

func fatalpanic(msgs *_panic)

進(jìn)行打印輸出缕贡,看一下實(shí)現(xiàn):

func fatalpanic(msgs *_panic) {
    pc := getcallerpc()
    sp := getcallersp()
    gp := getg()
    var docrash bool

    systemstack(func() {
        if startpanic_m() && msgs != nil {
            atomic.Xadd(&runningPanicDefers, -1)
                        
            printpanics(msgs)
        }

        docrash = dopanic_m(gp, pc, sp)
    })

    if docrash {
        crash()
    }

    systemstack(func() {
        exit(2)
    })

    *(*int)(nil) = 0 // not reached
}

重點(diǎn)看如下函數(shù):

 printpanics(msgs)

實(shí)現(xiàn):

func printpanics(p *_panic) {
    if p.link != nil {
        printpanics(p.link)
        print("\t")
    }
    print("panic: ")
    printany(p.arg)
    if p.recovered {
        print(" [recovered]")
    }
    print("\n")
}

發(fā)現(xiàn)這個(gè)是個(gè)遞歸調(diào)用翁授,從g._panic鏈表頭開始直到鏈表結(jié)束然后打印出panic信息。

golang捕獲的

例如slice越界晾咪,見下方代碼:

package main
import "fmt"
func main() {
    arr := []int{1, 2}
    arr[2] = 3
    fmt.Println(arr)
}

會(huì)panic:
panic: runtime error: index out of range
編譯成匯編代碼:go tool compile -S main.go > main.s

0x003c 00060 (main.go:7)    CALL    runtime.panicindex(SB)

可知調(diào)用了panicindex(SB)
去看一下它的實(shí)現(xiàn):

func panicindex() {
    if hasPrefix(funcname(findfunc(getcallerpc())), "runtime.") {
        throw(string(indexError.(errorString)))
    }
    panicCheckMalloc(indexError)
    panic(indexError)
}

發(fā)現(xiàn)最終還是會(huì)調(diào)用panic(interface{})這個(gè)函數(shù)收擦,然后就是上面所說的手動(dòng)panic的執(zhí)行流程,在這里不在重復(fù)贅述谍倦。

系統(tǒng)捕獲的

比如對(duì)只讀內(nèi)存區(qū)賦值操作會(huì)引起panic

package main

import "fmt"

func main() {
    var pi *int
    *pi = 100
    fmt.Printf("%v", *pi)
}

會(huì)報(bào)如下錯(cuò)誤:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x488a53]
goroutine 1 [running]:
main.main()
/server/gotest/src/hello/defer/main.go:7 +0x3a

編譯成匯編代碼沒有發(fā)現(xiàn)gopanic入口塞赂。因?yàn)樽罱K輸出panic棧的信息,所以肯定調(diào)用了gopanic昼蛀,給gopanic()打上斷點(diǎn)直接運(yùn)行到這里見下圖:

圖片.png

確實(shí)攔截到了gopanic宴猾,看一下它的調(diào)用鏈:
main.main => runtime.sigpanic() => runtime.panicmem() => gopanic()。
那為什么匯編中沒有sigpanic()入口還能調(diào)用這個(gè)函數(shù)呢叼旋?
看一下*pi = 100生成的匯編代碼:
圖片.png

劃紅線處:test BYTE PTR [ax], al 由于ax=0x0所以BYTE PTR [ax]是獲取不到0x0的內(nèi)存的仇哆。這樣cpu執(zhí)行這條語句的時(shí)候會(huì)進(jìn)入內(nèi)核態(tài)保存0x488b1a到寄存器,內(nèi)核態(tài)發(fā)送消息給go進(jìn)程夫植,go處理函數(shù)將0x488b1a所指向的內(nèi)容換成go啟動(dòng)時(shí)事先注冊(cè)號(hào)的函數(shù)作為指令入口讹剔,回到內(nèi)核態(tài)執(zhí)行0x488b1a -> 注冊(cè)函數(shù)的指令。具體的調(diào)用鏈在這里就不深究了重點(diǎn)還是panic,recover延欠。

2 defer panic

2.1示例:
package main
import "fmt"
func main() {
    defer fmt.Println("d1")
    defer fmt.Println("d2")
    panic("panic error")
}

輸出:
d2
d1
panic error
如下核心代碼:

//runtime/panic.go
func gopanic(e interface{}) {
    for {
        ...//獲取goroutine表頭deferd          
           //執(zhí)行表頭的deferd
           reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        ...//將表頭的deferd拖鏈陌兑,將下一個(gè)deferd綁定到表頭
     }
     ...
     fatalpanic(gp._panic) // 運(yùn)行遞歸調(diào)用gp._panic鏈表上的panic
     ...
}

從上面代碼可知,gopanic先遍歷deferd鏈在遍歷panic鏈衫冻,所以panic error最后輸出诀紊。

2.2示例:
圖片.png

輸出:
d2
d1
panic: panic error
panic: panic error2
根據(jù)示例2.1 函數(shù)gopanic()可知函數(shù)的調(diào)用鏈見下面調(diào)用關(guān)系:

第14行panic -> gopanic() -> reflectcall -> 第12行defer -> reflectcall -> 第8行defer -> 第9行panic -> gopanic -> reflectcall -> 繼續(xù)執(zhí)行deferd鏈上的也就是第6行defer -> fatalpanic(里面子函數(shù)printpanics()遞歸調(diào)用g._panic鏈)。

3 defer panic recover

下面介紹的是recover的執(zhí)行過程隅俘,先看下方示例代碼:

package main

import "fmt"

func main() {
    re()
    fmt.Println("After recovery!")
}
func re() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("err:", err)
        }
    }()
    panic("panic error1")
}

輸出:
err: panic error1
After recovery!

recover()的作用是捕獲異常之后讓程序正常往下執(zhí)行而不會(huì)退出。這個(gè)例子里re()函數(shù)里有了異常笤喳,并且被捕獲然后執(zhí)行了re()下面的代碼輸出'After recovery'为居。

那為什么執(zhí)行完recover()之后會(huì)跳轉(zhuǎn)到輸出行執(zhí)行呢?

從匯編角度考慮:執(zhí)行完re()之后要想保證繼續(xù)往下執(zhí)行杀狡,首先要把下一行的入口地址存起來蒙畴,然后recover()之后再去取回來,放到rip指令寄存器中這樣才可以向下執(zhí)行呜象。

在re()里除了deferd函數(shù)還有有panic()這行膳凝,那很明顯它的內(nèi)部實(shí)現(xiàn)里會(huì)有相關(guān)實(shí)現(xiàn),繼續(xù)分析recover的實(shí)現(xiàn)和panic內(nèi)部的相關(guān)實(shí)現(xiàn)恭陡。

匯編查看recover():go tool compile -S main.go
發(fā)現(xiàn)gorecover(SB),猜測(cè)是recover()的實(shí)現(xiàn):

    0x002a 00042 (main.go:13)   CALL    runtime.gorecover(SB)

在recover()行打斷點(diǎn),發(fā)現(xiàn)確實(shí)執(zhí)行了gorecover(SB)函數(shù)蹬音,實(shí)現(xiàn)如下:

func gorecover(argp uintptr) interface{} {
    gp := getg()
    p := gp._panic
    if p != nil && !p.recovered && argp == uintptr(p.argp) {
        p.recovered = true
        return p.arg
    }
    return nil
}

從以上代碼可知gorecover(uintptr)只是把當(dāng)前goroutine的_panic.recovered 設(shè)置為true,然后返回之前panic函數(shù)設(shè)置的參數(shù)(err)給調(diào)用方休玩。其實(shí)就是將當(dāng)前的g._panic設(shè)置個(gè)標(biāo)致著淆,告訴以后的程序說我已經(jīng)被捕獲到了。

這個(gè)有recover()的deferd函數(shù)執(zhí)行完之后會(huì)返回到上面提到的gopanic(interface{})函數(shù)中的reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))下一行繼續(xù)往下執(zhí)行拴疤。
見下方代碼:

func gopanic(e interfac{}) {
.......
        reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
//往下看:
        p.argp = nil


        if gp._defer != d {
            throw("bad defer entry in panic")
        }
        //執(zhí)行完defered函數(shù)之后脫鏈
        d._panic = nil
        d.fn = nil
        gp._defer = d.link

        pc := d.pc //deferproc()函數(shù)中存入的放回值地址
        sp := unsafe.Pointer(d.sp) //
        freedefer(d)
        if p.recovered {//執(zhí)行了gorecover()函數(shù)之后p.recovered == true
            atomic.Xadd(&runningPanicDefers, -1)

            gp._panic = p.link

            for gp._panic != nil && gp._panic.aborted {
                gp._panic = gp._panic.link
            }
            if gp._panic == nil { // must be done with signal
                gp.sig = 0
            }

            gp.sigcode0 = uintptr(sp)
            gp.sigcode1 = pc //pc恢復(fù)棧作用永部。
            mcall(recovery)
            throw("recovery failed") // mcall should not return
        }
......
}

看一下這行代碼:

pc := d.pc 

pc是什么呢?它是上篇文章中提到的deferproc()函數(shù)中存入的呐矾,見下方代碼:

func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
        ...
    callerpc := getcallerpc()
    d := newdefer(siz)
    if d._panic != nil {
        throw("deferproc: d.panic != nil after newdefer")
    }
    d.fn = fn
    d.pc = callerpc
       ....

我們?cè)谙路浇貓D的第12行打一斷點(diǎn)來看一下pc中到底是啥苔埋。看一下綠框中的指令:

圖片.png

defer關(guān)鍵字會(huì)翻譯成call runtime.deferproc那它下方綠框中的是runtime.deferproc后面的指令是編譯器生成的(也可以這么理解蜒犯,defer關(guān)鍵字會(huì)讓編譯器生成deferproc函數(shù)指令及后面一堆指令)第一行:test eax, eax的地址是0x4872d5稍后會(huì)再次說到這個(gè)指令及地址组橄。

繼續(xù)斷點(diǎn)執(zhí)行到d.pc = callerpc之后,我們看一下d.pc到底是什么值愧薛,見下圖:

圖片.png

0x4872d5這不是剛剛說的上圖綠框處 test eax, eax的指令地址嗎晨炕。帶著疑問繼續(xù)往下看。

從上面gorecover(uintptr)函數(shù)代碼可知 p.recoverd == true 所以gopanic()中會(huì)執(zhí)行到if p.recovered {里毫炉,我們著重看兩行代碼:

 gp.sigcode1 = pc

將pc就是deferproc()函數(shù)的返回值賦值給gp.sigcode1瓮栗,為返回到正常流程做準(zhǔn)備。

mcall(recovery)

其中的mcall先不看,先看recovery函數(shù)作用,見下方實(shí)現(xiàn):

func recovery(gp *g) {
    // Info about defer passed in G struct.
    sp := gp.sigcode0
    pc := gp.sigcode1

    // d's arguments need to be in the stack.
    if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {
        print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
        throw("bad recovery")
    }

    // Make the deferproc for this d return again,
    // this time returning 1.  The calling function will
    // jump to the standard return epilogue.
    gp.sched.sp = sp
    gp.sched.pc = pc
    gp.sched.lr = 0
    gp.sched.ret = 1
    gogo(&gp.sched)
}

recovery(*g) 主要是gp.sched賦值。其中pc是當(dāng)前deferproc函數(shù)的返回地址费奸。我們?cè)倏匆幌耮ogo(&gp.sched)函數(shù)實(shí)現(xiàn)弥激,因?yàn)間ogo函數(shù)是用匯編實(shí)現(xiàn)的所以用gdb跟蹤是最方便的見下方代碼:

TEXT runtime·gogo(SB), NOSPLIT, $16-8
    MOVQ    buf+0(FP), BX       // gobuf
    MOVQ    gobuf_g(BX), DX
    MOVQ    0(DX), CX       // make sure g != nil
    get_tls(CX)
    MOVQ    DX, g(CX)
    MOVQ    gobuf_sp(BX), SP    // restore SP
    MOVQ    gobuf_ret(BX), AX
    MOVQ    gobuf_ctxt(BX), DX
    MOVQ    gobuf_bp(BX), BP
    MOVQ    $0, gobuf_sp(BX)    // clear to help garbage collector
    MOVQ    $0, gobuf_ret(BX)
    MOVQ    $0, gobuf_ctxt(BX)
    MOVQ    $0, gobuf_bp(BX)
    MOVQ    gobuf_pc(BX), BX
    JMP BX

著重看2行代碼:

MOVQ    gobuf_ret(BX), AX

AX從某個(gè)值變成了1,這個(gè)指令的偏移數(shù)量是gobuf_ret,其中的ret不就是返回的意思嗎愿阐,見下圖微服。


圖片.png

再看最后一條指令:

JMP BX

看一下BX到底是啥:

圖片.png

綠框處就是BX的值,也就是要jmp到這個(gè)地址處執(zhí)行缨历,這個(gè)地址眼熟嗎以蕴,不就是剛提到的0x4872d5嗎,對(duì)應(yīng)的指令是test eax,eax辛孵。再重看一下這個(gè)圖:
圖片.png
其中綠框第一行就是要跳轉(zhuǎn)的地址丛肮。剛才說了AX已經(jīng)變成了1。那下方的兩行指令

test eax, eax 
jne 0x4872f9

的意思是如果eax不等于0就跳轉(zhuǎn)到這個(gè)地址否則就去執(zhí)行綠框處第三行的正常流程魄缚。因?yàn)閑ax已經(jīng)不等0了宝与,所以就會(huì)跳轉(zhuǎn)到0x4872f9這個(gè)地址處,跟蹤一下這個(gè)地址指向的是哪里冶匹,見下圖:

圖片.png

原來它調(diào)用了runtime.deferreturn()函數(shù),見下圖习劫。
圖片.png

執(zhí)行到這里。

sp := getcallersp()
sp是調(diào)用者的sp嚼隘。就是即將調(diào)用defer func() {時(shí)的sp诽里。
d.sp 是調(diào)用鏈上第二個(gè)defer,因?yàn)榈谝粋€(gè)deferd已經(jīng)脫鏈嗓蘑。
顯然這兩個(gè)不相等须肆,所以return了,具體return底層到底是如何將re()的返回地址返回的就不在跟蹤了桩皿。然后執(zhí)行到了下放的入口地址處:

fmt.Println("After recovery!")

整個(gè)流程豌汇,參看下圖代碼然后解釋:


圖片.png

call re() => 將re()返回值壓棧到棧頂 => 執(zhí)行12行defer函數(shù) => 執(zhí)行deferproc():將deferproc返回值存入pc,調(diào)用者(re())棧頂存入到sp,將defered函數(shù)加入到鏈表頭泄隔,返回0(return0函數(shù)作用是將ax設(shè)為0) => 返回到下方代碼test eax eax處 => 由于ax=0繼續(xù)運(yùn)行到17行的panic() =>gopanic() => 調(diào)用reflectcall():執(zhí)行deferd函數(shù) => 執(zhí)行recovery():將recoverd標(biāo)志位設(shè)為1 => mcall(recovery) => gogo():ax設(shè)為1拒贱,跳轉(zhuǎn)到pc處 => 再一次跳轉(zhuǎn)到test eax, eax :由于ax=1 => 跳轉(zhuǎn)到deferreturn()函數(shù):callersp !=d.sp,這里的d.sp中的d其實(shí)已經(jīng)是是g上面默認(rèn)帶的_defer了佛嬉,所以不等 => return 獲取re()的返回地址pop到rip處 => cpu執(zhí)行其返回值 => 輸出'After recovery'

...
//defer函數(shù) =>deferproc
0x00000000004872d0 <+48>:   call   0x426c00 <runtime.deferproc>
0x00000000004872d5 <+53>:   test   eax,eax
0x00000000004872d7 <+55>:   jne    0x4872f9 <main.re+89>
0x00000000004872d9 <+57>:   jmp    0x4872db <main.re+59>
0x00000000004872db <+59>:   lea    rax,[rip+0x111be]        # 0x4984a0
0x00000000004872e2 <+66>:   mov    QWORD PTR [rsp],rax
0x00000000004872e6 <+70>:   lea    rax,[rip+0x48643]     
0x00000000004872ed <+77>:   mov    QWORD PTR [rsp+0x8],rax

//panic() => gopanic
0x00000000004872f2 <+82>:   call   0x427880 <runtime.gopanic>
...

recover()的核心其實(shí)就是defer函數(shù)生成的匯編指令:判斷跳轉(zhuǎn)區(qū)分正常流程還是獲取返回值流程逻澳。見上方匯編代碼。
機(jī)器指令是從上往下執(zhí)行暖呕,正常流程是執(zhí)行完deferproc之后再執(zhí)行panic()生成的gopanic()斜做。獲取返回值流程必然需要跳轉(zhuǎn)到某處獲取,而golang的設(shè)計(jì)者放到了deferreturn()函數(shù)中所以最終要跳到這里來湾揽。

留個(gè)疑問下方代碼如何輸出瓤逼,為什么笼吟?

package main

import "fmt"

func main() {
    re()
    fmt.Println("After recovery!")
}

func re() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recover again:", err)
        }
    }()
    defer func() {
        if err := recover(); err != nil {
            switch v := err.(type) {
            case string:
                panic(string(v))
            }
        }
    }()
    panic("start panic")
}

參考:
Go語言panic/recover的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霸旗,隨后出現(xiàn)的幾起案子贷帮,更是在濱河造成了極大的恐慌,老刑警劉巖诱告,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撵枢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡精居,警方通過查閱死者的電腦和手機(jī)锄禽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來靴姿,“玉大人沟绪,你說我怎么就攤上這事】詹拢” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵恨旱,是天一觀的道長(zhǎng)辈毯。 經(jīng)常有香客問我,道長(zhǎng)搜贤,這世上最難降的妖魔是什么谆沃? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮仪芒,結(jié)果婚禮上唁影,老公的妹妹穿的比我還像新娘。我一直安慰自己掂名,他們只是感情好据沈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饺蔑,像睡著了一般锌介。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猾警,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天孔祸,我揣著相機(jī)與錄音,去河邊找鬼发皿。 笑死崔慧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的穴墅。 我是一名探鬼主播惶室,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼温自,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了拇涤?” 一聲冷哼從身側(cè)響起捣作,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹅士,沒想到半個(gè)月后券躁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掉盅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年也拜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趾痘。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慢哈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出永票,到底是詐尸還是另有隱情卵贱,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布侣集,位于F島的核電站键俱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏世分。R本人自食惡果不足惜编振,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臭埋。 院中可真熱鬧踪央,春花似錦、人聲如沸瓢阴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炫掐。三九已至魁莉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間募胃,已是汗流浹背旗唁。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痹束,地道東北人检疫。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像祷嘶,于是被迫代替她去往敵國(guó)和親屎媳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夺溢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359