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関係をリサーチしたよ。
ふー、時代は変っているのね。
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