Go執行時bug除錯過程解析
轉載:http://blog.csdn.net/dev_csdn/article/details/78813904
前言
我是Prometheus和Grafana的超級粉絲。作為一名前谷歌SRE(Site
Reliability Engineer, 網站可靠性工程師),我學會了如何選擇優秀的監控應用程式。這個組合在過去的一年中一直是我戰無不勝的法寶。我使用它們監控我自己的個人伺服器(包括黑盒和白盒監控)、為我的客戶提供專業的技術支援,以及實現其他很多的功能。 使用Prometheus編寫自定義匯出程式來監視資料非常地簡單,而且你可以很方便地在其他地方找到一個適合於自己的可用的匯出程式。例如,我們使用
Euskal Encounter的事件儀表盤
由於把node_exporter部署到任何一臺機器上都非常簡單,並且它能執行一個Prometheus例項來為機器做基本的系統級監控(包括CPU、記憶體、網路、磁碟、檔案系統的使用情況等),那麼我想,為什麼不監視一下我的膝上型電腦呢?我有一臺Clevo“遊戲”膝上型電腦,它是我主要的工作電腦,大部分時間都是假裝在家裡做桌上型電腦,有時也會和我一起參加像混沌通訊大會(
Chaos Communication Congress)這樣的大型活動。由於我已經在它和一臺執行Prometheus的伺服器之間建立了VPN連結,所以,我可以通過執行emerge prometheus-node_exporter
問題浮現
不過,在設定完的一個小時之後,我的手機確實出現了一個提示:新新增的目標無法訪問。我可以SSH到膝上型電腦,說明電腦執行正常,但node_exporter
已經崩潰了。
fatal error: unexpected signal during runtime execution [signal SIGSEGV: segmentation violation code=0x1 addr=0xc41ffc7fff pc=0x41439e] goroutine 2395 [running]: runtime.throw(0xae6fb8, 0x2a) /usr/lib64/go/src/runtime/panic.go:605 +0x95 fp=0xc4203e8be8 sp=0xc4203e8bc8 pc=0x42c815 runtime.sigpanic() /usr/lib64/go/src/runtime/signal_unix.go:351 +0x2b8 fp=0xc4203e8c38 sp=0xc4203e8be8 pc=0x443318 runtime.heapBitsSetType(0xc4204b6fc0, 0x30, 0x30, 0xc420304058) /usr/lib64/go/src/runtime/mbitmap.go:1224 +0x26e fp=0xc4203e8c90 sp=0xc4203e8c38 pc=0x41439e runtime.mallocgc(0x30, 0xc420304058, 0x1, 0x1) /usr/lib64/go/src/runtime/malloc.go:741 +0x546 fp=0xc4203e8d38 sp=0xc4203e8c90 pc=0x411876 runtime.newobject(0xa717e0, 0xc42032f430) /usr/lib64/go/src/runtime/malloc.go:840 +0x38 fp=0xc4203e8d68 sp=0xc4203e8d38 pc=0x411d68 github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_golang/prometheus.NewConstMetric(0xc42018e460, 0x2, 0x3ff0000000000000, 0xc42032f430, 0x1, 0x1, 0x10, 0x9f9dc0, 0x8a0601, 0xc42032f430) /var/tmp/portage/net-analyzer/prometheus-node_exporter-0.15.0/work/prometheus-node_exporter-0.15.0/src/github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_golang/prometheus/value.go:165 +0xd0 fp=0xc4203e8dd0 sp=0xc4203e8d68 pc=0x77a980
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
像其他的Prometheus元件一樣,node_exporter
是用Go編寫的。 Go是一種相對安全的語言,儘管有的時候你可能會搬起石頭砸自己的腳,而且它不像Rust那樣具有強有力的安全保證,但是,要在Go中產生段錯誤也並不是那麼容易的。 況且,node_exporter
是一個相對來說比較簡單的Go應用程式,只單純的依賴Go。
因此,這是一個非常有趣的崩潰,特別是崩潰在mallocgc
裡面。一般情況下,這裡永遠都不會崩潰。
重啟幾次之後,事情變得更有趣了:
2017/11/07 06:32:49 http: panic serving 172.20.0.1:38504: runtime error: growslice: cap out of range
goroutine 41 [running]:
net/http.(*conn).serve.func1(0xc4201cdd60)
/usr/lib64/go/src/net/http/server.go:1697 +0xd0
panic(0xa24f20, 0xb41190)
/usr/lib64/go/src/runtime/panic.go:491 +0x283
fmt.(*buffer).WriteString(...)
/usr/lib64/go/src/fmt/print.go:82
fmt.(*fmt).padString(0xc42053a040, 0xc4204e6800, 0xc4204e6850)
/usr/lib64/go/src/fmt/format.go:110 +0x110
fmt.(*fmt).fmt_s(0xc42053a040, 0xc4204e6800, 0xc4204e6850)
/usr/lib64/go/src/fmt/format.go:328 +0x61
fmt.(*pp).fmtString(0xc42053a000, 0xc4204e6800, 0xc4204e6850, 0xc400000073)
/usr/lib64/go/src/fmt/print.go:433 +0x197
fmt.(*pp).printArg(0xc42053a000, 0x9f4700, 0xc42041c290, 0x73)
/usr/lib64/go/src/fmt/print.go:664 +0x7b5
fmt.(*pp).doPrintf(0xc42053a000, 0xae7c2d, 0x2c, 0xc420475670, 0x2, 0x2)
/usr/lib64/go/src/fmt/print.go:996 +0x15a
fmt.Sprintf(0xae7c2d, 0x2c, 0xc420475670, 0x2, 0x2, 0x10, 0x9f4700)
/usr/lib64/go/src/fmt/print.go:196 +0x66
fmt.Errorf(0xae7c2d, 0x2c, 0xc420475670, 0x2, 0x2, 0xc420410301, 0xc420410300)
/usr/lib64/go/src/fmt/print.go:205 +0x5a
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
太有趣了。 這次Sprintf
出現崩潰了。 為什麼?
runtime: pointer 0xc4203e2fb0 to unallocated span idx=0x1f1 span.base()=0xc4203dc000 span.limit=0xc4203e6000 span.state=3
runtime: found in object at *(0xc420382a80+0x80)
object=0xc420382a80 k=0x62101c1 s.base()=0xc420382000 s.limit=0xc420383f80 s.spanclass=42 s.elemsize=384 s.state=_MSpanInUse
<snip>
fatal error: found bad pointer in Go heap (incorrect use of unsafe or cgo?)
runtime stack:
runtime.throw(0xaee4fe, 0x3e)
/usr/lib64/go/src/runtime/panic.go:605 +0x95 fp=0x7f0f19ffab90 sp=0x7f0f19ffab70 pc=0x42c815
runtime.heapBitsForObject(0xc4203e2fb0, 0xc420382a80, 0x80, 0xc41ffd8a33, 0xc400000000, 0x7f0f400ac560, 0xc420031260, 0x11)
/usr/lib64/go/src/runtime/mbitmap.go:425 +0x489 fp=0x7f0f19ffabe8 sp=0x7f0f19ffab90 pc=0x4137c9
runtime.scanobject(0xc420382a80, 0xc420031260)
/usr/lib64/go/src/runtime/mgcmark.go:1187 +0x25d fp=0x7f0f19ffac90 sp=0x7f0f19ffabe8 pc=0x41ebed
runtime.gcDrain(0xc420031260, 0x5)
/usr/lib64/go/src/runtime/mgcmark.go:943 +0x1ea fp=0x7f0f19fface0 sp=0x7f0f19ffac90 pc=0x41e42a
runtime.gcBgMarkWorker.func2()
/usr/lib64/go/src/runtime/mgc.go:1773 +0x80 fp=0x7f0f19ffad20 sp=0x7f0f19fface0 pc=0x4580b0
runtime.systemstack(0xc420436ab8)
/usr/lib64/go/src/runtime/asm_amd64.s:344 +0x79 fp=0x7f0f19ffad28 sp=0x7f0f19ffad20 pc=0x45a469
runtime.mstart()
/usr/lib64/go/src/runtime/proc.go:1125 fp=0x7f0f19ffad30 sp=0x7f0f19ffad28 pc=0x430fe0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
現在,垃圾收集者偶然間又發現了一個問題,是另一個崩潰。
在這一點上,很自然地就能得到兩個結論:要麼是硬體有嚴重的問題,要麼在在二進位制檔案中存在一個嚴重的記憶體破壞缺陷。 我最初認為第一種情況不太可能,因為這臺機器上執行的程式非常雜,沒有出現任何不穩定的與硬體有關的跡象。 由於像node_exporter
這樣的Go二進位制檔案是靜態連結的,不依賴於任何其他庫,所以我可以下載正式版的二進位制檔案來試一下。
然而,當我這樣做的時候,程式還是崩潰了。
unexpected fault address 0x0
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x76b998]
goroutine 13 [running]:
runtime.throw(0xabfb11, 0x5)
/usr/local/go/src/runtime/panic.go:605 +0x95 fp=0xc420060c40 sp=0xc420060c20 pc=0x42c725
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:374 +0x227 fp=0xc420060c90 sp=0xc420060c40 pc=0x443197
github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_model/go.(*LabelPair).GetName(...)
/go/src/github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_model/go/metrics.pb.go:85
github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_golang/prometheus.(*Desc).String(0xc4203ae010, 0xaea9d0, 0xc42045c000)
/go/src/github.com/prometheus/node_exporter/vendor/github.com/prometheus/client_golang/prometheus/desc.go:179 +0xc8 fp=0xc420060dc8 sp=0xc420060c90 pc=0x76b998
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
又是一次完全不同的崩潰。這說明node_exporter
的上游或者它的一個依賴項確實存在問題,所以,我在GitHub上提交了一個issue。也許開發者以前見過這個,如果他們有什麼想法的話,那麼引起他們的注意是非常值得的。
走了一趟並不順暢的彎路
毫無疑問,對於上游問題,首先能想到的是這是一個硬體問題。畢竟我只是在一臺特定的機器上碰到這個問題。其他所有的機器都能很順利地執行node_exporter
。雖然在這臺主機上沒有其他硬體連線不穩定的證據,但是我也無法解釋這臺機器存在能導致node_exporter
崩潰的特殊性。Memtest86+的執行永遠不會破壞其他程式,所以我安裝了一個。
然後,發生了這個:
這是我用客戶的電腦所得到的
哎呀!RAM壞了。更具體點說是有一位(bit)的壞記憶體。在測試程式完整地運行了一遍之後,最終得到的就只是那一個壞的位,另外在測試7中存在一些誤報(在附近移動塊的時候出來了一個錯誤)。
進一步的測試表明,SMP模式下的Memtest86+測試5可以快速檢測到錯誤,但通常不會在第一遍檢測的時候發現。錯誤總是出現在相同的地址和相同的位上。這說明這個問題出現在一個微弱或洩漏的RAM單元上,特別是隨溫度會變壞的那種。這非常符合邏輯:溫度的升高會增加RAM單元的洩漏,並且很有可能會引起位翻轉。
從這個角度來看,這是274,877,906,944個位中的一個壞點。這實際上是一個非常不錯的的錯誤率了!硬碟和快閃記憶體的錯誤率要高得多,只是這些裝置在出廠時會標出壞塊,在使用者不知情的情況下透明地換出,並且可以將新發現的弱塊透明地標記為壞塊,並將其重新定位到備用區。記憶體並不這麼奢侈,所以一個壞的位永遠都是壞的。
唉,這不可能成為node_exporter
崩潰的原因。那個應用程式使用的RAM很少,所以它碰到壞位的機會是非常低的。這類問題一般表現得並不會很明顯,也許會導致某些圖形中的畫素錯誤、在某些文字中出現單個字母的翻轉、也可能指令被破壞導致無法執行,或者當某些非常重要的資料確實落在了壞位上會出現崩潰。儘管如此,它確實會導致長期的可靠性問題,這就是伺服器和其他可靠裝置必須使用ECC
RAM才能糾正這種錯誤的原因。
我沒有在這檯筆記本電腦上配置豪華的ECC RAM。但是我擁有將記憶體壞塊標記為壞的能力,並告訴作業系統不要使用它。GRUB 2有一個鮮為人知的功能,它允許你改變傳遞給啟動核心的記憶體對映。僅僅為了一個壞塊而購買新的RAM是不值得的,所以這是一個不錯的選擇。
不過,還有一件事情是我可以做的。由於情況會隨著溫度的升高而變差,那麼如果我加熱RAM會發生什麼呢?
memtest86+
我把熱風槍設定到一個較低的溫度(130°C),並對兩個模組進行加熱(其他兩個模組在後蓋下,因為我的膝上型電腦總共有四個SODIMM插槽)。我發現另外還有三個弱點只能在高溫下才能檢測到,它們分佈在三個記憶體條上。
我還發現,即使我交換了模組的位置,發生錯誤的位置仍然保持大體上的一致:地址的最高位保持不變。這是因為RAM是交錯的:資料遍佈在四個記憶體條上,而不是在每個記憶體條上連續分配可用地址空間的四分之一。因此,我可以遮蔽一個足夠大的RAM區域,以覆蓋每個錯誤位所有可能的地址。我發現,遮蔽連續的128KB區域應該足以覆蓋每個給定壞點的所有可能的地址排列,但是,為了更好的進行測量,我將它四捨五入到1MB。我用了三個1MB對齊的塊來進行掩蓋(其中一個塊掩蓋了兩個壞點,我總共要掩蓋四個壞點):
0x36a700000
–0x36a7fffff
0x460e00000
–0x460efffff
0x4ea000000
–0x4ea0fffff
這可以使用GRUB的地址/掩碼語法來指定,/etc/default/grub
如下所示:
GRUB_BADRAM="0x36a700000,0xfffffffffff00000,0x460e00000,0xfffffffffff00000,0x4ea000000,0xfffffffffff00000"
- 1
- 2
不用說,node_exporter
還是崩潰了,但我知道了這不併是真正的問題所在。
深度挖掘
這種錯誤很煩人,它顯然是因為程式碼執行的某塊記憶體被破壞而引起的。這種錯誤很難除錯,因為我們無法預測什麼會被破壞(或發生變化),而且我們也無法在發生錯誤的時候捕捉到錯誤的程式碼。
首先,我嘗試了node_exporter
的其他一些版本,並啟用或禁用了不同的引數,但並沒有什麼效果。我還嘗試在strace
下執行例項,似乎沒有發生崩潰,這強烈說明了這是在競爭條件下的一個問題。strace
通常會攔截所有執行緒執行的所有系統呼叫,並在某種程度上讓應用程式的執行序列化。後來,我發現strace
也崩潰了,但是運行了很長時間才出現崩潰。由於這似乎與併發有關,所以我試著設定GOMAXPROCS=1
,這個引數告訴Go只使用一個OS級別的執行緒來執行Go程式碼。崩潰再也沒有發生,問題再一次指向了併發。
到目前為止,我已經收集了一定數量的崩潰日誌,並開始關注其中的一些規律。雖然崩潰的位置以及表面原因有很多種,但是最終的錯誤資訊可以分為多個不同的型別,而且每種型別的錯誤不止出現過一次。所以我開始使用谷歌搜尋這些錯誤,並偶然間發現了Go issue #20427。雖然這個問題似乎與Go無關,但卻引起了類似的段錯誤和隨機性問題。在Go 1.9之後,這個問題被關閉了,但並沒有得到解決。沒有人知道根本原因是什麼,而且它再也沒有出現過。
所以,我從issue中抓取了這段聲稱能夠重現問題的示例程式碼,並在我的機器上執行。你看,它在幾秒鐘內崩潰了。太好了。這比等待node_exporter
崩潰所需的時間要短得多。
這並沒有讓我從Go的角度更接近這個問題,但它卻加快了我測試的速度。所以,我們來試試從另一個角度進行分析吧。
把不同的電腦區分開來
這個問題發生在我的膝上型電腦上,但在其他機器上卻都沒有發生。我嘗試著在其他電腦上重現這個問題,但沒有一臺機器發生崩潰。這說明我的膝上型電腦中有一些特別的東西。由於Go是靜態連結的二進位制檔案,所以其餘的使用者空間並不重要。這留下了兩個相關的部分:硬體和核心。
我沒有什麼方法來測試各臺電腦的硬體,除了我自己的機器,但我可以搗鼓核心。所以,我們來試著走第一步:它會在虛擬機器中崩潰嗎?
為了測試這個,我建立了一個最小化的initramfs,這使我能夠快速啟動QEMU虛擬機器,而不必安裝發行版或啟動完整的Linux系統。我的initramfs是用Linux的scripts/gen_initramfs_list.sh
構建的,包含以下檔案:
dir /dev 755 0 0
nod /dev/console 0600 0 0 c 5 1
nod /dev/null 0666 0 0 c 1 3
dir /bin 755 0 0
file /bin/busybox busybox 755 0 0
slink /bin/sh busybox 755 0 0
slink /bin/true busybox 755 0 0
file /init init.sh 755 0 0
file /reproducer reproducer 755 0 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
/init
是Linux initramfs的入口,在我這個案例中是一個簡單的shell指令碼,用於啟動測試並測量時間:
#!/bin/sh
export PATH=/bin
start=$(busybox date +%s)
echo "Starting test now..."
/reproducer
ret=$?
end=$(busybox date +%s)
echo "Test exited with status $ret after $((end-start)) seconds"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
/bin/busybox
是BusyBox的一個靜態連結版本,通常用於這樣的最小化系統,用以提供所有基本的Linux shell實用程式(包括shell本身)。
initramfs可以這樣構建(從Linux核心原始碼樹中),其中,list.txt是上面的檔案列表:
scripts/gen_initramfs_list.sh -o initramfs.gz list.txt
- 1
- 2
QEMU可以直接引導核心和initramfs:
qemu-system-x86_64 -kernel /boot/vmlinuz-4.13.9-gentoo -initrd initramfs.gz -append 'console=ttyS0' -smp 8 -nographic -serial mon:stdio -cpu host -enable-kvm
- 1
- 2
並沒有任何資訊輸出到控制檯上…… 我意識到我沒有為膝上型電腦核心編譯8250串列埠支援。哦,我太蠢了。它根本沒有物理串列埠,對吧?不管怎麼樣,我重新編譯了核心,並附帶序列支援。我再試了一下,它成功啟動並運行了。
它崩潰了嗎?是的。太好了,這意味著這個問題在同一臺機器上的虛擬機器上是可以重現的。我在家裡的伺服器上用同樣的QEMU命令,用自己的核心,但什麼也沒有發生。然後,我從膝上型電腦中把核心複製過來,然後啟動,崩潰了。核心是問題的關鍵,硬體不是問題。
搗鼓核心
我意識到自己需要編譯許多的核心來嘗試才能縮小範圍,所以,我決定轉移到一臺最強大的機器上來:一個有點舊的12核24執行緒Xeon處理器的機器。我將已知的壞核心源複製到那臺機器上,構建並進行測試。
它竟然沒有崩潰!為什麼?
在仔細思索了一番之後,我已經能夠確定是原來的壞的核心二進位制檔案崩潰了。我們要回到分析硬體的問題上去嗎?跟我在哪臺機器上編譯核心有關嗎?所以,我試著在家用伺服器上編譯核心,接著,這個崩潰立即觸發了。在兩臺機器上構建相同的核心會導致崩潰,而第三臺機器不會。它們之間有什麼不同呢?
我的膝上型電腦和家用伺服器都是〜amd64
(非穩定版),而我的Xeon伺服器是amd64
(穩定版)。這意味著它們的GCC是不同的。我的膝上型電腦和家用伺服器都是gcc(Gentoo
Hardened 6.4.0 p1.0)6.4.0
,而我的Xeon是gcc(Gentoo硬體5.4.0-r3 p1.3,pie-0.6.5) 5.4.0
。
但是我的家用伺服器核心與膝上型電腦核心幾乎是相同的(儘管不完全相同),使用相同的GCC構建,並沒有重現崩潰。所以,現在我們必須得出結論:用來構建核心的編譯器和核心本身(或其配置?)都有問題。
為了進一步縮小範圍,我在家用伺服器(linux-4.13.9-gentoo)上編譯了膝上型電腦上的核心樹,並確認它出現了崩潰。然後,我把家用伺服器上的.config
複製過來並編譯,發現它沒有崩潰。這麼做是因為我們想要尋找核心配置之間的差異和編譯器之間的差異:
- linux-4.13.9-gentoo + gcc 5.4.0-r3 p1.3 + laptop .config - 沒有崩潰
- linux-4.13.9-gentoo + gcc 6.4.0 p1.0 + laptop .config - 崩潰
- linux-4.13.9-gentoo + gcc 6.4.0 p1.0 + server .config - 沒有崩潰
兩個.config
,一個好,一個壞。需要一點時間來檢視它們之間的差異。當然,這兩個配置檔案是完全不同的(因為我喜歡定製我的核心配置,讓它只包含特定機器上所需的驅動程式),所以我不得不在重複編譯核心來縮小差異。
我決定從“壞”的.config
開始著手,從中刪除一些東西。由於要測試崩潰需要等待一定的時間,所以測試“崩潰”比“不崩潰”更容易。在22個核心的構建過程中,我對config檔案做了簡化,使其不支援網路、沒有檔案系統、沒有塊裝置核心,甚至不支援PCI(但它仍然可以在虛擬機器上正常工作!)。現在編譯一下核心不到60秒的時間,核心大小大約是我常用核心的四分之一左右。
然後,我轉移到“好”的.config
檔案上來,刪除了所有不必要的垃圾,同時確保它不會崩潰(這比之前的測試更加棘手更加慢)。也有一些有問題的分支,我在這些分支上修改了一些東西,接著就開始崩潰了。但是,我誤認為這些分支是“不會崩潰”的。所以,當崩潰發生的時候,我不得不找回以前的核心,並找出引起崩潰的確切的原因。最後,我一共編譯了7個核心。
最後,我把範圍縮小到.config
中的幾個不同的選項上來。其中有幾個嫌疑很大,特別是CONFIG_OPTIMIZE_INLINING
。經過仔細地測試,我得出結論:這個選項就是罪魁禍首。把它關掉,就會產生崩潰,啟用,就不會崩潰。這個選項在開啟的時候允許GCC自己確定哪個inline
函式真的需要內聯,而不是強制內聯。這也解釋了:內聯行為可能隨著GCC版本的不同而不同。
/*
* Force always-inline if the user requests it so via the .config,
* or if gcc is too old.
* GCC does not warn about unused static inline functions for
* -Wunused-function. This turns out to avoid the need for complex #ifdef
* directives. Suppress the warning in clang as well by using "unused"
* function attribute, which is redundant but not harmful for gcc.
*/
#if !defined(CONFIG_ARCH_SUPPORTS_OPTIMIZED_INLINING) || \
!defined(CONFIG_OPTIMIZE_INLINING) || (__GNUC__ < 4)
#define inline inline __attribute__((always_inline,unused)) notrace
#define __inline__ __inline__ __attribute__((always_inline,unused)) notrace
#define __inline __inline __attribute__((always_inline,unused)) notrace
#else
/* A lot of inline functions can cause havoc with function tracing */
#define inline inline __attribute__((unused)) notrace
#define __inline__ __inline__ __attribute__((unused)) notrace
#define __inline __inline __attribute__((unused)) notrace
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
那麼接下來做什麼呢? 我們知道CONFIG_OPTIMIZE_INLINING
這個選項使得測試結果出現不同,但是它可能會改變整個核心中每一個inline
的行為。
如何查明問題的真相呢?
我有一個主意。
基於雜湊的差異化編譯
我們要做的是在選項開啟的情況下編譯核心的一部分,在選項關閉的情況下編譯另一部分。 通過測試生成的核心並檢查問題是否重現,可以推匯出核心編譯單元的哪個子集的程式碼有問題。
我沒有列舉出所有的目標檔案,或是進行某種二分法搜尋,而是決定採用基於雜湊的方法。 我為GCC編寫了這個包裝器指令碼:
#!/bin/bash
args=("[email protected]")
doit=
while [ $# -gt 0 ]; do
case "$1" in
-c)
doit=1
;;
-o)
shift
objfile="$1"
;;
esac
shift
done
extra=
if [ ! -z "$doit" ]; then
sha="$(echo -n "$objfile" | sha1sum - | cut -d" " -f1)"
echo "${sha:0:8} $objfile" >> objs.txt
if [ $((0x${sha:0:8} & (0x80000000 >> $BIT))) = 0 ]; then
echo "[n]" "$objfile" 1>&2
else
extra=-DCONFIG_OPTIMIZE_INLINING
echo "[y]" "$objfile" 1>&2
fi
fi
exec gcc $extra "${args[@]}"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
這個指令碼使用SHA-1來取目標檔名的雜湊值,然後從前32位中檢查雜湊的給定位(由環境變數$BIT
進行標識)。 如果這個位的值是0,則編譯的時候不帶CONFIG_OPTIMIZE_INLINING
,
如果是1,則帶上CONFIG_OPTIMIZE_INLINING
。 我發現核心大約有685個目標檔案,這需要大約10個位來進行唯一標識。 這種基於雜湊的方法有一個很好的屬性:我可以選擇產生崩潰可能性比較大的結果(即位的值是0),因為要證明給定的核心不會崩潰是很困難的(因為崩潰是概率性出現的,
可能需要相當一段時間才會發生)。
我構建了32個核心,只花了29分鐘的時間。然後,我開始對它們進行測試,每當發生崩潰的時候,我都會將可能的SHA-1雜湊的正則表示式縮小到那些在這些特定位置上是0的雜湊。在發生了8次崩潰的時候,我把範圍縮小到4個目標檔案。一旦出現了10次崩潰之後,就只剩下唯一的一個了。
$ grep '^[0246][012389ab][0189][014589cd][028a][012389ab][014589cd]' objs_0.txt
6b9cab4f arch/x86/entry/vdso/vclock_gettime.o
- 1
- 2
- 3
vDSO的程式碼。當然。
vDSO在搗鬼
核心的vDSO實際上並不算是核心程式碼。 vDSO是核心放置在每個程序地址空間中的一個小型共享庫,它允許應用程式在不離開使用者模式的情況下執行特定的系統呼叫。這大大提高了系統性能,同時仍然允許核心根據需要更改這些系統呼叫的實現細節。
換句話說,vDSO是用GCC編譯的程式碼,與核心一起構建,最終與每個使用者空間的應用程式進行連結。它是使用者空間的程式碼。這就解釋了為什麼核心和它的編譯器都與此有關:這並不是跟核心本身有關,而是與核心提供的共享庫有關! Go使用vDSO來提升效能。Go也正好有一個重建自己的標準庫的戰略,所以,它沒有使用任何標準的Linux glibc的程式碼來呼叫vDSO,而是使用了自己的程式碼。
那麼改變CONFIG_OPTIMIZE_INLINING
的值對vDSO有什麼作用呢?我們來看看這段彙編。
設定CONFIG_OPTIMIZE_INLINING = n
:
arch/x86/entry/vdso/vclock_gettime.o.no_inline_opt: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <vread_tsc>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 90 nop
5: 90 nop
6: 90 nop
7: 0f 31 rdtsc
9: 48 c1 e2 20 shl $0x20,%rdx
d: 48 09 d0 or %rdx,%rax
10: 48 8b 15 00 00 00 00 mov 0x0(%rip),%rdx # 17 <vread_tsc+0x17>
17: 48 39 c2 cmp %rax,%rdx
1a: 77 02 ja 1e <vread_tsc+0x1e>
1c: 5d pop %rbp
1d: c3 retq
1e: 48 89 d0 mov %rdx,%rax
21: 5d pop %rbp
22: c3 retq
23: 0f 1f 00 nopl (%rax)
26: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
2d: 00 00 00
0000000000000030 <__vdso_clock_gettime>:
30: 55 push %rbp
31: 48 89 e5 mov %rsp,%rbp
34: 48 81 ec 20 10 00 00 sub $0x1020,%rsp
3b: 48 83 0c 24 00 orq $0x0,(%rsp)
40: 48 81 c4 20 10 00 00 add $0x1020,%rsp
47: 4c 8d 0d 00 00 00 00 lea 0x0(%rip),%r9 # 4e <__vdso_clock_gettime+0x1e>
4e: 83 ff 01 cmp $0x1,%edi
51: 74 66 je b9 <__vdso_clock_gettime+0x89>
53: 0f 8e dc 00 00 00 jle 135 <__vdso_clock_gettime+0x105>
59: 83 ff 05 cmp $0x5,%edi
5c: 74 34 je 92 <__vdso_clock_gettime+0x62>
5e: 83 ff 06 cmp $0x6,%edi
61: 0f 85 c2 00 00 00 jne 129 <__vdso_clock_gettime+0xf9>
[...]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
-
相關推薦
Go執行時bug除錯過程解析
轉載:http://blog.csdn.net/dev_csdn/article/details/78813904 前言 我是Prometheus和Grafana的超級粉絲。作為一名前谷歌SRE(Site Reliability Engineer, 網站可靠性工
Android 6 0 執行時許可權處理完全解析
一、概述ok,本篇文章目的之一就是對執行時許可權處理的一個介紹,以及對目前許可權相關的庫的一些瞭解。當然非常推薦閱讀官網許可權相關文章:本文也是在上述文章基礎上理解、實驗以及封裝。二、執行時許可權的變化及特點對於6.0以下的許可權及在安裝的時候,根據許可權宣告產生一個
Android 6.0 執行時許可權處理完全解析
一、概述 ok,本篇文章目的之一就是對執行時許可權處理的一個介紹,以及對目前許可權相關的庫的一些瞭解。 當然非常推薦閱讀官網許可權相關文章: 本文也是在上述文章基礎上理解、實驗以及封裝。 二、執行時許可權的變化及特點 對於6.0以下
第8章2節《MonkeyRunner源代碼剖析》MonkeyRunner啟動執行過程-解析處理命令行參數
path 轉載 iss 命令 code rst pri bsp ack MonkeyRunnerStarter是MonkeyRunner啟動時的入口類,由於它裏面包括了main方法.它的整個啟動過程主要做了以下幾件事情:解析用戶啟動MonkeyRunner時從命令行傳輸
關於Class物件、類載入機制、虛擬機器執行時記憶體佈局的全面解析和推測
簡介: 本文是對Java的類載入機制,Class物件,反射原理等相關概念的理解、驗證和Java虛擬機器中記憶體佈局的一些推測。本文重點講述瞭如何理解Class物件以及Class物件的作用。 歡迎探討,如有錯誤敬請指正 如需轉載,請註明出處 http://www.cnblogs.com/nul
Go 語言執行時環境變數快速導覽
原文: http://dave.cheney.net/2015/11/29/a-whirlwind-tour-of-gos-runtime-environment-variables Go 語言執行時環境變數快速導覽 Go Runtime除了提供:GC, goroutine排程, 定時器,
[bug]”System.InvalidProgramException:公共語言執行時檢測到無效程式“解決方案
Visual Studio 2017版本15.8.x執行某些程式會報這樣的錯誤:“System.InvalidProgramException:公共語言執行時檢測到無效程式” 此問題的臨時解決方案: 1)刪除vs2017\Team Tools\Performance Tools\Performance T
【面試題】多執行緒在執行過程中,某個執行緒執行時,突然釋放鎖。會發生的特殊狀態
一,背景 今天在刷面試題的時候,做到一道面試題,雖然看了答案,但有一個答案還是不理解。後來研究了一下,得到結論:執行緒拿到鎖進行執行時,哪怕獲得了CPU執行權,但是那個鎖不能丟失,它後面執行的過程都需要帶著鎖,才能往下繼續執行。 二,測試程式碼 /**
你的應用進入了中斷狀態,但當前未執行任何受選定除錯引擎支援的程式碼(例如,僅在執行本機執行時程式碼)
錯誤資訊 英文錯誤資訊:Your app has entered a break state, but no code is currently executing that is supported by the selected debug engine 環境 IDE:VS201
《自己動手寫java虛擬機器》學習筆記(七)-----執行緒私有執行時資料區(go)
專案地址:https://github.com/gongxianshengjiadexiaohuihui 在執行java程式時,Java虛擬機器需要使用記憶體來存放各種各樣的資料,Java虛擬機器規範把這些記憶體的區
程式執行時,建立一個額外的輸出臺,輸出程式內的Log以及除錯結果
需求描述:在做硬體除錯的時候,經常會需要用程式除錯具體問題處在哪裡,但是不斷重啟程式看日誌顯得繁瑣,想將日誌及除錯結果實時輸出。 解決方案:使用Kernel32.dll和user32.dll建立Console,使用控制檯實時輸出。在Main函式中新增實現程式碼
Android執行時ART載入OAT檔案的過程分析
在前面一文中,我們介紹了Android執行時ART,它的核心是OAT檔案。OAT檔案是一種Android私有ELF檔案格式,它不僅包含有從DEX檔案翻譯而來的本地機器指令,還包含有原來的DEX檔案內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART裡
【轉】vscode除錯執行c#詳細操作過程
【轉】vscode除錯執行c#詳細操作過程 主要命令: //路徑跳轉cd //新建專案dotnet new console -o 路徑 //執行dotnet run //用於釋出exe<RuntimeIdentifie
JVM執行時資料區域解析
Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裡面的人想出來。 Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,有些區域則是依賴使用者執行緒
GoLand建立go檔案,執行時產生CreateProcess failed with error 216:錯誤
剛剛安裝了GoLand,嘗試著寫第一個go程式,但是預設包報錯。如下: 經過探索,發現,go的預設的包名需要為main,修改為main即可。 package main import "fmt" func main() { fmt.
mysql 方法或者儲存過程執行慢的除錯方法
第一步:修改/etc/my.cnf檔案,找到[mysqld] 裡面加入 #執行的sql log=/tmp/logs/mysqld.log #記錄sql執行超過下面設定時間的sql log-slow-queries = /tmp/mysqlslowquery.log #
Spring基本執行過程解析
一。基本過程 1.當建立一個上下文: ApplicationContext applicationContext=new ClassPathXmlApplicationContext("sprin
Tensorflow原始碼解析7 -- TensorFlow分散式執行時
Tensorflow原始碼解讀系列文章,歡迎閱讀 帶你深入AI(1) - 深度學習模型訓練痛點及解決方法 自然語言處理1 – 分詞 Tensorflow原始碼解析1 – 核心架構和原始碼結構 Tensorflow原始碼解析2 – 前後端連線的橋樑 - Session Tensorflow
Tensorflow原始碼解析6 -- TensorFlow本地執行時
Tensorflow原始碼解讀系列文章,歡迎閱讀 帶你深入AI(1) - 深度學習模型訓練痛點及解決方法 自然語言處理1 – 分詞 Tensorflow原始碼解析1 – 核心架構和原始碼結構 Tensorflow原始碼解析2 – 前後端連線的橋樑 - Session Tensorflow
Microsoft VBScript 執行時錯誤 '800a0005'無效的過程呼叫或引數: 'Instr'
從後臺上傳軟體時出現這個報警,麻煩幫忙解決一下!報警內容:Microsoft VBScript 執行時錯誤 '800a0005' 無效的過程呼叫或引數: 'Instr' /upload.asp, line 656程式碼:if Instr(intTemp,strItem,"fi