🔧

Zigの実行ファイルをgdbで追いかけてみる

2023/06/19に公開1

gdbでZigはまだサポートされていませんが、今の段階でもけっこう使えます。

zig, gdb のバージョン

$ zig version
0.11.0-dev.3704+729a051e9
$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

zigのビルドとシンボル情報の確認

使用するソースコードは前回の記事のa3.zigです。

$ zig build-exe a3.zig
$ file ./a3
./a3: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

デバッグ情報がついていて、stripされていないことが確認できました。

mainの関数のシンボルを調べてみます。

$ nm ./a3 |grep main
000000000021974c t a3.main
0000000000204788 r builtin.panic_messages.exact_division_remainder
00000000002056a3 r builtin.panic_messages.exact_division_remainder__anon_6900
000000000027c000 b os.linux.tls.main_thread_tls_buffer

どうやら a3.main がa3のmain関数のようです。
他に a3.がつくシンボルをみてみると、以下のものが見つかりました。

$ nm ./a3 |grep 'a3\.'
000000000021974c t a3.main
0000000000200e38 r a3.OUTPUT_FILENAME_PATTERN
000000000020509c r a3.OUTPUT_FILENAME_PATTERN__anon_3721
0000000000218ff4 t a3.writeFile

gdbを使ってみる

gdbの起動

$ gdb ./a3
  ...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a3...
(gdb)  

a3.mainにブレークポイントをかけて、実行

(gdb) break a3.main
Breakpoint 1 at 0x219784: file a3.zig, line 14.
(gdb) run < a.mjpeg 
Starting program: /autofs/usbssd1/koba/work/zig/jpegdec/split_jpeg/a3 < a.mjpeg

Breakpoint 1, a3.main () at a3.zig:14
14	    var allocator = std.heap.page_allocator;
(gdb) 

n(next) コマンドで、ソースコードの行単位でステップ実行

(gdb) n
15	    var buffer: [BUF_SIZE]u8 = undefined;
(gdb) n
16	    var frame_num: usize = 0;
(gdb) n
17	    var write_buffer = std.ArrayList(u8).init(allocator);
(gdb) n
25	    var state: State = State.st0;
(gdb) n
28	        const n = try io.getStdIn().read(&buffer);
(gdb) n
29	        if (n == 0) break;
(gdb) n
31	        var i: usize = 0;
(gdb) n
32	        while (i < n) : (i += 1) {
(gdb) n
33	            switch (state) {
(gdb) n
35	                    if (buffer[i] == JPEG_START0) {
(gdb) n
36	                        state = State.st1;
(gdb) n
32	        while (i < n) : (i += 1) {
(gdb) n
32	        while (i < n) : (i += 1) {
(gdb) n
33	            switch (state) {
(gdb) n
40	                    if (buffer[i] == JPEG_START1) {
(gdb) n
41	                        try write_buffer.append(JPEG_START0);
(gdb) n
42	                        try write_buffer.append(JPEG_START1);
(gdb) n
43	                        state = State.st2;
(gdb) n
32	        while (i < n) : (i += 1) {
(gdb) 

変数の値を見てみる。

(gdb) p n
$1 = 65536
(gdb) p i
$2 = 1
(gdb) p buffer
$3 = "\377\330\377\333\000\305\000\003\003\003\003\003\003\004\004\004\004\005\004\003\003\005\a\005\004\004\005\a\a\005\005", '\t' <repeats 16 times>, '\n' <repeats 24 times>, "\001\003\005\005\a\a\a\t\t\t\t\v\t\t\t", '\v' <repeats 18 times>, '\f' <repeats 32 times>, "\002\003\005\005\a\a\a\t\t\t\t\v\t\t\t", '\v' <repeats 18 times>, '\f' <repeats 32 times>...
(gdb) 

アセンブラ命令単位でステップ実行する

(gdb) display/i $pc
1: x/i $pc
=> 0x219f9c <a3.main+2144>:	ldr	x9, [x9, #176]
(gdb) si
0x0000000000219fa0	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fa0 <a3.main+2148>:	adds	x10, x9, #0x1
(gdb) si
0x0000000000219fa4	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fa4 <a3.main+2152>:	cset	w9, cs  // cs = hs, nlast
(gdb) si
0x0000000000219fa8	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fa8 <a3.main+2156>:	str	x10, [x8]
(gdb) si
0x0000000000219fac	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fac <a3.main+2160>:	and	w9, w9, #0x1
(gdb) si
0x0000000000219fb0	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fb0 <a3.main+2164>:	and	w9, w9, #0x1
(gdb) si
0x0000000000219fb4	32	        while (i < n) : (i += 1) {
1: x/i $pc
=> 0x219fb4 <a3.main+2168>:	strb	w9, [x8, #8]
(gdb) 

別の関数にブレークポイントをかけて、そこまで実行する

(gdb) b a3.writeFile
Breakpoint 2 at 0x219010: file a3.zig, line 71.
(gdb) c
Continuing.

Breakpoint 2, a3.writeFile (frame_num=0, buf=...) at a3.zig:71
71	    var filename_buf: [32]u8 = undefined;
1: x/i $pc
=> 0x219010 <a3.writeFile+44>:	mov	x10, #0x101010101010101     	// #72340172838076673
(gdb) 

引数の値を見てみる

(gdb) p frame_num
$2 = 0
(gdb) p buf
$3 = {ptr = 0xfffff7ff3000 "\377\330\377", <incomplete sequence \333>, len = 11537}
(gdb) 

bufの型は []const u8 なのですが、ptrlenのフィールドを持つstructと同等の扱いになっているようですね。

バックトレースを見る

(gdb) bt
#0  a3.writeFile (frame_num=0, buf=...) at a3.zig:71
#1  0x0000000000219e30 in a3.main () at a3.zig:57
#2  0x0000000000218f38 in start.callMain ()
    at /opt/zig-linux-aarch64-0.11.0-dev.3704+729a051e9/lib/std/start.zig:608
#3  start.initEventLoopAndCallMain ()
    at /opt/zig-linux-aarch64-0.11.0-dev.3704+729a051e9/lib/std/start.zig:542
#4  start.callMainWithArgs () at /opt/zig-linux-aarch64-0.11.0-dev.3704+729a051e9/lib/std/start.zig:492
#5  start.posixCallMainAndExit ()
    at /opt/zig-linux-aarch64-0.11.0-dev.3704+729a051e9/lib/std/start.zig:455
#6  0x0000000000218a0c in _start ()
    at /opt/zig-linux-aarch64-0.11.0-dev.3704+729a051e9/lib/std/start.zig:367
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) 

これだけ使えれば、けっこういいですね。
もちろん、Zigの文法を解釈できないので、p buffer[0..n] のようなことはできません。

関連

https://zenn.dev/tetsu_koba/articles/ff873cd2ff1f5c

Discussion

ktz_aliasktz_alias

VSCodeにCodeLLDB機能拡張入れてデバッグしてる。

プロジェクト直下に .vscode/launch.json作り、

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "program": "${workspaceFolder}/zig-out/bin/${workspaceFolderBasename}",
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}

デバッグ開始するだけなので、ゆるふわレベルな私にも安心(複数のArtifact作る場合はちょっと工夫がいるかも)。
実行ファイルはこれでいけてる。

ユニットテストへのデバッガはちょっとめんどくさい模様。
https://zig.news/guidorice/comment/5k