Perl の warn は何をしているの?
最近 Go ばっかだったので、久しぶりに Perl を書いてると色々疑問が出てきました。その中の一つが「$SIG{__WARN__}
に代入されたサブルーチンが実行されるタイミング」でした。
__WARN__
とあるので、 warn
を実行した時がトリガーになって実行されるものだろうというのは予想できていましたが、僕が warn
の処理内容を内部で caller
のようなものを実行し、package 名、warn
を実行した行番号を取得して stderr へ出力しているものだと思っていました。そのため「stderr を通じて出力された時がトリガーとなって $SIG{__WARN__}
が呼び出される」と考えたのですが、Okinawa.pm の Slack で
というようなアドバイスをもらったので以下のようなコードを書いて試してみました。
use strict; use warnings; use Carp; BEGIN { $SIG{__WARN__} = sub { print "CALLED!!\n" }; } warn "First\n"; carp "Second\n"; print STDERR "Third\n";
結果は
CALLED!! CALLED!! Third
というような結果を得ることができました。carp
も内部では warn
を呼び出しているようなので、結果から warn
を実行したタイミングがトリガーになるということが分かりました!
…が今度は warn
って実際に何やってるのだろうという疑問が出てきたので、コードを大まかに追ってみることにしました。
これが多分 warn
のソースコード。この関数の中で vwarn
という関数を呼んでいますが、実際は Perl_vwarn のマクロ みたいですね!
ということは warn
の本体である Perl_vwarn
を読んでみると stderr へ書き出す前に invoke_exception_hook
という S_invoke_exception_hook
のマクロが呼び出されていることが分かりますね!
S_invoke_exception_hook のコードです。
STATIC bool S_invoke_exception_hook(pTHX_ SV *ex, bool warn) { HV *stash; GV *gv; CV *cv; SV **const hook = warn ? &PL_warnhook : &PL_diehook; /* sv_2cv might call Perl_croak() or Perl_warner() */ SV * const oldhook = *hook; if (!oldhook) return FALSE; ENTER; SAVESPTR(*hook); *hook = NULL; cv = sv_2cv(oldhook, &stash, &gv, 0); LEAVE; if (cv && !CvDEPTH(cv) && (CvROOT(cv) || CvXSUB(cv))) { dSP; SV *exarg; ENTER; save_re_context(); if (warn) { SAVESPTR(*hook); *hook = NULL; } exarg = newSVsv(ex); SvREADONLY_on(exarg); SAVEFREESV(exarg); PUSHSTACKi(warn ? PERLSI_WARNHOOK : PERLSI_DIEHOOK); PUSHMARK(SP); XPUSHs(exarg); PUTBACK; call_sv(MUTABLE_SV(cv), G_DISCARD); POPSTACK; LEAVE; return TRUE; } return FALSE; }
分からない部分多すぎて泣けてくる…
きっと SV **const hook = warn ? &PL_warnhook : &PL_diehook;
の部分で hook
変数に $SIG{__WARN__}
へ代入されたサブルーチンを渡していて、SV * const oldhook = *hook;
して cv = sv_2cv(oldhook, &stash, &gv, 0);
を行って最終的に cv
変数の中にサブルーチンが代入されるのだろうと。 call_sv(MUTABLE_SV(cv), G_DISCARD);
でそのサブルーチンが実行されているんだろうなと考えました。間違っていたら教えてください。
ちなみに $SIG{__WARN__}
部分のコードは Perl_magic_setsig
だと思います。
結論
warn
は stderr へ出力するだけの関数ではない。warn
は$SIG{__WARN__}
に代入されたサブルーチンを実行して stderr へ出力する。
…多分!!!!
Go で複数プロジェクトを同時に起動する Golet を作った
Golet can manage many services with goroutine from one golang program.
Go で複数コマンドを同時に実行したり、 Cron のようにあるタイミングで何か実行させたりなどを行うことができるものを作りました。
これは元々 Perl で Proclet という kazeburo さんが作成したものを基にして作ってたのがきっかけです。Proclet 本当に素晴らしいモジュールなので Perl 使ってる方は是非使ってみてください!!
そもそも何ができるのか
例えば、マイクロサービス的な言語別の複数プロジェクトを一つのサービスとして扱いたい、つまり複数プロジェクトを起動したい時に Golet を使うことができます。
どんなコードを書けばいいのかは Synopsis を見ると分かるはずです。
特徴
自動でサービスごとのポートの割り当てや cron で動かすタスクを指定することもできます。 github.com/robfig/cron のフォーマットを使って動かします。
動きはこんな感じ。
興味があれば使ってみてください。
約10年共に生活したカメが亡くなりました
昨日1月8日に小6の頃から生活を共にしてきたクサガメのカメ丸がお亡くなりになりました。
僕の家では2匹のカメを水槽別々で飼っていて、カメ丸はそのうちの一匹でした。(もう一匹は甲太郎です。)水槽を洗う時はよくベランダに野放しにしていると、室外機の下や、園芸用道具箱の隙間などによく入っていく子でした。つい最近、カメ丸の水槽につけているフィルターも新しい物へ変えたばっかりだったし、ついこの間まで水槽内を暴れまわるくらい元気だったので、突然の別れが本当に信じられませんでした。
亡くなったカメ丸は近くの公園にある森林のエリアで埋葬しました。
そして今日の午後に飼っていた水槽を片付けすることができました。
今の僕の気持ちとしては、なんで早く異変に気付いてやれなかったのか、一昨日からもう少し観察していれば変化に気づいていたのではないかなどの後悔があります。そして今までキツイことがある度に何度もカメ達に癒されてきたことか、一匹でも存在を失ったことが正直とても辛いです。
このブログを書いた目的としては、今までの思い出(めっちゃ沢山ある)を思い出しながら自分の気持ちを新たに切り替えていこうという気持ちで書きました。
そして残りのもう一匹である甲太郎をカメ丸の分、精一杯可愛がろうと思います。
カメ丸よ本当に今までありがとう。そしてお疲れ様でした。また会う日まで。
YAPC::Hokkaido へ参加してきた
この記事は 12/10 の Perl入学式 Advent Calendar 2016 も兼ねています。本当は沖縄のPerl入学式の様子を書こうと思ってましたが、今日が YAPC::Hokkaido だったので、どういう雰囲気だったのかを踏まえて書くことができると良いかなと思います。
僕が聞くことができたセッション、LT一覧です。
前夜祭
- JSON, JSON::PP, and more
- 楽器アプリ制作の裏側
- From Perl to Haskell
- Perl MongersのためのサーバーサイドSwift入門
- なるほどErlangプロセス
前夜祭はどんな感じだったかというと、乾杯した後LT大会が始まってからが盛り上がってきました。しかも本セッションで聴けてもいいのでは?といった内容だったので、来年のYAPCに参加したい方は是非前夜祭から行くことをお勧めします。
本セッション
- 奥 一穂さんの「HTTP/2の最適化について」
- papixさんの「APIをPerlで作る時に僕達が考えたこと 」
- charsbarさんの「2016年のPerl (Long Version)」
- Vickenty Fesunovさんの「Writing Perl extensions in Rust (English) 」
- Dan Kogaiさんの「Number Unlimited」
- kazeburoさんの「Site Reliability Engineeringの話」
- 高山裕司さんの「CMS と API の素敵な関係」
- 千葉 誠さんの「Vue.jsによるWebアプリケーション開発 」
- karupaneruraさんの「頼りがいのあるORM「Aniki」徹底解説!」
- masayoshiさんの「はてなのインフラ環境を自宅で再現する」
- Yappoさんの「LINE Bot on the Perl」
- Miyagawaさんの「Delivering a CDN」
一番感じたことは、YAPCはそれぞれのトークが部屋ごとに分かれており、「もう一人自分がいれば...!!!!」というくらい全てのセッションを聴きたかったです。 僕はあまりにも学べることがあったので、メモのためにツイートの数が増えてしまいました。
感想
僕は前夜祭からの参加することにしました。今回の YAPC に参加して感じたことは、トークの内容が少しついていけない部分があるなーと感じましたが、何が分からなかったという部分を覚えていれば、後で発表者に直接聞いてみるということができたので、そういうことをできるのが YAPC の凄さかなと思いました。
セッション後は懇親会が開催されました。懇親会では Perl 界の重鎮達と会話する機会があったので積極的に話を聞きに行きました。皆さん優しいのでちゃんと話を聞いてくれるし、話をしてくれますので、初心者でも積極的に話を聞きに行くことができればとても勉強になるだろうなーと感じました。
次はYAPC::Kansaiがあるのでこの記事を読んでくれている皆さん是非参加してみましょう!
cgo をちゃんと使ってみる!!
cgo を使ってみます
cgo って何?という時にここを読むと良さそうです!
コードを書いてみる
cgo では C.C言語の関数()
とすることで, C言語から関数を呼び出すことができます. 実際に簡単な print を行うコードを書いてみます. ちなみに OS は Mac OSX 10.11.6 です.
package main /* #include <stdio.h> void print(const char *str) { printf("%s", str); } */ import "C" func main() { str := C.CString("Hello, World\n") C.print(str) }
出力結果は
Hello, World
となります. 始め
C.printf(C.CString("Hello, World\n"))
という風に書いてましたが, unexpected type: ...
というエラーが出てコンパイルができませんでした. これは僕の予想ですが, printf(1)
の引数は次のようになっているからだと考えました.
int printf(const char * __restrict, ...)
事実はわからないので, 知ってる方教えて欲しいです.
C で得られた結果を Go で扱ってみる
package main /* package main /* #include <string.h> int num(int n) { return 10 + n; } float flt(float f) { return f + 20.5; } char *str(char *s) { return strcat(s, " Good!!"); } */ import "C" import "fmt" func main() { n := C.num(10) fmt.Println(n) f := C.flt(20.78) fmt.Println(f) s := C.str(C.CString("Condition is")) fmt.Println(C.GoString(s)) }
得られた出力結果です.
20 41.28 Condition is Good!!
結構簡単にやり取りできますが, C で扱う文字列と Go で扱う文字列は型が異なるため, C.GoString
を利用して C 側で得られた文字列の結果を Go の文字列へ変換してあげることがポイントです.
Go から C の Macro を呼び出してみる
Macro に文字列, をしていしてる場合は次のように Go の文字列として扱うことができるようです.
package main /* #define Hello "Hello" #define INT 10 #define CHR 'A' */ import "C" import "fmt" func main() { fmt.Println(C.Hello, C.INT, C.CHR) }
結果は次のようになります.
Hello 10 65
しかし, まだ対応してないのか #define FLOAT 1.234
のような Macro を呼び出そうとするとエラーになります.
C の errno を Go の error として扱う
cgo のドキュメントには次のような記述があります.
Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error (use _ to skip the result value if the function returns void).
試してみましょう.
package main /* #include <math.h> */ import "C" import ( "fmt" "log" ) func main() { n, err := C.sqrt(-1) //n, err := C.sqrt(4) if err != nil { log.Fatal(err.Error()) } fmt.Println(n) }
結果は
NaN
ちゃんとエラーを得ることができているみたいですね!
Go でかわいいコードを書きたいの♡
この記事は 12/3 の okinawago アドベントカレンダーの記事です。 サーモンを作った話をする予定でしたが全く進んでないので... かわいいコードを書いてみようと思います(´∀`艸)♡♡♡♡♡
かわいいの定義
「かわいい」にはいろいろ定義がありそうなのでここでは僕がかわいいと思ったことを可愛いということにしますね!!
Go 1.7.4 のシンタックス
Go 1.7.4 の構文の識別子 (identifier) は次のような条件になっています。
unicode.IsLetter(c) || c == '_' || unicode.IsDigit(c)
1.7.4 の識別子のソースコードです。
ちなみに、この記事を書いてる時点での開発中の最新版のソースコードと違いますが、どうやら最新版も同じ条件みたいです。
ここで unicode.IsLetter(r rune) bool
がどのような文字をカバーしているのか気になります。そこで unicode.IsLetter(r rune) bool
を使い true の時だけ出力するコードを書いてみましょう。
package main import ( "fmt" "unicode" ) func main() { for i := 0; i < 0xffff; i++ { if unicode.IsLetter(rune(i)) { fmt.Printf("%c\n", i) } } }
今回は 0x0000
から 0xffff
までを範囲としています。
このコードを実行した結果をファイルに書き出して、less(1) などで閲覧するのも有りでしょう。
日本語を使ったコードを書いてみる
それでは日本語を使ったコードを書いてみましょう。
package main import "fmt" type 性 int const ( 男 性 = iota 女 ) type 人 struct { 性別 性 名前 string 住んでる場所 string } func 生まれる(性別 性, 名前, 住んでる場所 string) *人 { return &人{ 性別: 性別, 名前: 名前, 住んでる場所: 住んでる場所, } } func main() { 瀧 := 生まれる(男, "瀧", "東京") 三葉 := 生まれる(女, "三葉", "糸守町") 入れ替わり(瀧, 三葉) fmt.Println(瀧.君の名前は()) fmt.Println(三葉.君の名前は()) } func (相手の 人) 君の名前は() string { return 相手の.名前 } func 入れ替わり(瀧, 三葉 *人) { これってもしかして, 私たち身体が := 入れ替わってるー(瀧, 三葉) *瀧, *三葉 = *これってもしかして, *私たち身体が } func 入れ替わってるー(瀧, 三葉 *人) (*人, *人) { return 三葉, 瀧 }
結果は
三葉 瀧
うん!入れ替わってますね!!
カゝ ゎ レヽ レヽ ] ─ ├″を書 、キ ま ι ょ ぅ ♡
それではかわいいコードを書いてみましょう。
package main import "fmt" func main() { ょιょιˊȎωȎˋノ("இдஇ") ㄘんㄘんぺㄋぺㄋ("。╹ω╹。") } func ょιょιˊȎωȎˋノ(இдஇ string) { fmt.Printf("ょιょιˊʘωʘˋノ(%s;)\n", இдஇ) } func ㄘんㄘんぺㄋぺㄋ(カゝぉ string) { fmt.Printf("ㄘんㄘんぺㄋぺㄋ(%s)\n", カゝぉ) }
結果は
ょιょιˊʘωʘˋノ(இдஇ;) ㄘんㄘんぺㄋぺㄋ(。╹ω╹。)
かわいい!!!!!
go build をデバッグしてみた
環境
- MacBook Air (11-inch, Mid 2012)
- OSX 10.11.6(15G1108)
- メモリ 8 GB
- 2 GHz Intel Core i7
build を行うソースコード
今回、チャネルや goroutine は build 時にどうなるのか気になったので、このようなソースコードを準備しました。
package main func main() { title := "Hello, World" strCh := make(chan string) go func() { s := <-strCh println(s) }() strCh <- title close(strCh) }
gdb で go build を読んでいく
「go1.7.3 の go コマンドを gdb でデバッグする」の内容を把握していることを前提として進めていきます。
gdb で起動する
それではやっていきましょう。 今ディレクトリはこのようになっています。
$ pwd /cloned/path/to/go/bin $ ls ./ ../ go* main.go
この状態で
$ gdb go (gdb) b main.main Breakpoint 1 at... (gdb) r build main.go Starting program: /cloned/path/to/go/bin/go build main.go [New Thread 0x121f of process 50639] [New Thread 0x1403 of process 50639] [New Thread 0x1503 of process 50639] [New Thread 0x1603 of process 50639] [New Thread 0x1703 of process 50639] Thread 1 hit Breakpoint 1... 118 func main() { (gdb)
これで読み進めていけます。
next を cmd.Run(cmd, args)
まで行う
cmd.Run(cmd, args)
は 181 行目 に存在します。commands から range を用いて、 cmd に代入していっている様子がわかりますね。つまりこれが各サブコマンドの実行時のコードとなるわけです。
cmd.Run(cmd, args)
まで来たら
(gdb) s
で中を見てみましょう。
runBuild(cmd *Command, args []string)
の中へステップできたと思います。
/src/cmd/go/build.go
を読んでいく
ここから先は自分の読みたいところを読んでいきましょう。 ここから先は僕の作業ログです。有りえないほど汚いです。
func runBuild
が実行されることを知り,(gdb) b main.runBuild
- 読み進めていき、build.go:448 の
switch buildContext.Compiler {
へ到着します。今回の場合条件分岐はcase "gc"
になりました。 (gdb) b 467
(gdb) c
a := b.action(modeInstall, depMode, p)
へやってきました。この処理がどうなっているか見ていきます。(gdb) s
func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action { return b.action1(mode, depMode, p, false, "") }
なんだよ b.action1
って!!となりましたが続けます。
build.go の 900 行目です。
return b.action1(mode, depMode, p, false, "")
の所で(gdb) s
を実行しメソッド内へ(gdb) b 1000
を実行
ここでちょこちょこ出てくる modeBuild
, modeInstall
が気になりました。これらの正体は
const ( modeBuild buildMode = iota modeInstall )
なんと iota
さんじゃないですかー :smile:
(gdb) p mode
を実行。 mode == 1 だったので modeInstall
なんだこれは...
a.f = (*builder).install
builder は構造体で、元を辿ると build.go:477 の b.action()
の b
です。
つまり builder から生えてる install メソッドですね!!
そして次の行。
a.deps = []*action{b.action1(modeBuild, depMode, p, lookshared, forShlib)}
またお前か!! >> b.action1
!!
どうやら今回は modeBuild
になる様子。先ほど b 1000
していたので (gdb) n
を実行するとネストで実行された b.action1
の中の 1000 行目で止まります。 (gdb) n
で 1030 行目へ。
a.f = (*builder).build
install と同様、 builder から生えてる build メソッドでした!! んでまたどんどん進めていくと。 - 1055 で作成された action を return することが分かります。
この時の (gdb) bt
です。
(gdb) bt #0 main.(*builder).action1 (b=0xc4201a5c00, mode=0, depMode=0, p=0xc4201c0000, lookshared=false, forShlib="", ~r5=0x0) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1055 #1 0x000000000000c784 in main.(*builder).action1 (b=0xc4201a5c00, mode=1, depMode=0, p=0xc4201c0000, lookshared=false, forShlib="", ~r5=0x0) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1004 #2 0x000000000000b139 in main.(*builder).action (b=0xc4201a5c00, mode=1, depMode=0, p=0xc4201c0000, ~r3=0x0) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:893 #3 0x0000000000006a47 in main.runBuild (cmd=0x64d540 <main.statictmp_5573>, args= []string = {...}) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:477 #4 0x00000000000498a8 in main.main () at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/main.go:181
main() の中の runBuild() 内の b.action() 中の b.action1() のネストの b.action1() が return されることが分かります。これから b.action() としていた理由は action を生成するためのネストしないメソッドだったことが分かりますね!( b.action 自体をネストするのは良くなかったのか...??)
元の b.action1 に戻ってきました。(/src/cmd/go/build.go の 1005行目) next を実行していくと...
main.runBuild (cmd=0x64d540 <main.statictmp_5573>, args= []string = {...}) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:478 478 b.do(a)
やっとで戻ってきましたねー 素早く backtrace を カチャカチャカチャッターン!!
#0 main.runBuild (cmd=0x64d540 <main.statictmp_5573>, args= []string = {...}) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:478 #1 0x00000000000498a8 in main.main () at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/main.go:181
ただいま! main.runBuild !! ここにいます。
b.do(a)
を追う
この記事を書きながら実行しているので予想ですが、このメソッドがコンパイルを行っているんだと思います!(ここでコンパイルしてくれ!!) ちなみにこのコードは build.go:1223 です。
ソースコードにはコメントがあります。
// Build list of all actions, assigning depth-first post-order priority. // The original implementation here was a true queue // (using a channel) but it had the effect of getting // distracted by low-level leaf actions to the detriment // of completing higher-level actions. The order of // work does not matter much to overall execution time, // but when running "go test std" it is nice to see each test // results as soon as possible. The priorities assigned // ensure that, all else being equal, the execution prefers // to do what it would have done first in a simple depth-first // dependency order traversal. all := actionList(root)
actionlist を作って 深さ優先探索の後順で並び替えるということ? leaf action というのは探索木の葉の位置に存在する action の事。
とりあえずこの並び替えによって全体の実行時間に影響がないことは分かりました。
深さ優先探索の後順というのはこういうものです。(懐かしい感じ)
1240行目から始めます。
(gdb) n
(gdb) p all
こういう結果が得られました。
$3 = []*main.action = {0xc420432340, 0xc420432410, 0xc420432270, 0xc4204321a0, 0xc4204324e0, 0xc4204320d0} (gdb) p *all.array[0] $4 = {p = 0xc4201c1200, deps = []*main.action, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc420432340, ignoreFail = false, link = false, pkgdir = "/usr/local/opt/go/libexec/pkg", objdir = "", objpkg = "", target = "", pending = 0, priority = 0, failed = false} (gdb) p *all.array[1] $5 = {p = 0xc4201c1680, deps = []*main.action, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc420432410, ignoreFail = false, link = false, pkgdir = "/usr/local/opt/go/libexec/pkg", objdir = "", objpkg = "", target = "/usr/local/opt/go/libexec/pkg/darwin_amd64/runtime/internal/sys.a", pending = 0, priority = 0, failed = false} (gdb) p *all.array[2] $6 = {p = 0xc4201c0d80, deps = []*main.action = {0xc420432340, 0xc420432410}, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc420432270, ignoreFail = false, link = false, pkgdir = "/usr/local/opt/go/libexec/pkg", objdir = "", objpkg = "", target = "/usr/local/opt/go/libexec/pkg/darwin_amd64/runtime/internal/atomic.a", pending = 0, priority = 0, failed = false} (gdb) p *all.array[3] $7 = {p = 0xc4201c0480, deps = []*main.action = {0xc420432270, 0xc420432410, 0xc420432340, 0xc420432410}, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc4204321a0, ignoreFail = false, link = false, pkgdir = "/usr/local/opt/go/libexec/pkg", objdir = "", objpkg = "", target = "/usr/local/opt/go/libexec/pkg/darwin_amd64/runtime.a", pending = 0, priority = 0, failed = false} (gdb) p *all.array[4] $8 = {p = 0xc4201c0000, deps = []*main.action = {0xc4204321a0}, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc4204324e0, ignoreFail = false, link = true, pkgdir = "", objdir = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028/command-line-arguments/_obj/", objpkg = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028/command-line-arguments.a", target = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028/command-line-arguments/_obj/exe/a.out", pending = 0, priority = 0, failed = false} (gdb) p *all.array[5] $9 = {p = 0xc4201c0000, deps = []*main.action = {0xc4204324e0}, triggers = []*main.action, cgo = 0x0, args = []string, testOutput = 0x0, f = {void (struct main.builder *, struct main.action *, error *)} 0xc4204320d0, ignoreFail = false, link = true, pkgdir = "", objdir = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028/command-line-arguments/_obj/", objpkg = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028/command-line-arguments.a", target = "main", pending = 0, priority = 0, failed = false}
a.f
はクロージャですね! a.deps
は action のスライスのようです。
なんかすごい。こうやって見ていくと objdir や objpkg から go もオブジェクトファイルを作っていたんだ!という驚きがありますね!
(gdb) n
で進めていく。- 1245 行目,
b.readySema = make(chan bool, len(all))
- b.readySema を queue として扱っているようです。
- b は builder 構造体のポインタです。現在の中身はこうなってます。
(gdb) p *b $20 = {work = "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build184457028", actionCache = map[main.cacheKey]*main.action = {[{mode = 1, p = 0xc4201c0000, shlib = ""}] = 0xc4204320d0, [{mode = 0, p = 0xc4201c0480, shlib = ""}] = 0xc4204321a0, [{mode = 0, p = 0xc4201c0d80, shlib = ""}] = 0xc420432270, [{mode = 0, p = 0xc4201c1200, shlib = ""}] = 0xc420432340, [{mode = 0, p = 0xc4201c1680, shlib = ""}] = 0xc420432410, [{mode = 0, p = 0xc4201c0000, shlib = ""}] = 0xc4204324e0}, mkdirCache = map[string]bool, flagCache = map[string]bool<error reading variable: Cannot access memory at address 0x9>, print = {void (struct []interface {}, int *, error *, ...)} 0xc4201a5c00, output = {state = 0, sema = 0}, scriptDir = "", exec = {state = 0, sema = 0}, readySema = 0xc4204143f0, ready = { array = 0x0, len = 0, cap = 0}}
...(結構疲れてきた)
- 1248 行目へ
- ここで action を action.deps の中の action.triggers に push していく(何故これを行うのか分からないです)
- 1261 行目でクロージャを作成
- 1306 行目からビルドの始まり
1259 // Handle runs a single action and takes care of triggering 1260 // any actions that are runnable as a result. 1261 handle := func(a *action) { 1262 var err error 1263 if a.f != nil && (!a.failed || a.ignoreFail) { 1264 err = a.f(b, a) 1265 }
先ほど a.f
がクロージャだということは理解してるので 1264 行目に breakpoint を仕掛けます。
(gdb) b build.go:1264
(gdb) c
Thread 6 hit Breakpoint 4, main.(*builder).do.func1 (a=0xc42041e4e0) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1264 1264 err = a.f(b, a) (gdb) s main.(*builder).build (b=0xc4201c1c00, a=0xc420454340, err=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1334 1334 func (b *builder) build(a *action) (err error) {
この結果見て気づいたのは、メソッド呼び出しをクロージャから行う場合、引数を二つ渡してあげることで可能なことが分かりました。
(gdb) l
でソースコードを見ていくと
1493 // Prepare Go import path list. 1494 inc := b.includeArgs("-I", allArchiveActions(a)) 1495 1496 // Compile Go. 1497 ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, len(sfiles) > 0, inc, gofiles)
よし、見ていくぞ!!
(gdb) b 1497
(gdb) c
Thread 7 hit Breakpoint 5, main.(*builder).build (b=0xc420193c00, a=0xc420428410, err=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1497 1497 ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, len(sfiles) > 0, inc, gofiles) (gdb) s main.(*gcToolchain).gc (this=0x670e50 <runtime.zerobase>, b=0xc4201b6070, p=0xc4201ca000, archive="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build692270690/command-line-arguments.a", obj="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build692270690/command-line-arguments/_obj/", asmhdr=false, importArgs= []string = {...}, gofiles= []string = {...}, ofile="", output= []uint8, err=...) at <autogenerated>:5 5 <autogenerated>: No such file or directory.
引数の量がすごいという印象。そこは置いておいて、ここで急に l
や n
が効かなくなって困ったので、もう一度、ソースコードを読んで breakpoint を仕掛けてみます。
ソースコードは cmd/go/build.go の 2264 行目です。
この中の 2322 行目の b.runOut
が気になったのでそこで breakpoint を仕掛けます。
(gdb) b cmd/go/build.go:2322
(gdb) s
(gdb) l
(gdb) l
こうするとこれくらいのソースコードを gdb で表示できますね。
2034 return messages 2035 } 2036 2037 // runOut runs the command given by cmdline in the directory dir. 2038 // It returns the command output and any errors that occurred. 2039 func (b *builder) runOut(dir string, desc string, env []string, cmdargs ...interface{}) ([]byte, error) { 2040 cmdline := stringList(cmdargs...) 2041 if buildN || buildX { 2042 var envcmdline string 2043 for i := range env { (gdb) 2044 envcmdline += env[i] 2045 envcmdline += " " 2046 } 2047 envcmdline += joinUnambiguously(cmdline) 2048 b.showcmd(dir, "%s", envcmdline) 2049 if buildN { 2050 return nil, nil 2051 } 2052 } 2053 (gdb) 2054 nbusy := 0 2055 for { 2056 var buf bytes.Buffer 2057 cmd := exec.Command(cmdline[0], cmdline[1:]...) 2058 cmd.Stdout = &buf 2059 cmd.Stderr = &buf 2060 cmd.Dir = dir 2061 cmd.Env = mergeEnvLists(env, envForDir(cmd.Dir, os.Environ())) 2062 err := cmd.Run() 2063
cmd.Run()
を実行してる時点で外部コマンドを実行していることが分かりますね!(まじかよ...)
とりあえず cmdline
を読んでみましょう。
(gdb) n
(gdb) p cmdline
$1 = []string = {"/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/compile", "-o", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments.a", "-trimpath", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832", "-p", "main", "-complete", "-buildid", "c9a41e1d0bf90f085a8ae6008ac361fb59c7a503", "-D", "_/Users/CodeHex/.ghq/github.com/go/bin", "-I", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832", "-pack", "/Users/CodeHex/.ghq/github.com/go/bin/main.go"}
こういう感じに go の compile コマンドを使って archive ファイル*1を作り出してることが分かりますね!
しかしこれだけだと archive ファイルを作って終わりなので、実行ファイルができているということは何かしらリンクが行われていることを予想し、(gdb) b cmd/go/build.go:2039
を行いました。するとどうでしょう
$2 = []string = {"/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/link", "-o", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments/_obj/exe/a.out", "-L", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832", "-extld=clang", "-buildmode=exe", "-buildid=c9a41e1d0bf90f085a8ae6008ac361fb59c7a503", "/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments.a"}
link コマンドってあったのかー! ちなみに backtrace は
(gdb) bt #0 main.(*builder).runOut (b=0xc420192070, dir=".", desc="command-line-arguments", env= []string, cmdargs= []interface {} = {...}, ~r4= []uint8, ~r5=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:2041 #1 0x000000000001b308 in main.(*builder).run (b=0xc420192070, dir=".", desc="command-line-arguments", env= []string, cmdargs= []interface {} = {...}, ~r4=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:2007 #2 0x0000000000022dbe in main.gcToolchain.ld (b=0xc420192070, root=0xc420403520, out="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments/_obj/exe/a.out", allactions= []*main.action = {...}, mainpkg="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments.a", ofiles= []string, ~r6=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:2530 #3 0x000000000008d030 in main.(*gcToolchain).ld (this=0x670e50 <runtime.zerobase>, b=0xc420192070, root=0xc420403520, out="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments/_obj/exe/a.out", allactions= []*main.action = {...}, mainpkg="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build038795832/command-line-arguments.a", ofiles= []string, ~r6=...) at <autogenerated>:9 #4 0x0000000000014ccb in main.(*builder).build (b=0xc420192070, a=0xc420403520, err=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1583 #5 0x0000000000084125 in main.(*builder).do.func1 (a=0xc420403520) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1264 #6 0x000000000008462b in main.(*builder).do.func2 (&wg=0xc42041a150, b=0xc420192070, handle={void (struct main.action *)} 0xc420111f88) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1321 #7 0x00000000000ebec1 in runtime.goexit () at /Users/CodeHex/.ghq/github.com/go/src/runtime/asm_amd64.s:2086 #8 0x000000c42041a150 in ?? () #9 0x000000c420192070 in ?? () #10 0x000000c42040f7e0 in ?? () #11 0x0000000000000000 in ?? ()
もともとは main.gcToolchain.ld
から呼び出されているようですね!
しかし link コマンドを実行する際の引数を見て分かる通り、a.out
を吐いていますね。しかし、go build を実行したカレントディレクトリに build したソースコードと同一名のバイナリが吐き出されるはずなので、 a.out
を作った後、move を実行しているに違いないと考えました。検索してみると、moveOrCopyFile
というのがいましたねー
一度処理を終了させて、(gdb) b cmd/go/build.go:1732
を行いもう一度実行してみます。
Thread 4 hit Breakpoint 1, main.(*builder).runOut (b=0xc4201b6070, dir="/Users/CodeHex/.ghq/github.com/go/bin", desc="command-line-arguments", env= []string, cmdargs= []interface {} = {...}, ~r4= []uint8, ~r5=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:2039 2039 func (b *builder) runOut(dir string, desc string, env []string, cmdargs ...interface{}) ([]byte, error) { (gdb) c Continuing. Thread 4 hit Breakpoint 1, main.(*builder).runOut (b=0xc4201b6070, dir=".", desc="command-line-arguments", env= []string, cmdargs= []interface {} = {...}, ~r4= []uint8, ~r5=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:2039 2039 func (b *builder) runOut(dir string, desc string, env []string, cmdargs ...interface{}) ([]byte, error) { (gdb) c Continuing. [Switching to Thread 0x1afb of process 53762] Thread 6 hit Breakpoint 2, main.(*builder).moveOrCopyFile (b=0xc4201b6070, a=0xc420427110, dst="main", src="/var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build294100860/command-line-arguments/_obj/exe/a.out", perm=511, force=false, ~r5=...) at /Users/CodeHex/.ghq/github.com/go/src/cmd/go/build.go:1732 1732 func (b *builder) moveOrCopyFile(a *action, dst, src string, perm os.FileMode, force bool) error {
ビンゴですね!
2 回コマンド(compile, link)を実行した後にカレントディレクトリへ move を実行してそうです。
この時以下のコマンドを別プロセスの shell から実行してみました。
$ ls /var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build294100860/command-line-arguments/_obj/exe ./ ../ a.out* $ /var/folders/8b/tw364d5n15qc3wdz3k622qxm0000gn/T/go-build294100860/command-line-arguments/_obj/exe/a.out Hello, World
これを確認した後に gdb 側で move の処理を終了させてみます。moveOrCopyFile
内の os.Rename(src, dst) の行
ですね!そこに breakpoint を仕掛けます。
そして (gdb) n
を実行すると、カレントディレクトリに main バイナリが作成されます!やったね!
まとめ
- go build はコマンドのラッパーである
- pure go の go build は
compile
,link
を実行する /var
以下にランダムなディレクトリが作成されてその中にa.out
が作成される- 最終的にカレントディレクトにへ move を行う
結局コンパイル時に何をやっているかについては compile コマンドをデバッグしないと分からないので、今度はそれにチャレンジをしてみようと思います。