List of Stack - text.Baldanders.info
tag:text.Baldanders.info,2016-02-13:/tags
2016-02-13T14:48:05+09:00
帰ってきた「しっぽのさきっちょ」
https://text.baldanders.info/images/avatar.jpg
https://text.baldanders.info/images/avatar.jpg
スタック追跡とパニック・ハンドリング
tag:text.Baldanders.info,2016-02-13:/golang/stack-trace-and-panic-handling/
2016-02-13T05:48:05+00:00
2020-01-02T00:55:59+00:00
panic 時の出力をカスタマイズすることを考える。スタック情報を取得するには, panic を recover で捕まえた上で runtime.Caller() 関数を使う。
Spiegel
https://baldanders.info/profile/
<p>今回は軽めの小ネタで。</p>
<p>「<a href="https://text.baldanders.info/golang/error-handling/">エラー・ハンドリングについて</a>」でも少し説明したが, <a href="https://golang.org/" title="The Go Programming Language">Go 言語</a>では回復不能のエラー(ゼロ除算やメモリ不足など)が発生した場合には <a href="http://blog.golang.org/defer-panic-and-recover" title="Defer, Panic, and Recover - The Go Blog">panic</a> を投げる仕様になっている。
たとえば以下のコードでは</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"os"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">os</span><span class="p">.</span><span class="nf">Exit</span><span class="p">(</span><span class="nf">run</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">run</span><span class="p">()</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">f</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">numbers</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">numbers</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span> <span class="c1">//panic!
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>以下のスタック情報が標準エラー出力に表示される<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。
(<a href="https://play.golang.org/">The Go Playground</a> での実行結果)</p>
<pre tabindex="0"><code>panic: runtime error: index out of range
goroutine 1 [running]:
main.f()
/tmp/sandbox269685094/main.go:19 +0x160
main.run(0x20300, 0x104000e0)
/tmp/sandbox269685094/main.go:13 +0x20
main.main()
/tmp/sandbox269685094/main.go:9 +0x20
</code></pre><p>まぁ必要な情報はあるのでこれでも構わないのだが,ファイル名がフルパスで表示されるのがアレな感じである。
また出力先が標準エラー出力で固定されているのも面白くない。</p>
<p>そこで <a href="http://blog.golang.org/defer-panic-and-recover" title="Defer, Panic, and Recover - The Go Blog">panic</a> 時の出力をカスタマイズすることを考える。
スタック情報を取得するには, <a href="http://blog.golang.org/defer-panic-and-recover" title="Defer, Panic, and Recover - The Go Blog">panic</a> を <a href="http://blog.golang.org/defer-panic-and-recover" title="Defer, Panic, and Recover - The Go Blog">recover</a> で捕まえた上で <a href="https://golang.org/pkg/runtime/" title="runtime - The Go Programming Language"><code>runtime</code></a><code>.Caller()</code> 関数を使う。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"io"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"os"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"runtime"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">os</span><span class="p">.</span><span class="nf">Exit</span><span class="p">(</span><span class="nf">run</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stderr</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">run</span><span class="p">(</span><span class="nx">log</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span> <span class="p">(</span><span class="nx">exit</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">defer</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">r</span> <span class="o">:=</span> <span class="nb">recover</span><span class="p">();</span> <span class="nx">r</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">log</span><span class="p">,</span> <span class="s">"Panic: %v\n"</span><span class="p">,</span> <span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">depth</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">;</span> <span class="nx">depth</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pc</span><span class="p">,</span> <span class="nx">src</span><span class="p">,</span> <span class="nx">line</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">runtime</span><span class="p">.</span><span class="nf">Caller</span><span class="p">(</span><span class="nx">depth</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">break</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">log</span><span class="p">,</span> <span class="s">" -> %d: %s: %s(%d)\n"</span><span class="p">,</span> <span class="nx">depth</span><span class="p">,</span> <span class="nx">runtime</span><span class="p">.</span><span class="nf">FuncForPC</span><span class="p">(</span><span class="nx">pc</span><span class="p">).</span><span class="nf">Name</span><span class="p">(),</span> <span class="nx">src</span><span class="p">,</span> <span class="nx">line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">exit</span> <span class="p">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nf">f</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="nx">exit</span> <span class="p">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">f</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">numbers</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">numbers</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span> <span class="c1">//panic!
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>これで出力は</p>
<pre tabindex="0"><code>Panic: runtime error: index out of range
-> 0: main.run.func1: /tmp/sandbox562252505/main.go(19)
-> 1: runtime.call16: /usr/local/go/src/runtime/asm_amd64p32.s(390)
-> 2: runtime.gopanic: /usr/local/go/src/runtime/panic.go(423)
-> 3: runtime.panicindex: /usr/local/go/src/runtime/panic.go(12)
-> 4: main.f: /tmp/sandbox562252505/main.go(36)
-> 5: main.run: /tmp/sandbox562252505/main.go(29)
-> 6: main.main: /tmp/sandbox562252505/main.go(11)
-> 7: runtime.main: /usr/local/go/src/runtime/proc.go(111)
-> 8: runtime.goexit: /usr/local/go/src/runtime/asm_amd64p32.s(1133)
</code></pre><p>となる。
ファイル名を出力したくないなら for 文の中を</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">for</span> <span class="nx">depth</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">;</span> <span class="nx">depth</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">pc</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">line</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">runtime</span><span class="p">.</span><span class="nf">Caller</span><span class="p">(</span><span class="nx">depth</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">break</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintf</span><span class="p">(</span><span class="nx">log</span><span class="p">,</span> <span class="s">" -> %d: %s: (%d)\n"</span><span class="p">,</span> <span class="nx">depth</span><span class="p">,</span> <span class="nx">runtime</span><span class="p">.</span><span class="nf">FuncForPC</span><span class="p">(</span><span class="nx">pc</span><span class="p">).</span><span class="nf">Name</span><span class="p">(),</span> <span class="nx">line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>とする手もある。
コードを書いてる人はスタック追跡情報とファイルの行番号があれば大体あたりをつけられるので,これだけでもありがたい。</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="http://sgykfjsm.github.io/blog/2016/01/20/golang-function-tracing/">Goでfunctionが実行された順番を追いかける - sgykfjsm.github.com</a></li>
<li><a href="http://qiita.com/kitsuyui/items/d03a9de90330d8c275c8">Go のバイナリには -ldflags ‘-w -s’ でコンパイルしてもたくさんパスが埋め込まれていた - Qiita</a></li>
</ul>
<p><a href="https://text.baldanders.info/golang/bookmark/">Go 言語に関するブックマーク集はこちら</a>。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>ちなみにこの情報は <code>-s</code> のリンクオプション(ビルド時に <code>-ldflags "-s"</code> と指定する)でデバッグ用のシンボル情報を削除しても表示されるようだ。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>