4. Hugo
Hugo は Go 言語で書かれた静的サイトジェネレータ
● https://gohugo.io
● Hugo には 2 種類あり、それぞれのバイナリが配布されている
○ Hugo
■ 基本機能のみの Hugo
○ Hugo Extended
■ SASS/SCSS のアセット処理もできる拡張版 Hugo
■ C++ で書かれたライブラリ libsass を組み込んでいる (cgo)
5. Hugo Extended on Docker
先日 gitlab.com/pages/hugo に対してコントリビュートした
● https://gitlab.com/pages/hugo/merge_requests/83
● プロジェクト Docker レジストリに hugo_extended イメージを追加
○ registry.gitlab.com/pages/hugo/hugo
○ registry.gitlab.com/pages/hugo/hugo_extended
● GitHub の Release からビルドされたバイナリをダウンロードして
Alpine Linux のコンテナで動かす、よくある構成
6. Hugo Extended on Alpine Linux
その後 hugo_extended イメージが動かないという報告がきた
● https://gitlab.com/pages/hugo/issues/31
● 複雑な SCSS を含むサイトで Segmentation fault が起きるらしい
● 同じ問題を抱えている人が結構多いらしい
○ Alpine Linux を使っているとだめ
○ musl libc だと動かない glibc だと動く
7. musl libc
musl は glibc と ABI 互換性のある Linux 用軽量 C ランタイムライブラリ
● https://www.musl-libc.org
● コンテナ容量削減の目的で Alpine Linux などで使われている
● glibc でビルドした実行ファイルがそのまま動くが一部動かないものがある
● musl が原因で Alpine Linux で動かない場合の一般的な解決策
a. Alpine Linux をやめて Debian などを使う → コンテナサイズふえる
b. Alpine Linux の中に glibc もインストールする → めんどくさい
https://gitlab.com/dar/hugo/blob/feat-postCSS/Dockerfile
8. 原因の調査
$ git clone https://gitlab.com/pirivan/pirivan.gitlab.io
$ cd pirivan.gitlab.io
$ git checkout development
$ docker run --rm -it -v $PWD:/src --workdir /src registry.gitlab.com/pages/hugo/hugo_extended /bin/sh
/src # hugo
Building sites ... Segmentation fault
まずは報告のあったサイトで hugo を動かしてみる
10. gdb による調査
Thread 6 "hugo" received signal SIGSEGV, Segmentation fault.
[Switching to LWP 19]
0x0000000000f5807e in Sass::Parser::parse_expression() ()
(gdb) info threads
Id Target Id Frame
1 LWP 11 "hugo" 0x00000000004e63e3 in ?? ()
2 LWP 15 "hugo" 0x00000000004e5e4d in ?? ()
3 LWP 16 "hugo" 0x00000000004e63e3 in ?? ()
4 LWP 17 "hugo" 0x00000000004e63e3 in ?? ()
5 LWP 18 "hugo" 0x00000000004e63e3 in ?? ()
* 6 LWP 19 "hugo" 0x0000000000f5807e in Sass::Parser::parse_expression() ()
7 LWP 20 "hugo" 0x00000000004e63e3 in ?? ()
8 LWP 21 "hugo" 0x00000000004e63e3 in ?? ()
9 LWP 22 "hugo" 0x00000000004e63e3 in ?? ()
10 LWP 23 "hugo" 0x00000000004e63e3 in ?? ()
11 LWP 24 "hugo" 0x00000000004e63e3 in ?? ()
12 LWP 25 "hugo" 0x00000000004e63e3 in ?? ()
13 LWP 26 "hugo" 0x00000000004e63e3 in ?? ()
スレッドのひとつが libsass 関数の中で SIGSEGV
11. gdb による調査
(gdb) bt
#0 0x0000000000f5807e in Sass::Parser::parse_expression() ()
#1 0x0000000000f59520 in Sass::Parser::parse_relation() ()
#2 0x0000000000f5a7c0 in Sass::Parser::parse_conjunction() ()
...
...
#90 0x0000000000f94988 in sass_compiler_parse ()
#91 0x0000000000ea0f87 in _cgo_a9af2a58ca08_Cfunc_sass_compiler_parse ()
#92 0x00000000004e3d10 in ?? ()
#93 0x0000000001ea6100 in __bss_start ()
#94 0x0000000000000001 in ?? ()
#95 0x0000000001ec0006 in __bss_start ()
#96 0x00007ffff5a221f0 in ?? ()
#97 0x000000c000fecee8 in ?? ()
#98 0x0000000000002b40 in ?? ()
#99 0x000000c000001e00 in ?? ()
#100 0x00000000004b9690 in ?? ()
#101 0x0000000000000000 in ?? ()
スタックが深い... これはスタックオーバーフロー?
12. musl スレッドスタックサイズ
Thread stack size
The default stack size for new threads on glibc is determined
based on the resource limit governing the main thread’s stack
(RLIMIT_STACK). It generally ends up being 2-10 MB.
musl provides a default stack size of 80k. This does not
include the guard page, nor does it include the space used for
TLS unless total TLS size is very small. So the actual map size
may appear closer to 90k, with around 80k usable by the
application. This size was determined empirically with the goals of
not gratuitously breaking applications but also not causing large
amounts of memory and virtual address space to be committed in
programs with large numbers of threads. Programs needing
larger stacks, or which explicitly want a smaller stack, should
make this explicit with pthread_attr_setstacksize. For largely
unrestrained use of the standard library, a minimum of 12k is
recommended, but stack sizes down to 2k are allowed.
https://wiki.musl-libc.org/functional-differences-from-glibc.html
● muslはスレッドに対して極端に少ない
スタックを割り当てる
● 80KBと書いてあるが現在は128KBに
増量されている
● 解決方法はスレッドを作る前に
pthread_attr_setstacksize() などでス
タックサイズを増やすこと
● Ruby・Python含む様々なプロジェクト
がmuslのスタックオーバーフローに辛
酸を舐めさせられている
● Goではlibsassのような外部ライブラリ
を呼び出すときに問題になる
14. musl 新機能の発見
support setting of default thread stack size via
PT_GNU_STACK header
this facilitates building software that assumes a large default
stack size without any patching to call pthread_setattr_default_np
or pthread_attr_setstacksize at each thread creation site, using
just LDFLAGS.
normally the PT_GNU_STACK header is used only to reflect
whether executable stack is desired, but with GNU ld at least,
passing -Wl,-z,stack-size=N will set a size on the program header.
with this patch, that size will be incorporated into the default
stack size (subject to increase-only rule and
DEFAULT_STACK_MAX limit).
http://git.musl-libc.org/cgit/musl/commit/?id=7b3348a98c139b4
b4238384e52d4b0eb237e4833
● なんと ELF のプログラムヘッダの
PT_GNU_STACK セクションのサイズで
デフォルトスタックサイズを変更できる
機能が追加されていることがわかった
(ELF = Linux などで使われる実行ファ
イルの形式のこと)
● リンク時のオプションで
-Wl,-z,stack-size=N のように設
定することが前提だが…
● 実行ファイルしかなくても ELF プログラ
ムヘッダにバイナリパッチできればス
タックサイズは増やせる!