ruby debug

check debug

前回やったrubyのwebrickがDebianで動かない問題を追跡する。OpenBSDで試そうと思ったら、byebugが動かない。なんでかなと思ったらOpenBSD(64bit)には、ruby 3.0系が入っていた。どうも対応していないみたい。(debian 32bitにはruby 3.1が入ってるけど、こちらでも使えなかった)

ob$ byebug -t --no-stop ref.rb
Tracing: ref.rb:1 require 'sqlite3'
No such file or directory @ rb_sysopen - <internal:/usr/local/lib/ruby/3.0/rubygems/core_ext/kernel_require.rb>
["/usr/local/lib/ruby/gems/3.0/gems/byebug-11.1.3/lib/byebug/helpers/file.rb:20:in `initialize'", ...

このbyebygはgitのいいねを見ると、3万人もの支持を貰っているから、人気なんだけど、近い将来ruby 3.X 対応になるかな。

待っていられないので、rubyのdebug関係をリサーチしたよ。

Ruby 3.1 の debug.gem を自慢したい

ふー、時代は変っているのね。

debug.gem

ob$ gem info debug

 *** LOCAL GEMS ***

debug (1.5.0, 0.2.1)
    Author: Koichi Sasada
    Homepage: https://github.com/ruby/debug
    Licenses: Ruby, BSD-2-Clause
    Installed at (1.5.0): /usr/local/lib/ruby/gems/3.0
                 (0.2.1, default): /usr/local/lib/ruby/gems/3.0

    Debugging functionality for Ruby

gemにはこんなコマンドも用意されてる。巷ではgem installしかお目にかからないけど。

ob$ doas gem uninstall -v 0.2.1 debug
Gem debug-0.2.1 cannot be uninstalled because it is a default gem

バージョンを指定して削除しようにもデフォは拒否された。複数のバージョンがある時は、最新のものが使われるのかな? それとも、ロシアン・ルーレット? こういう細部が気になるぞと。

ob$ rdbg30 -h
/usr/local/bin/rdbg30 [options] -- [debuggee options]

Debug console mode:
    -n, --nonstop                    Do not stop at the beginning of the script.
   :

普通に使おう、rdbgだな。何よりもgdbと同じ使い心地が嬉しい。

tracer

で、少々困ったのが、byebugにあったトレース機能。debugにも有るんだけど、少々目的が違うみたい。

元々rubyの標準装備に、トレーサーが有る亊に気付いた。まずはマニュアルだな。

sakae@deb:~$ ri -a Tracer

-a を付けると、みんな見せてくれるよ。ついでにriの挙動を少々確かめた。

sakae@deb:~$ ri -a Webrick
Nothing known about Webrick
Did you mean?  WEBrick
sakae@deb:~$ ri -a webrick
Nothing known about .webrick

riを使う時は検索文字の頭を大文字で始めるのがよさそう。これってクラスだな。

で、トレースしたい範囲を指定するのが楽だ。前回やったgd.rbとかに埋め込む。

require 'tracer'

  ew = sw + "%"
  Tracer.on
  db = SQLite3::Database.new("/usr/share/dict/ejdict.sqlite3")
   :
  stmt.close(); db.close()
  Tracer.off

これで、必要部分だけがトレースされる。

#1:gd.rb:36:Object:-:     db = SQLite3::Database.new("/usr/share/dict/ejdict.sqlite3")
#1:/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:65:SQLite3::Database:>:     def initialize file,
options = {}, zvfs = nil
  :
#1:/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/resultset.rb:136:SQLite3::ResultSet:<:     end
#1:gd.rb:46:Object:-:     stmt.close(); db.close()
#1:gd.rb:47:Object:-:     Tracer.off
#2:/usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/utils.rb:187:WEBrick::Utils::TimeoutHandler:-:             @queue.clear

勘弁して下さいよ。長い長い接頭語。資料によると、スレッド番号、スクリプトファイルPath、行番号、クラス名、シンボルがコロン区切で並び、その後、ソースって塩梅だ。

ファイル名はdir名前を省いたものが欲しいけど、そんな亊簡単に出来るのか? 後で調べてみる。

比べてみるぞ。

FreeBSD, Debian で対比してみた。どうもOpenBSDのスレッドは過去に苦い経験があるのでさけたのさ。

#1:gd.rb:36:Object:-:     db = SQLite3::Database.new("/usr/sh   #1:gd.rb:36:Object:-:     db = SQLite3::Database.new("/usr/sh
#1:database.rb:65:SQLite3::Database:>:     def initialize fil   #1:database.rb:65:SQLite3::Database:>:     def initialize fil
 :
#1:resultset.rb:109:SQLite3::ResultSet:-:       return nil if   #1:resultset.rb:109:SQLite3::ResultSet:-:       return nil if
#1:resultset.rb:111:SQLite3::ResultSet:-:       row = @db.tra <
#1:statement.rb:117:SQLite3::Statement:>:     def types       <
 :
#1:resultset.rb:136:SQLite3::ResultSet:<:     end               #1:resultset.rb:136:SQLite3::ResultSet:<:     end
#1:gd.rb:46:Object:-:     stmt.close(); db.close()              #1:gd.rb:46:Object:-:     stmt.close(); db.close()
#1:gd.rb:47:Object:-:     Tracer.off                            #1:gd.rb:47:Object:-:     Tracer.off
                                                              > #2:/usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webr

左側がFreeBSD、右側がDebianだ。FreeBSDには、ruby 2.7.6と言う、2.X 最後の版が入っている。 やはり、リナは検索をさぼっている。これじゃ進展が無いな。どうする?

次の手段

折角便利なrdbgが有るんだから、使ってみろよってのは、置いておいて別の角度から。straceしてみるかな。ref.rbは、普通にsqliteを使うやつ。前回のsql.rb相当。

sakae@deb:/tmp/t$ strace -o REF ruby ref.rb
sakae@deb:/tmp/t$ strace -o GD  ruby gd.rb

そしてこんな風に、bashの荒技を繰り出す。一時ファイルを作らなくて済むってのがラクダ。この機能は褒めてやる。普段使いのOpenBSDのshellには無い機能だ。それからBSDにはstrace相当としてktraceが有るけど、数値の羅列でちと不親切。問題発生がリナで良かった。をぃ。

sdiff -w 126 <(grep sqlite3 REF | grep -v 'No such') <(grep sqlite3 GD | grep -v 'No such')
  :
lstat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/li   lstat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/li
lstat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/li   lstat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/li
stat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib   stat64("/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib
lstat64("/usr/share/dict/ejdict.sqlite3", {st_mode=S_IFREG|06 <
openat(AT_FDCWD, "/usr/share/dict/ejdict.sqlite3", O_RDWR|O_C <
openat(AT_FDCWD, "/usr/share/dict/ejdict.sqlite3", O_RDONLY|O <
stat64("/usr/share/dict/ejdict.sqlite3", {st_mode=S_IFREG|064 <
stat64("/usr/share/dict/ejdict.sqlite3", {st_mode=S_IFREG|064 <
stat64("/usr/share/dict/ejdict.sqlite3", {st_mode=S_IFREG|064 <

そして、驚愕の事実が判明した。影も形もなく、辞書ファイルにアクセスしていない。

エラーを起してみる

本当に辞書ファイルを認識してるの? ちょいと辞書名をありえない名前に変更してみる。

sakae@deb:/tmp/t$ ruby ref.rb
Content-type: text/html; charset=UTF-8
/usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89:in `open_v2': unable to open database file (SQLite3::CantOpenException)
        from /usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89:in `initialize'
        from ref.rb:5:in `new'
        from ref.rb:5:in `search'
        from ref.rb:16:in `<main>'

ちゃんとnewする所でエラーになったな。

sakae@deb:/tmp/t$ ruby gd.rb
[2022-04-19 06:00:44] INFO  WEBrick 1.7.0
[2022-04-19 06:00:44] INFO  ruby 3.1.1 (2022-02-18) [i686-linux]
[2022-04-19 06:00:44] INFO  WEBrick::HTTPServer#start: pid=1727 port=8080
127.0.0.1 - - [19/Apr/2022:06:01:08 JST] "GET / HTTP/1.0" 200 254
- -> /
[2022-04-19 06:01:16] ERROR SQLite3::CantOpenException: unable to open database file
        /usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89:in `open_v2'
        /usr/local/lib/ruby/gems/3.1.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89:in `initialize'
        gd.rb:34:in `new'
        gd.rb:34:in `block in httpd'
        /usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/httpservlet/prochandler.rb:39:in `do_GET'
        /usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/httpservlet/abstract.rb:105:in `service'
        /usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb:140:in `service'
        /usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb:96:in `run'
        /usr/local/lib/ruby/gems/3.1.0/gems/webrick-1.7.0/lib/webrick/server.rb:310:in `block in start_thread'
127.0.0.1 - - [19/Apr/2022:06:01:16 JST] "POST /ejdict HTTP/1.0" 500 308
http://localhost:8080/ -> /ejdict

深い所でエラーが発生。

Internal Server Error

unable to open database file
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WEBrick/1.7.0 (Ruby/3.1.1/2022-02-18) at localhost:8080

そして、こちらはクライアント側のブラウザー画面。昔は、よくこの画面を三田ぞ。

何でOS依存で不都合な亊が起きるのよ。今迄の経験ではリナで動くのにBSD系では駄目ってパターンばかりだった。BSD人口が少くなった今、目が行き届かないってのは理解出来る。でも、リナで動かないってのは、超不満。

OS界隈に例えると、モジュールの中のドライバーが動かなくて、右往左往してる図だな。 それもトップハーフは問題なくてボトムハーフに何か問題ありそう、かな。

今回だと、sqlite3の深い所の問題のように思える。それも sqlite3_native.so あたりの問題。上記のOSの例に例えると、ボトムハーフ問題、かな。どうしたらいいの? 誰か教えて!!

play with debug.gem

もう、いじけて、遊んでみるしかない。

with chrome

README.mdを見ていると、ブラウザーと接続して使えるようだ。firefoxが対象になっていないのは、民主主義の勝利なんだな。一番利用者が多い所をターゲットにするのは資本主義の道理。

chromeはもしもの為にWindowsに入れてる。そして、debianは、VMWarePlayerの中。そんな環境で使えるのか?

sakae@pen:/tmp/t$ rdbg ref.rb  --open=chrome
DEBUGGER: Debugger can attach via TCP/IP (127.0.0.1:38139)
DEBUGGER: wait for debugger connection...
DEBUGGER: With Chrome browser, type the following URL in the address-bar:

   devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=127.0.0.1:38139/d7a9b....

ローカルホスト限定ってのが辛い所。Debianに入っているchromeもどきなら動くかな? あれ、近頃BUGに余念がないv8って指定も入っているな。

取り敢えず、関係先を視察する。 … server_cdp.rb

        port, path, pid = run_new_chrome
        begin
          s = Socket.tcp '127.0.0.1', port

        when /linux/
          'google-chrome'
        else
          raise "Unsupported OS"

        if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/

        req = "GET #{path} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: U\
pgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key:\
 #{key}==\r\n\r\n"

config

(rdbg)
[89, 98] in /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb
    89|         open_v2 file.encode("utf-8"), mode, zvfs
    90|       end
    91|
    92|       @tracefunc        = nil
    93|       @authorizer       = nil
=>  94|       @encoding         = nil
    95|       @busy_handler     = nil
    96|       @collations       = {}
    97|       @functions        = {}
    98|       @results_as_hash  = options[:results_as_hash]
=>#0    SQLite3::Database#initialize(file="/usr/share/dict/ejdict.sqlite3", options={}, zvfs=nil) at /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:94
  #1    [C] Class#new at ref.rb:5
  # and 2 frames (use `bt' command for all frames)

毎度お馴染の、/var/lib/gem… が煩わしい。こういう時は

(rdbg) config use_short_path = true    # command
use_short_path = true          # UI: Show shorten PATH (like $(Gem)/foo.rb)

短かくしてよの指示を与える。

[91, 100] in $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb
    91|
    92|       @tracefunc        = nil
    93|       @authorizer       = nil
    94|       @encoding         = nil
    95|       @busy_handler     = nil
=>  96|       @collations       = {}
    97|       @functions        = {}
    98|       @results_as_hash  = options[:results_as_hash]
    99|       @type_translation = options[:type_translation]
   100|       @type_translator  = make_type_translator @type_translation
=>#0    SQLite3::Database#initialize(file="/usr/share/dict/ejdict.sqlite3", options={}, zvfs=nil) at $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb:96
  #1    [C] Class#new at ref.rb:5
  # and 2 frames (use `bt' command for all frames)

rdbg起動時から有効にするには、~/.rdbgrcに記述しておく。

trace

(rdbg) trace call into: LOG    # command
Enable CallTracer (enabled) into: LOG
(rdbg) trace    # command
Tracers:
 * #0 CallTracer (enabled) into: LOG

こういう指定をしておいて、continueすると記録が残る。

sakae@pen:/tmp/t$ head -5 LOG
PID:2328 CallTracer (disabled) into: LOG
DEBUGGER (trace/call) #th:1 #depth:5 >    String#encode at /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89
DEBUGGER (trace/call) #th:1 #depth:5 <    String#encode #=> "/usr/share/dict/ejdict.sqlite3" at /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89
DEBUGGER (trace/call) #th:1 #depth:5 >    SQLite3::Database#open_v2 at /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89
DEBUGGER (trace/call) #th:1 #depth:5 <    SQLite3::Database#open_v2 #=> #<SQLite3::Database:0x00007f9d50066220> at /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:89

目が痛くなるようなログが採取された。schemeに装備しなきゃならないtrace機能がdebug.gemから利用出来るのは、良い亊だ。

record

レコードしておくと、過去に戻れる。まずは関連コマンド確認。

(rdbg) h record    # help command

### Trace

 * `record`
  * Show recording status.
 * `record [on|off]`
  * Start/Stop recording.
 * `step back`
  * Start replay. Step back with the last execution log.
  * `s[tep]` does stepping forward with the last log.
 * `step reset`
  * Stop replay .

レコードしてから再生ね。

(rdbg) record on    # command
Recorder for #<Thread:0x00000712bf9981e8 run>: on (0 records)
(rdbg) s    # step command
[90, 95] in $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb
    90|       end
    91|
    92|       @tracefunc        = nil
=>  93|       @authorizer       = nil
    94|       @encoding         = nil
    95|       @busy_handler     = nil
=>#0    SQLite3::Database#initialize(file="/usr/share/dict/ejdict.sqlite3", opti
ons={}, zvfs=nil) at $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb:93
  #1    [C] Class#new at ref.rb:5
  # and 2 frames (use `bt' command for all frames)
  :
(rdbg) record    # command
Recorder for #<Thread:0x00000712bf9981e8 run>: on (15 records)
(rdbg) s back    # step command
[replay] [145, 150] in $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb
[replay]    145|     #
[replay]    146|     def prepare sql
[replay]    147|       stmt = SQLite3::Statement.new( self, sql )
[replay] => 148|       return stmt unless block_given?
[replay]    149|
[replay]    150|       begin
[replay] =>#0   SQLite3::Database#prepare(sql="SELECT word,mean FROM items WHERE
 word ...) at $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb:148
[replay]   #1   Object#search(sw="ruby") at ref.rb:6
[replay]   # and 1 frames (use `bt' command for all frames)
(rdbg)
[replay] [144, 149] in $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb
[replay]    144|     # The Statement can then be executed using Statement#execute.
[replay]    145|     #
[replay]    146|     def prepare sql
[replay] => 147|       stmt = SQLite3::Statement.new( self, sql )
[replay]    148|       return stmt unless block_given?
[replay]    149|
[replay] =>#0   SQLite3::Database#prepare(sql="SELECT word,mean FROM items WHERE
 word ...) at $(Gem)/sqlite3-1.4.2/lib/sqlite3/database.rb:147
[replay]   #1   Object#search(sw="ruby") at ref.rb:6
[replay]   # and 1 frames (use `bt' command for all frames)

再生中ってマークで、それと分る。

i and o

i と o も大事だよ。ってか、便利だよ。

(rdbg) i    # info command
%self = #<SQLite3::Statement:0x000007127ef09c08 @connection=#<SQLite3::Datab...>
bind_vars = []
@columns = nil
@connection = #<SQLite3::Database:0x0000071258d681e0 @tracefunc=nil, @author...>
@remainder = ""
@types = nil
(rdbg) o    # outline command
Enumerable#methods:
  all?             any?              chain         chunk       chunk_while
  collect          collect_concat    count         cycle       detect
   :
SQLite3::Statement#methods:
  active?          bind_param   bind_parameter_count  bind_params
  clear_bindings!  close        closed?               column_count
  column_decltype  column_name  columns               database_name
  done?            each         execute               execute!
  must_be_open!    remainder    reset!                step
  types
instance variables: @columns  @connection  @remainder  @types
locals: bind_vars

This year's Index

Home