アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

Perl の warn は何をしているの?

最近 Go ばっかだったので、久しぶりに Perl を書いてると色々疑問が出てきました。その中の一つが「$SIG{__WARN__} に代入されたサブルーチンが実行されるタイミング」でした。

__WARN__ とあるので、 warn を実行した時がトリガーになって実行されるものだろうというのは予想できていましたが、僕が warn の処理内容を内部で caller のようなものを実行し、package 名、warn を実行した行番号を取得して stderr へ出力しているものだと思っていました。そのため「stderr を通じて出力された時がトリガーとなって $SIG{__WARN__} が呼び出される」と考えたのですが、Okinawa.pm の Slack で
f:id:codehex:20170226224215p:plain
というようなアドバイスをもらったので以下のようなコードを書いて試してみました。

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 を作った

Code-Hex/Golet - GitHub  GitHub stars

Golet can manage many services with goroutine from one golang program.

Go で複数コマンドを同時に実行したり、 Cron のようにあるタイミングで何か実行させたりなどを行うことができるものを作りました。

これは元々 PerlProclet という 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さんの「APIPerlで作る時に僕達が考えたこと 」
  • charsbarさんの「2016年のPerl (Long Version)」
  • Vickenty Fesunovさんの「Writing Perl extensions in Rust (English) 」
  • Dan Kogaiさんの「Number Unlimited」
  • kazeburoさんの「Site Reliability Engineeringの話」
  • 高山裕司さんの「CMSAPI の素敵な関係」
  • 千葉 誠さんの「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"

typeint

const (
    男 性 = iota
    女
)

typestruct {
    性別     性
    名前     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 をデバッグしてみた

環境

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:448switch 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:477b.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 の事。 とりあえずこの並び替えによって全体の実行時間に影響がないことは分かりました。
深さ優先探索の後順というのはこういうものです。(懐かしい感じ)
tree_postorder_traverse
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.

引数の量がすごいという印象。そこは置いておいて、ここで急に ln が効かなくなって困ったので、もう一度、ソースコードを読んで 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 コマンドをデバッグしないと分からないので、今度はそれにチャレンジをしてみようと思います。

*1:ar コマンドという archive ファイルを操作できるコマンドがありますが、tar は ar の一般化したものだという話を最近講義で習いました。多分このこと