From e5bacc03f20c3be5006f0283200b48a15ff699b7 Mon Sep 17 00:00:00 2001 From: mimaki Date: Tue, 18 Nov 2014 23:33:32 +0900 Subject: [PATCH] Add mruby debugger (mrdb) --- AUTHORS | 11 + Rakefile | 10 + build_config.rb | 25 + doc/debugger/README.md | 341 ++++++++ include/mruby.h | 2 + mrbgems/full-core.gembox | 2 +- mrbgems/mruby-bin-debugger/bintest/mrdb.rb | 286 +++++++ mrbgems/mruby-bin-debugger/bintest/print.rb | 701 ++++++++++++++++ mrbgems/mruby-bin-debugger/mrbgem.rake | 9 + .../mruby-bin-debugger/tools/mrdb/apibreak.c | 529 +++++++++++++ .../mruby-bin-debugger/tools/mrdb/apibreak.h | 26 + .../mruby-bin-debugger/tools/mrdb/apilist.c | 232 ++++++ .../mruby-bin-debugger/tools/mrdb/apilist.h | 14 + .../mruby-bin-debugger/tools/mrdb/apiprint.c | 78 ++ .../mruby-bin-debugger/tools/mrdb/apiprint.h | 13 + .../mruby-bin-debugger/tools/mrdb/cmdbreak.c | 426 ++++++++++ .../mruby-bin-debugger/tools/mrdb/cmdmisc.c | 500 ++++++++++++ .../mruby-bin-debugger/tools/mrdb/cmdprint.c | 58 ++ .../mruby-bin-debugger/tools/mrdb/cmdrun.c | 54 ++ mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c | 746 ++++++++++++++++++ mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h | 163 ++++ .../mruby-bin-debugger/tools/mrdb/mrdbconf.h | 16 + .../mruby-bin-debugger/tools/mrdb/mrdberror.h | 20 + 23 files changed, 4261 insertions(+), 1 deletion(-) create mode 100755 doc/debugger/README.md create mode 100755 mrbgems/mruby-bin-debugger/bintest/mrdb.rb create mode 100755 mrbgems/mruby-bin-debugger/bintest/print.rb create mode 100755 mrbgems/mruby-bin-debugger/mrbgem.rake create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h create mode 100755 mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h diff --git a/AUTHORS b/AUTHORS index 555cf7743a..c782589150 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,3 +20,14 @@ Original Authors "mruby developers" are: Tatsuhiko Kubo Takeshi Watanabe Yuki Kurihara + specified non-profit corporation mruby Forum + Kazuaki Tanaka + Hiromasa Ishii + Hiroshi Mimaki + Satoshi Odawara + Mitsubishi Electric Micro-Computer Application Software Co.,Ltd. + Hiroyuki Matsuzaki + Yuhei Okazaki + Manycolors, Inc. + Shota Nakano + Yuichi Osawa diff --git a/Rakefile b/Rakefile index 135c7a6a2a..cc6ded8d0f 100644 --- a/Rakefile +++ b/Rakefile @@ -75,6 +75,16 @@ MRuby.each_target do |target| FileUtils.cp t.prerequisites.first, t.name, { :verbose => $verbose } end depfiles += [ install_path ] + elsif target == MRuby.targets['host-debug'] + unless MRuby.targets['host'].gems.map {|g| g.bins}.include?([bin]) + install_path = MRuby.targets['host-debug'].exefile("#{MRUBY_ROOT}/bin/#{bin}") + + file install_path => exec do |t| + FileUtils.rm_f t.name, { :verbose => $verbose } + FileUtils.cp t.prerequisites.first, t.name, { :verbose => $verbose } + end + depfiles += [ install_path ] + end else depfiles += [ exec ] end diff --git a/build_config.rb b/build_config.rb index 795251486e..3408f19a13 100644 --- a/build_config.rb +++ b/build_config.rb @@ -83,6 +83,31 @@ # conf.enable_bintest end +MRuby::Build.new('host-debug') do |conf| + # load specific toolchain settings + + # Gets set by the VS command prompts. + if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR'] + toolchain :visualcpp + else + toolchain :gcc + end + + enable_debug + + # include the default GEMs + conf.gembox 'default' + + # C compiler settings + conf.cc.defines = %w(ENABLE_DEBUG) + + # Generate mruby debugger command (require mruby-eval) + conf.gem :core => "mruby-bin-debugger" + + # bintest + # conf.enable_bintest +end + # Define cross build settings # MRuby::CrossBuild.new('32bit') do |conf| # toolchain :gcc diff --git a/doc/debugger/README.md b/doc/debugger/README.md new file mode 100755 index 0000000000..7e4b281a67 --- /dev/null +++ b/doc/debugger/README.md @@ -0,0 +1,341 @@ +# How to use mruby debugger mrdb + +copyright (c) 2014 Specified Non-Profit Coorporation mruby Forum + +## 1.Summary +This file documents the method for using the mruby debugger 'mrdb' + +## 2 debugging with mrdb + +## 2.1 Building mrdb + +The trunk of the mruby source tree can be checked out with following command/ + +```bash +$ git clone https://github.com/Kumikomi-Ruby/forum-mruby.git mruby +``` + +Run make command +```bash +$ cd mruby +$ make +``` + +By default, make command will install debugger files into mruby/bin. + +You can add the path for mrdb on your host environment with following command + +```bash +$ echo "export PATH=\$PATH:MRUBY_ROOT/bin" >> ~/.bashrc +$ source ~/.bashrc +``` + +*MRUBY_ROOT is the directory in which mruby source cords will be installed. + +To confirm mrdb was instaleed properly,run mrdb with --version option + +```bash +$ mrdb --version +mruby 1.1.0 (2014-11-19) +``` + +## 2.2 Bassic Operation + +### 2.2.1 debugging mruby script file(rb file) with mrdb + +To invoke mruby debugger, just type mrdb. + +To specify the script file , + +```bash +$ mrdb [option] file name +``` + +For example : Debugging sample.rb + +```bash +$ mrdb sample.rb +``` + +You can excute shell commands listed below + +|command|description| +|:-:|:--| +|run|execute programs| +|step|execute stepping| +|continue|execute continuing program| +|break|configure the breaking point| +|delete|deleting the breaking points| +|disable|disabling the breaking points| +|enable|enabling the breaking points| +|info breakpoints|showing list of the breaking points| +|print|eavaluating and printing the values of the mruby expressions in the script| +|list|displaying the source cords| +|help|showing help| +|quit|terminating the mruby debugger| + +### 2.2.2 debugging mruby binary file(mrb file) with mrdb + +You can debugg the mruby binary files + +#### 2.2.2.1 debuggg the binary files + +* notice +To debugg mruby binary files, you need to compile mruby files with option -g. + +```bash +$ mrbc -g sample.rb +``` + +You can debugg the mruby binary files with following command and the option -b. + +```bash +$ mrdb -b sample.mrb +``` + +Then you can execute all debugger shell commands. + +#### break command + you can give any breakpoint to stop the program by specifiyng the line number and method name. + And the breakpoint list will be displayed after you finished to set the breakpoint succesfully. + +Usage: +``` +break [file:]lineno +b [file:]lineno +break [class:]method +b [class:]method +``` + +The breakpoint will be numbered in serial order from 1.The number which was given to deleted breakpoint will not be given to another breakpoint again. + +You can give multiple breakpoints to specified the line number and method. +Be ware that breakpoint command will not check the validity of the class name and method name. + +You can see the current breakpoint information by following options. + +breakpoint breakpoint number : file name. line number + +breakpoint breakpoint number : [class name,] method name + +#### continue command + +Usage: +``` +continue [N] +c [N] +``` + +N: the next breakpoint number + +Resuming the program and will stop the program at breakpoint at N (N-1 breakpoint will be ignored) + +When you run continue command without any specifying N ,Program will be stopped at next breakpoint. + +Example: +``` +(foo.rb:1) continue 3 +``` +Resuming the program and stopping the program at the third breakpoint. + +#### delete command + +Deleting specified breakpoint + +Usage: +``` +delete [breakpointno] +d [breakpointno] +``` + +breakpointno: breakpoint number + +Example: +``` +(foo.rb:1) delete +``` + +Deleting all brealpoint +``` +(foo.rb:1) delete 1 3 +``` + +Deleting the breakpoint 1 and 3 + +#### diable command + +Disabling the specified breakpoint + +Usage: +``` +disable [breakpointno] +dis [breakpointno] +``` + +brealpointno: breakpoint number + +Example: +``` +(foo.rb:1) disable +``` + +Desabling all brealpoint +``` +(foo.rb:1) disable 1 3 +``` + +Disabling the breakpoint 1 and 3 + +#### enable command + +Enababling the specified breakpoint + +Usage: +``` +enable [breakpointno] +e [breakpointno] +``` + +brealpointno: breakpoint number + +Example: +``` +(foo.rb:1) enable +``` + +Enabling all brealpoint +``` +(foo.rb:1) enable 1 3 +``` + +Enabling the breakpoint 1 and 3 + +#### eval command + +Evaluating the string as source code and printing the value. + +Same as print command, please see print command. + +#### help command + +Displaying the help message. + +Usage: +``` +help [comand] +h [command] +``` +Typing help without any option will displays the command list. + +#### info breakpoints command + +Displaying the specified breakpoint information. + +Usage: +``` +info breakpoints [breakpointno] +i b [breakpointno] +``` + +breakpointno: breakpoint number + +Typing "info breakpoints" without ant option will display all breakpoint information. +Example +``` +(sample.rb:1) info breakpoints +Num Type Enb What +1 breakpoint y at sample.rb:3 -> file name,line number +2 breakpoint n in Sample_class:sample_class_method -> [class:]method name +3 breakpoint y in sample_global_method +``` + +Displaying specified the breakpoint number +``` +(foo.rb:1) info breakpoints 1 3 +Num Type Enb What +1 breakpoint y at sample.rb:3 +3 breakpoint y in sample_global_method +``` + +#### list command + +Displaying the cords of the source file. + +Usage: +``` +list [filename:]first[,last] +l [filename]:first[,last] +``` + +first: the opening row number +last : the closing row number + +When you specify first , but not specify option "last" , you will get 10 rows. +When you don not specify both of first and last, you will next 10 rows. + +Example: +``` +Specifying file name and first row number +sample.rb:1) list sample2.rb:5 +``` + +Specifying file name and first and last row number +``` +(sample.rb:1) list sample2.rb:6,7 +``` + +#### print command +Evaluating the string as source code and printing the value. + +Usage: +``` +print [expr] +p [expr] +``` + +expr: expression + +To specify the expression is indispensableness. +The displayed expressions will be numbered in serial order from 1. +If an exception occurs, the exception information will be displayed and the debugging will be continued. + +Example: +``` +(sample.rb:1) print 1+2 +$1 = 3 +(sample.rb:1) print self +$2 = main +``` + +below is the case of the exception: +``` +(sample.rb:1) print (1+2 +$1 = SyntaxError: line 1: syntax error, unexpected $end, expecting ')' +``` + +#### quit command +Quitting the debugger. + +Usage: +``` +quit +q +``` + +#### run command + +Running the program and stopping at the first breakpoint. + +Usage: +``` +run +r +``` + +#### step command + +Running the program step by step. +When the method and the block will be invoked, the program will be stop at the first row. +The program which is developed by C language will be ignored. + +period diff --git a/include/mruby.h b/include/mruby.h index dd7cdab086..6f2b9dd9df 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -358,6 +358,8 @@ MRB_API mrb_value mrb_obj_clone(mrb_state *mrb, mrb_value self); #define ISALPHA(c) (ISASCII(c) && isalpha((int)(unsigned char)(c))) #define ISDIGIT(c) (ISASCII(c) && isdigit((int)(unsigned char)(c))) #define ISXDIGIT(c) (ISASCII(c) && isxdigit((int)(unsigned char)(c))) +#define ISBLANK(c) (ISASCII(c) && isblank((int)(unsigned char)(c))) +#define ISCNTRL(c) (ISASCII(c) && iscntrl((int)(unsigned char)(c))) #define TOUPPER(c) (ISASCII(c) ? toupper((int)(unsigned char)(c)) : (c)) #define TOLOWER(c) (ISASCII(c) ? tolower((int)(unsigned char)(c)) : (c)) #endif diff --git a/mrbgems/full-core.gembox b/mrbgems/full-core.gembox index 4dbb13e922..d1ff5f4146 100644 --- a/mrbgems/full-core.gembox +++ b/mrbgems/full-core.gembox @@ -4,6 +4,6 @@ MRuby::GemBox.new do |conf| Dir.glob("#{root}/mrbgems/mruby-*/mrbgem.rake") do |x| g = File.basename File.dirname x - conf.gem :core => g unless g =~ /^mruby-(print|sprintf)$/ + conf.gem :core => g unless g =~ /^mruby-(print|sprintf|bin-debugger)$/ end end diff --git a/mrbgems/mruby-bin-debugger/bintest/mrdb.rb b/mrbgems/mruby-bin-debugger/bintest/mrdb.rb new file mode 100755 index 0000000000..ae40f0a462 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/bintest/mrdb.rb @@ -0,0 +1,286 @@ +require 'open3' +require 'tempfile' + +class BinTest_MrubyBinDebugger + @debug1=false + @debug2=true + @debug3=true + def self.test(rubysource, testcase) + script, bin = Tempfile.new(['test', '.rb']), Tempfile.new(['test', '.mrb']) + + # .rb + script.write rubysource + script.flush + + # compile + `./bin/mrbc -g -o "#{bin.path}" "#{script.path}"` + + # add mrdb quit + testcase << {:cmd=>"quit"} + + stdin_data = testcase.map{|t| t[:cmd]}.join("\n") << "\n" + + ["bin/mrdb #{script.path}","bin/mrdb -b #{bin.path}"].each do |cmd| + o, s = Open3.capture2(cmd, :stdin_data => stdin_data) + + exp_vals = testcase.map{|t| t.fetch(:exp, nil)} + unexp_vals = testcase.map{|t| t.fetch(:unexp, nil)} + +if @debug1 + o.split("\n").each_with_index do |i,actual| + p [i,actual] + end +end + # compare actual / expected + o.split("\n").each do |actual| + next if actual.empty? + exp = exp_vals.shift +if @debug2 + a = true + a = actual.include?(exp) unless exp.nil? + p [actual, exp] unless a +end + assert_true actual.include?(exp) unless exp.nil? + end + # compare actual / unexpected + o.split("\n").each do |actual| + next if actual.empty? + unexp = unexp_vals.shift +if @debug3 + a = false + a = actual.include?(unexp) unless unexp.nil? + p [actual, unexp] if a +end + assert_false actual.include?(unexp) unless unexp.nil? + end + end + end +end + +INVCMD = "invalid command" + +assert('mruby-bin-debugger(mrdb) command line') do + # ruby source + src = "foo = 'foo'\n" + + str = "" + 103.times { + str += "1234567890" + } + cmd = "p a=#{str}" + + # test case + BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1023], :unexp=>'command line too long.'}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1024], :unexp=>'command line too long.'}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>cmd[0...1025], :exp=>'command line too long.'}]) +end + +assert('mruby-bin-debugger(mrdb) command: "break"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"b", :unexp=>INVCMD} + tc << {:cmd=>"br", :unexp=>INVCMD} + tc << {:cmd=>"brea", :unexp=>INVCMD} + tc << {:cmd=>"break", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"bl", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"breaka", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "continue"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"c", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"co", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continu", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continue", :unexp=>INVCMD}]) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"cn", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"continuee", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "delete"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"d 1", :unexp=>INVCMD} + tc << {:cmd=>"de 1", :unexp=>INVCMD} + tc << {:cmd=>"delet 1", :unexp=>INVCMD} + tc << {:cmd=>"delete 1", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"dd 1", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"deletee 1", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "disable"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"dis", :unexp=>INVCMD} + tc << {:cmd=>"disa", :unexp=>INVCMD} + tc << {:cmd=>"disabl", :unexp=>INVCMD} + tc << {:cmd=>"disable", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"di", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"disb", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"disablee", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "enable"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"en", :unexp=>INVCMD} + tc << {:cmd=>"ena", :unexp=>INVCMD} + tc << {:cmd=>"enabl", :unexp=>INVCMD} + tc << {:cmd=>"enable", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"e", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"enb", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"enablee", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "eval"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"ev", :unexp=>INVCMD} + tc << {:cmd=>"eva", :unexp=>INVCMD} + tc << {:cmd=>"eval", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"e", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"evl", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"evall", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "help"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"h", :unexp=>INVCMD} + tc << {:cmd=>"he", :unexp=>INVCMD} + tc << {:cmd=>"hel", :unexp=>INVCMD} + tc << {:cmd=>"help", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"hl", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"helpp", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "info breakpoints"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"i b", :unexp=>INVCMD} + tc << {:cmd=>"in b", :unexp=>INVCMD} + tc << {:cmd=>"i br", :unexp=>INVCMD} + tc << {:cmd=>"inf breakpoint", :unexp=>INVCMD} + tc << {:cmd=>"info breakpoints", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ii b", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"i bb", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"infoo breakpoints", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"info breakpointss", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "list"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"l", :unexp=>INVCMD} + tc << {:cmd=>"li", :unexp=>INVCMD} + tc << {:cmd=>"lis", :unexp=>INVCMD} + tc << {:cmd=>"list", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ll", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"listt", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "print"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"p", :unexp=>INVCMD} + tc << {:cmd=>"pr", :unexp=>INVCMD} + tc << {:cmd=>"prin", :unexp=>INVCMD} + tc << {:cmd=>"print", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"pp", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"printt", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "quit"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"q", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qu", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qui", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"quit", :unexp=>INVCMD}]) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"qq", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"quitt", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "run"') do + # ruby source + src = "foo = 'foo'\n" + + # test case + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"r", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ru", :unexp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"run", :unexp=>INVCMD}]) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"rr", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"runn", :exp=>INVCMD}]) +end + +assert('mruby-bin-debugger(mrdb) command: "step"') do + # ruby source + src = <<"SRC" +while true + foo = 'foo' +end +SRC + + # test case + tc = [] + tc << {:cmd=>"s", :unexp=>INVCMD} + tc << {:cmd=>"st", :unexp=>INVCMD} + tc << {:cmd=>"ste", :unexp=>INVCMD} + tc << {:cmd=>"step", :unexp=>INVCMD} + BinTest_MrubyBinDebugger.test(src, tc) + + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"ss", :exp=>INVCMD}]) + BinTest_MrubyBinDebugger.test(src, [{:cmd=>"stepp", :exp=>INVCMD}]) +end diff --git a/mrbgems/mruby-bin-debugger/bintest/print.rb b/mrbgems/mruby-bin-debugger/bintest/print.rb new file mode 100755 index 0000000000..e9d85f3335 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/bintest/print.rb @@ -0,0 +1,701 @@ +require 'open3' +require 'tempfile' + +class BinTest_MrubyBinDebugger + @debug1=false + @debug2=true + def self.test(rubysource, testcase) + script, bin = Tempfile.new(['test', '.rb']), Tempfile.new(['test', '.mrb']) + + # .rb + script.write rubysource + script.flush + + # compile + `./bin/mrbc -g -o "#{bin.path}" "#{script.path}"` + + # add mrdb quit + testcase << {:cmd=>"quit"} + + stdin_data = testcase.map{|t| t[:cmd]}.join("\n") << "\n" + + ["bin/mrdb #{script.path}","bin/mrdb -b #{bin.path}"].each do |cmd| + o, s = Open3.capture2(cmd, :stdin_data => stdin_data) + + exp_vals = testcase.map{|t| t.fetch(:exp, nil)} +=begin +if @debug1 + o.split("\n").each_with_index do |i,actual| + p [i,actual] + end +end + # compare actual / expected + o.split("\n").each do |actual| + next if actual.empty? + exp = exp_vals.shift +if @debug2 + a = true + a = actual.include?(exp) unless exp.nil? + p [actual, exp] unless a +end + assert_true actual.include?(exp) unless exp.nil? + end +=end + idx = 0 + exp_vals.each do |exp| + next if exp.nil? + idx = o.index(exp, idx) + assert_false idx.nil? + break unless idx + idx += 1 + end + end + end +end + +assert('mruby-bin-debugger(print) invalid arguments') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"p", :exp=>"Parameter not specified."} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) nomal') do + # ruby source + src = <<"SRC" +foo = 'foo' +bar = foo +baz = bar +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"p (1+2)", :exp=>'$1 = 3'} + tc << {:cmd=>"p foo", :exp=>'$2 = "foo"'} + tc << {:cmd=>"p foo*=2", :exp=>'$3 = "foofoo"'} + tc << {:cmd=>"s"} + tc << {:cmd=>"p bar", :exp=>'$4 = "foofoo"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) error') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"p (1+2", :exp=>'$1 = SyntaxError'} + tc << {:cmd=>"p bar", :exp=>'$2 = NoMethodError'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +# Kernel#instance_eval(string) does't work multiple statements. +=begin +assert('mruby-bin-debugger(print) multiple statements') do + # ruby source + src = <<"SRC" +x = 0 +y = 0 +z = 0 +SRC + + # test case + tc = [] + tc << {:cmd=>"s",} + tc << {:cmd=>"p x=1;x+=2", :exp=>"3"} + tc << {:cmd=>"s",} + tc << {:cmd=>"p x", :exp=>"3"} + + BinTest_MrubyBinDebugger.test(src, tc) +end +=end + +assert('mruby-bin-debugger(print) scope:top') do + # ruby source (bp is break point) + src = "bp=nil\n" + + # test case + tc = [] + tc << {:cmd=>"p self", :exp=>'$1 = main'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) scope:class') do + # ruby source (bp is break point) + src = <<"SRC" +class TestClassScope + bp = nil +end +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"p self", :exp=>'$1 = TestClassScope'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) scope:module') do + # ruby source (bp is break point) + src = <<"SRC" +class TestModuleScope + bp = nil +end +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"p self", :exp=>'$1 = TestModuleScope'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) scope:instance method') do + # ruby source (bp is break point) + src = <<"SRC" +class TestMethodScope + def m + bp = nil + end +end +TestMethodScope.new.m +SRC + + tc = [] + tc << {:cmd=>"b 3"} + tc << {:cmd=>"r"} + tc << {:cmd=>"p self", :exp=>'$1 = #"b 3"} + tc << {:cmd=>"r"} + tc << {:cmd=>"p self", :exp=>'$1 = TestClassMethodScope'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) scope:block') do + # ruby source (bp is break point) + src = <<"SRC" +1.times do + bp = nil +end +class TestBlockScope + 1.times do + bp = nil + end + def m + 1.times do + bp = nil + end + end +end +TestBlockScope.new.m +SRC + + tc = [] + tc << {:cmd=>"b 2"} + tc << {:cmd=>"b 6"} + tc << {:cmd=>"b 10"} + tc << {:cmd=>"c"} + tc << {:cmd=>"p self", :exp=>'$1 = main'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p self", :exp=>'$2 = TestBlockScope'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p self", :exp=>'$3 = #"b 6"} + tc << {:cmd=>"b 8"} + tc << {:cmd=>"b 11"} + tc << {:cmd=>"r"} + tc << {:cmd=>"p lv", :exp=>'$1 = "class"'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p lv", :exp=>'$2 = "instance method"'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p lv", :exp=>'$3 = "top"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) same name:instance variabe') do + # ruby source (bp is break point) + src = <<"SRC" +@iv = 'top' +class TestInstanceVariableName + def initialize(v) + @iv = v + end + def m + bp = nil + end +end +i1 = TestInstanceVariableName.new('instance1') +i2 = TestInstanceVariableName.new('instance2') +i1.m +i2.m +bp = nil +SRC + + tc = [] + tc << {:cmd=>"b 7"} + tc << {:cmd=>"b 14"} + tc << {:cmd=>"r"} + tc << {:cmd=>"p @iv", :exp=>'$1 = "instance1"'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p @iv", :exp=>'$2 = "instance2"'} + tc << {:cmd=>"c"} + tc << {:cmd=>"p @iv", :exp=>'$3 = "top"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +# Kernel#instance_eval(string) does't work const. +=begin +assert('mruby-bin-debugger(print) same name:const') do + # ruby source (bp is break point) + src = <<"SRC" +CONST='top' +class TestConstNameSuperClass + CONST='super class' + def m + bp = nil + end +end +class TestConstNameSubClass < TestConstNameSuperClass + CONST='sub class' + def m + bp = nil + end +end + +TestConstNameSuperClass.new.m() +TestConstNameSubClass.new.m() +bp = nil +SRC + + # todo: wait for 'break' to be implimented + tc = [] + 9.times { tc << {:cmd=>"s"} } + tc << {:cmd=>"p CONST", :exp=>"super class"} + 3.times { tc << {:cmd=>"s"} } + tc << {:cmd=>"p CONST", :exp=>"sub class"} + 1.times { tc << {:cmd=>"s"} } + tc << {:cmd=>"p CONST", :exp=>"top"} + + BinTest_MrubyBinDebugger.test(src, tc) +end +=end + +assert('mruby-bin-debugger(print) Literal:Numeric') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>"p 100", :exp=>'$1 = 100'} + tc << {:cmd=>"p -0b100", :exp=>'$2 = -4'} + tc << {:cmd=>"p +0100", :exp=>'$3 = 64'} + tc << {:cmd=>"p 0x100", :exp=>'$4 = 256'} + tc << {:cmd=>"p 1_234", :exp=>'$5 = 1234'} + tc << {:cmd=>"p 0b1000_0000", :exp=>"$6 = #{0b1000_0000.to_s}"} + tc << {:cmd=>"p 0x1000_0000", :exp=>"$7 = #{0x1000_0000.to_s}"} + + tc << {:cmd=>"p 3.14", :exp=>'$8 = 3.14'} + tc << {:cmd=>"p -12.3", :exp=>'$9 = -12.3'} + tc << {:cmd=>"p +12.000", :exp=>'$10 = 12.0'} + tc << {:cmd=>"p 1e4", :exp=>'$11 = 10000.0'} + tc << {:cmd=>"p -0.1e-2", :exp=>'$12 = -0.001'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Literal:String') do + # ruby source + src = <<"SRC" +foo = 'foo' +bar = "bar" +baz = "baz" +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"s"} + + tc << {:cmd=>'p "str"', :exp=>'$1 = "str"'} + tc << {:cmd=>'p "s\tt\rr\n"', :exp=>'$2 = "s\\tt\\rr\\n"'} + tc << {:cmd=>'p "\C-a\C-z"', :exp=>'$3 = "\\001\\032"'} + tc << {:cmd=>'p "#{foo+bar}"', :exp=>'$4 = "foobar"'} + + tc << {:cmd=>'p \'str\'', :exp=>'$5 = "str"'} + tc << {:cmd=>'p \'s\\tt\\rr\\n\'', :exp=>'$6 = "s\\\\tt\\\\rr\\\\n"'} + tc << {:cmd=>'p \'\\C-a\\C-z\'', :exp=>'$7 = "\\\\C-a\\\\C-z"'} + tc << {:cmd=>'p \'#{foo+bar}\'', :exp=>'$8 = "#{foo+bar}"'} + + tc << {:cmd=>'p %!str!', :exp=>'$9 = "str"'} + tc << {:cmd=>'p %!s\tt\rr\n!', :exp=>'$10 = "s\\tt\\rr\\n"'} + tc << {:cmd=>'p %!\C-a\C-z!', :exp=>'$11 = "\\001\\032"'} + tc << {:cmd=>'p %!#{foo+bar}!', :exp=>'$12 = "foobar"'} + + tc << {:cmd=>'p %Q!str!', :exp=>'$13 = "str"'} + tc << {:cmd=>'p %Q!s\tt\rr\n!', :exp=>'$14 = "s\\tt\\rr\\n"'} + tc << {:cmd=>'p %Q!\C-a\C-z!', :exp=>'$15 = "\\001\\032"'} + tc << {:cmd=>'p %Q!#{foo+bar}!', :exp=>'$16 = "foobar"'} + + tc << {:cmd=>'p %q!str!', :exp=>'$17 = "str"'} + tc << {:cmd=>'p %q!s\\tt\\rr\\n!', :exp=>'$18 = "s\\\\tt\\\\rr\\\\n"'} + tc << {:cmd=>'p %q!\\C-a\\C-z!', :exp=>'$19 = "\\\\C-a\\\\C-z"'} + tc << {:cmd=>'p %q!#{foo+bar}!', :exp=>'$20 = "#{foo+bar}"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Literal:Array') do + # ruby source + src = <<"SRC" +foo = 'foo' +bar = "bar" +baz = "baz" +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"s"} + + tc << {:cmd=>'p []', :exp=>'$1 = []'} + tc << {:cmd=>'p [ 5, 12, 8, 10, ]', :exp=>'$2 = [5, 12, 8, 10]'} + tc << {:cmd=>'p [1,2.5,"#{foo+bar}"]', :exp=>'$3 = [1, 2.5, "foobar"]'} + tc << {:cmd=>'p %w[3.14 A\ &\ B #{foo}]', :exp=>'$4 = ["3.14", "A & B", "#{foo}"]'} + tc << {:cmd=>'p %W[3.14 A\ &\ B #{foo}]', :exp=>'$5 = ["3.14", "A & B", "foo"]'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Literal:Hash') do + # ruby source + src = <<"SRC" +foo = 'foo' +bar = "bar" +baz = "baz" +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"s"} + + tc << {:cmd=>'p {}', :exp=>'$1 = {}'} + tc << {:cmd=>'p {"one"=>1,"two"=>2}', :exp=>'$2 = {"one"=>1, "two"=>2}'} + tc << {:cmd=>'p {:eins=>"1", :zwei=>"2", }', :exp=>'$3 = {:eins=>"1", :zwei=>"2"}'} + tc << {:cmd=>'p {uno:"one", dos: 2}', :exp=>'$4 = {:uno=>"one", :dos=>2}'} + tc << {:cmd=>'p {"one"=>1, :zwei=>2, tres:3}', :exp=>'$5 = {"one"=>1, :zwei=>2, :tres=>3}'} + tc << {:cmd=>'p {:foo=>"#{foo}",:bar=>"#{bar}"}', :exp=>'$6 = {:foo=>"foo", :bar=>"bar"}'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Literal:Range') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>'p 1..10', :exp=>'$1 = 1..10'} + tc << {:cmd=>'p 1...10', :exp=>'$2 = 1...10'} + tc << {:cmd=>'p 100..10', :exp=>'$3 = 100..10'} + tc << {:cmd=>'p 1 ... 10', :exp=>'$4 = 1...10'} + + tc << {:cmd=>'p "1" .. "9"', :exp=>'$5 = "1".."9"'} + tc << {:cmd=>'p "A" ... "Z"', :exp=>'$6 = "A"..."Z"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Literal:Symbol') do + # ruby source + src = <<"SRC" +foo = 'foo' +bar = "bar" +baz = "baz" +SRC + + # test case + tc = [] + tc << {:cmd=>"s"} + tc << {:cmd=>"s"} + + tc << {:cmd=>'p :sym', :exp=>'$1 = :sym'} + tc << {:cmd=>'p :"sd"', :exp=>'$2 = :sd'} + tc << {:cmd=>"p :'ss'", :exp=>'$3 = :ss'} + tc << {:cmd=>'p :"123"', :exp=>'$4 = :"123"'} + tc << {:cmd=>'p :"#{foo} baz"', :exp=>'$5 = :"foo baz"'} + tc << {:cmd=>'p %s!symsym!', :exp=>'$6 = :symsym'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Unary operation') do + # ruby source + src = "foo = 'foo'\n" + + # test case + tc = [] + tc << {:cmd=>'p +10', :exp=>'$1 = 10'} + tc << {:cmd=>'p -100', :exp=>'$2 = -100'} + tc << {:cmd=>'p !true', :exp=>'$3 = false'} + tc << {:cmd=>'p !false', :exp=>'$4 = true'} + tc << {:cmd=>'p !nil', :exp=>'$5 = true'} + tc << {:cmd=>'p !1', :exp=>'$6 = false'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Binary operation') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, 8 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p a+1', :exp=>'$1 = 2'} + tc << {:cmd=>'p 2-b', :exp=>'$2 = -3'} + tc << {:cmd=>'p c * 3', :exp=>'$3 = 24'} + tc << {:cmd=>'p a/b', :exp=>'$4 = 0.2'} + tc << {:cmd=>'p c%b', :exp=>'$5 = 3'} + tc << {:cmd=>'p 2**10', :exp=>'$6 = 1024'} + tc << {:cmd=>'p ~3', :exp=>'$7 = -4'} + + tc << {:cmd=>'p 1<<2', :exp=>'$8 = 4'} + tc << {:cmd=>'p 64>>5', :exp=>'$9 = 2'} + + tc << {:cmd=>'p a|c', :exp=>'$10 = 9'} + tc << {:cmd=>'p a&b', :exp=>'$11 = 1'} + tc << {:cmd=>'p a^b', :exp=>'$12 = 4'} + + tc << {:cmd=>'p a>b', :exp=>'$13 = false'} + tc << {:cmd=>'p a'$14 = true'} + tc << {:cmd=>'p b>=5', :exp=>'$15 = true'} + tc << {:cmd=>'p b<=5', :exp=>'$16 = true'} + + tc << {:cmd=>'p "A"<=>"B"', :exp=>'$17 = -1'} + tc << {:cmd=>'p "A"=="B"', :exp=>'$18 = false'} + tc << {:cmd=>'p "A"==="B"', :exp=>'$19 = false'} + tc << {:cmd=>'p "A"!="B"', :exp=>'$20 = true'} + + tc << {:cmd=>'p false || true', :exp=>'$21 = true'} + tc << {:cmd=>'p false && true', :exp=>'$22 = false'} + + tc << {:cmd=>'p not nil', :exp=>'$23 = true'} + tc << {:cmd=>'p false or true', :exp=>'$24 = true'} + tc << {:cmd=>'p false and true', :exp=>'$25 = false'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Ternary operation') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, -10 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p (a < b) ? a : b', :exp=>'$1 = 1'} + tc << {:cmd=>'p (a > b) ? a : b', :exp=>'$2 = 5'} + tc << {:cmd=>'p true ? "true" : "false"', :exp=>'$3 = "true"'} + tc << {:cmd=>'p false ? "true" : "false"', :exp=>'$4 = "false"'} + tc << {:cmd=>'p nil ? "true" : "false"', :exp=>'$5 = "false"'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Substitution:simple') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, -10 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p a=2', :exp=>'$1 = 2'} + tc << {:cmd=>'p foo=[foo,bar,baz]', :exp=>'$2 = ["foo", "bar", "baz"]'} + + tc << {:cmd=>'p undefined=-1', :exp=>'$3 = -1'} + tc << {:cmd=>'p "#{undefined}"', :exp=>'$4 = NoMethodError'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Substitution:self') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, -10 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p a+=9', :exp=>'$1 = 10'} + tc << {:cmd=>'p b-=c', :exp=>'$2 = 15'} + tc << {:cmd=>'p bar*=2', :exp=>'$3 = "barbar"'} + tc << {:cmd=>'p a/=4', :exp=>'$4 = 2.5'} + tc << {:cmd=>'p c%=4', :exp=>'$5 = 2'} + + tc << {:cmd=>'p b&=0b0101', :exp=>'$6 = 5'} + tc << {:cmd=>'p c|=0x10', :exp=>'$7 = 18'} + + tc << {:cmd=>'p "#{a} #{b} #{c}"', :exp=>'$8 = "2.5 5 18"'} + tc << {:cmd=>'p "#{foo}#{bar}#{baz}"', :exp=>'$9 = "foobarbarbaz"'} + + tc << {:cmd=>'p a,b,c=[10,20,30]',:exp=>'$10 = [10, 20, 30]'} + tc << {:cmd=>'p [a,b,c]', :exp=>'$11 = [10, 20, 30]'} + tc << {:cmd=>'p a,b=b,a', :exp=>'$12 = [20, 10]'} + tc << {:cmd=>'p [a,b]', :exp=>'$13 = [20, 10]'} + + tc << {:cmd=>'p undefined=-1', :exp=>'$14 = -1'} + tc << {:cmd=>'p "#{undefined}"', :exp=>'$15 = NoMethodError'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Substitution:multiple') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, -10 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p a,b=[10,20]', :exp=>'$1 = [10, 20]'} + tc << {:cmd=>'p [a,b,c]', :exp=>'$2 = [10, 20, -10]'} + + tc << {:cmd=>'p foo,bar=["FOO","BAR","BAZ"]', :exp=>'$3 = ["FOO", "BAR", "BAZ"]'} + tc << {:cmd=>'p [foo,bar,baz]', :exp=>'$4 = ["FOO", "BAR", "baz"]'} + + tc << {:cmd=>'p a,foo=foo,a', :exp=>'$5 = ["FOO", 10]'} + tc << {:cmd=>'p [a,foo]', :exp=>'$6 = ["FOO", 10]'} + +# tc << {:cmd=>'p a,*b=[123, 456, 789]'} +# tc << {:cmd=>'p [a,b]', :exp=>'[123, [456, 789]]'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + +assert('mruby-bin-debugger(print) Substitution:self') do + # ruby source + src = <<"SRC" +CONST = 100 +a,b,c = 1, 5, -10 +foo,bar,baz = 'foo','bar','baz' +ary = [] +SRC + + # test case + tc = [] + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + tc << {:cmd=>'s'} + + tc << {:cmd=>'p a+=9', :exp=>'$1 = 10'} + tc << {:cmd=>'p b-=c', :exp=>'$2 = 15'} + tc << {:cmd=>'p bar*=2', :exp=>'$3 = "barbar"'} + tc << {:cmd=>'p a/=4', :exp=>'$4 = 2.5'} + tc << {:cmd=>'p c%=4', :exp=>'$5 = 2'} + + tc << {:cmd=>'p b&=0b0101', :exp=>'$6 = 5'} + tc << {:cmd=>'p c|=0x10', :exp=>'$7 = 18'} + + tc << {:cmd=>'p "#{a} #{b} #{c}"', :exp=>'$8 = "2.5 5 18"'} + tc << {:cmd=>'p "#{foo}#{bar}#{baz}"', :exp=>'$9 = "foobarbarbaz"'} + + tc << {:cmd=>'p a,b,c=[10,20,30]',:exp=>'$10 = [10, 20, 30]'} + tc << {:cmd=>'p [a,b,c]', :exp=>'$11 = [10, 20, 30]'} + tc << {:cmd=>'p a,b=b,a', :exp=>'$12 = [20, 10]'} + tc << {:cmd=>'p [a,b]', :exp=>'$13 = [20, 10]'} + + tc << {:cmd=>'p undefined=-1', :exp=>'$14 = -1'} + tc << {:cmd=>'p "#{undefined}"', :exp=>'$15 = NoMethodError'} + + BinTest_MrubyBinDebugger.test(src, tc) +end + diff --git a/mrbgems/mruby-bin-debugger/mrbgem.rake b/mrbgems/mruby-bin-debugger/mrbgem.rake new file mode 100755 index 0000000000..b9d6647798 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/mrbgem.rake @@ -0,0 +1,9 @@ +MRuby::Gem::Specification.new('mruby-bin-debugger') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'mruby debuggeer command' + + spec.add_dependency('mruby-eval', :core => 'mruby-eval') + + spec.bins = %w(mrdb) +end diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c new file mode 100755 index 0000000000..f1bf34a06f --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c @@ -0,0 +1,529 @@ +/* +** apibreak.c +** +*/ + +#include +#include "mruby.h" +#include "mruby/irep.h" +#include "mrdb.h" +#include "mruby/debug.h" +#include "mruby/opcode.h" +#include "mruby/class.h" +#include "mruby/proc.h" +#include "mruby/variable.h" +#include "mrdberror.h" +#include "apibreak.h" + +#define MAX_BREAKPOINTNO (MAX_BREAKPOINT * 1024) +#define MRB_DEBUG_BP_FILE_OK (0x0001) +#define MRB_DEBUG_BP_LINENO_OK (0x0002) + +static uint16_t +check_lineno( mrb_irep_debug_info_file *info_file, uint16_t lineno ) +{ + uint32_t count = info_file->line_entry_count; + uint16_t l_idx; + + if( info_file->line_type == mrb_debug_line_ary ) { + for (l_idx = 0; l_idx < count; ++l_idx) { + if(lineno == info_file->lines.ary[l_idx]) { + return lineno; + } + } + } else { + for (l_idx = 0; l_idx < count; ++l_idx) { + if(lineno == info_file->lines.flat_map[l_idx].line) { + return lineno; + } + } + } + + return 0; +} + +static int32_t +get_break_index( mrb_debug_context *dbg, int32_t bpno ) +{ + uint32_t i; + int32_t index; + char hit = FALSE; + + for(i = 0 ; i < dbg->bpnum; i++) { + if(dbg->bp[i].bpno == bpno) { + hit = TRUE; + index = i; + break; + } + } + + if(hit == FALSE) { + return MRB_DEBUG_BREAK_INVALID_NO; + } + + return index; +} + +static void +free_breakpoint( mrb_state *mrb, mrb_debug_breakpoint *bp ) +{ + switch(bp->type) { + case MRB_DEBUG_BPTYPE_LINE: + mrb_free(mrb, (void*)bp->point.linepoint.file); + break; + case MRB_DEBUG_BPTYPE_METHOD: + mrb_free(mrb, (void*)bp->point.methodpoint.method_name); + if(bp->point.methodpoint.class_name != NULL) { + mrb_free(mrb, (void*)bp->point.methodpoint.class_name); + } + break; + default: + break; + } +} + +static uint16_t +check_file_lineno( struct mrb_irep *irep, const char *file, uint16_t lineno ) +{ + mrb_irep_debug_info_file *info_file; + uint16_t result = 0; + uint16_t f_idx; + uint16_t fix_lineno; + uint16_t i; + + for (f_idx = 0; f_idx < irep->debug_info->flen; ++f_idx) { + info_file = irep->debug_info->files[f_idx]; + if(!strcmp(info_file->filename, file)) { + result = MRB_DEBUG_BP_FILE_OK; + + fix_lineno = check_lineno( info_file, lineno ); + if(fix_lineno != 0) { + return result | MRB_DEBUG_BP_LINENO_OK; + } + } + for ( i=0; i < irep->rlen; ++i ) { + result |= check_file_lineno(irep->reps[i], file, lineno); + if(result == (MRB_DEBUG_BP_FILE_OK | MRB_DEBUG_BP_LINENO_OK)) { + return result; + } + } + } + return result; +} + +static const char* +get_class_name( mrb_state *mrb, struct RClass *class_obj ) +{ + struct RClass *outer; + mrb_sym class_sym; + + outer = mrb_class_outer_module(mrb, class_obj); + class_sym = mrb_class_sym(mrb, class_obj, outer); + return mrb_sym2name(mrb, class_sym); +} + +static int32_t +compare_break_method( mrb_state *mrb, mrb_debug_breakpoint *bp, struct RClass *class_obj, mrb_sym method_sym, mrb_bool* isCfunc ) +{ + const char* class_name; + const char* method_name; + struct RProc* m; + struct RClass* sc; + const char* sn; + mrb_sym ssym; + mrb_debug_methodpoint *method_p; + mrb_bool is_defined; + + method_name = mrb_sym2name(mrb, method_sym); + + method_p = &bp->point.methodpoint; + if(strcmp(method_p->method_name, method_name) == 0) { + class_name = get_class_name(mrb, class_obj); + if(class_name == NULL) { + if(method_p->class_name == NULL) { + return bp->bpno; + } + } + else if(method_p->class_name != NULL) { + m = mrb_method_search_vm(mrb, &class_obj, method_sym); + if(m == NULL) { + return MRB_DEBUG_OK; + } + if(MRB_PROC_CFUNC_P(m)) { + *isCfunc = TRUE; + } + + is_defined = mrb_class_defined(mrb, method_p->class_name); + if(is_defined == FALSE) { + return MRB_DEBUG_OK; + } + + sc = mrb_class_get(mrb, method_p->class_name); + ssym = mrb_symbol(mrb_check_intern_cstr(mrb, method_p->method_name)); + m = mrb_method_search_vm(mrb, &sc, ssym); + if(m == NULL) { + return MRB_DEBUG_OK; + } + + class_name = get_class_name(mrb, class_obj); + sn = get_class_name(mrb, sc); + if(strcmp(sn, class_name) == 0) { + return bp->bpno; + } + } + } + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_set_break_line( mrb_state *mrb, mrb_debug_context *dbg, const char *file, uint16_t lineno) +{ + int32_t index; + char* set_file; + uint16_t result; + + if((mrb == NULL)||(dbg == NULL)||(file == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if(dbg->bpnum >= MAX_BREAKPOINT) { + return MRB_DEBUG_BREAK_NUM_OVER; + } + + if(dbg->next_bpno > MAX_BREAKPOINTNO) { + return MRB_DEBUG_BREAK_NO_OVER; + } + + /* file and lineno check (line type mrb_debug_line_ary only.) */ + result = check_file_lineno( dbg->root_irep, file, lineno ); + if(result == 0) { + return MRB_DEBUG_BREAK_INVALID_FILE; + }else if(result == MRB_DEBUG_BP_FILE_OK) { + return MRB_DEBUG_BREAK_INVALID_LINENO; + } + + set_file = mrb_malloc(mrb, strlen(file) + 1); + if(set_file == NULL) { + return MRB_DEBUG_NOBUF; + } + + index = dbg->bpnum; + dbg->bp[index].bpno = dbg->next_bpno; + dbg->next_bpno++; + dbg->bp[index].enable = TRUE; + dbg->bp[index].type = MRB_DEBUG_BPTYPE_LINE; + dbg->bp[index].point.linepoint.lineno = lineno; + dbg->bpnum++; + + strncpy(set_file, file, strlen(file) + 1); + + dbg->bp[index].point.linepoint.file = set_file; + + return dbg->bp[index].bpno; +} + +int32_t +mrb_debug_set_break_method( mrb_state *mrb, mrb_debug_context *dbg, const char *class_name, const char *method_name ) +{ + int32_t index; + char* set_class; + char* set_method; + + if((mrb == NULL) || (dbg == NULL) || (method_name == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if(dbg->bpnum >= MAX_BREAKPOINT) { + return MRB_DEBUG_BREAK_NUM_OVER; + } + + if(dbg->next_bpno > MAX_BREAKPOINTNO) { + return MRB_DEBUG_BREAK_NO_OVER; + } + + if(class_name != NULL) { + set_class = mrb_malloc(mrb, strlen(class_name) + 1); + if(set_class == NULL) { + return MRB_DEBUG_NOBUF; + } + + strncpy(set_class, class_name, strlen(class_name) + 1); + } + else { + set_class = NULL; + } + + set_method = mrb_malloc(mrb, strlen(method_name) + 1); + if(set_method == NULL) { + if(set_class != NULL) { + mrb_free(mrb, (void*)set_class); + } + return MRB_DEBUG_NOBUF; + } + + strncpy(set_method, method_name, strlen(method_name) + 1); + + index = dbg->bpnum; + dbg->bp[index].bpno = dbg->next_bpno; + dbg->next_bpno++; + dbg->bp[index].enable = TRUE; + dbg->bp[index].type = MRB_DEBUG_BPTYPE_METHOD; + dbg->bp[index].point.methodpoint.method_name = set_method; + dbg->bp[index].point.methodpoint.class_name = set_class; + dbg->bpnum++; + + return dbg->bp[index].bpno; +} + +int32_t +mrb_debug_get_breaknum( mrb_state *mrb, mrb_debug_context *dbg ) +{ + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + return dbg->bpnum; +} + +int32_t +mrb_debug_get_break_all( mrb_state *mrb, mrb_debug_context *dbg, uint32_t size, mrb_debug_breakpoint *bp ) +{ + uint32_t get_size = 0; + + if((mrb == NULL) || (dbg == NULL) || (bp == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if(dbg->bpnum >= size) { + get_size = size; + } + else { + get_size = dbg->bpnum; + } + + memcpy(bp, dbg->bp, sizeof(mrb_debug_breakpoint) * get_size); + + return get_size; +} + +int32_t +mrb_debug_get_break( mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno, mrb_debug_breakpoint *bp ) +{ + uint32_t index; + + if((mrb == NULL) || (dbg == NULL) || (bp == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + index = get_break_index(dbg, bpno); + if(index == MRB_DEBUG_BREAK_INVALID_NO) { + return MRB_DEBUG_BREAK_INVALID_NO; + } + + bp->bpno = dbg->bp[index].bpno; + bp->enable = dbg->bp[index].enable; + bp->point = dbg->bp[index].point; + bp->type = dbg->bp[index].type; + + return 0; +} + +int32_t +mrb_debug_delete_break( mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno ) +{ + uint32_t i; + int32_t index; + + if((mrb == NULL) ||(dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + index = get_break_index(dbg, bpno); + if(index == MRB_DEBUG_BREAK_INVALID_NO) { + return MRB_DEBUG_BREAK_INVALID_NO; + } + + free_breakpoint(mrb, &dbg->bp[index]); + + for(i = index ; i < dbg->bpnum; i++) { + if(dbg->bp[i + 1].type == MRB_DEBUG_BPTYPE_NONE) { + memset(&dbg->bp[i], 0, sizeof(mrb_debug_breakpoint)); + } + else { + memcpy(&dbg->bp[i], &dbg->bp[i + 1], sizeof(mrb_debug_breakpoint)); + } + } + + dbg->bpnum--; + + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_delete_break_all( mrb_state *mrb, mrb_debug_context *dbg ) +{ + uint32_t i; + + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + for(i = 0 ; i < dbg->bpnum ; i++) { + free_breakpoint(mrb, &dbg->bp[i]); + } + + dbg->bpnum = 0; + + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_enable_break( mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno ) +{ + int32_t index = 0; + + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + index = get_break_index(dbg, bpno); + if(index == MRB_DEBUG_BREAK_INVALID_NO) { + return MRB_DEBUG_BREAK_INVALID_NO; + } + + dbg->bp[index].enable = TRUE; + + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_enable_break_all( mrb_state *mrb, mrb_debug_context *dbg ) +{ + uint32_t i; + + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + for(i = 0 ; i < dbg->bpnum; i++) { + dbg->bp[i].enable = TRUE; + } + + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_disable_break( mrb_state *mrb, mrb_debug_context *dbg, uint32_t bpno ) +{ + int32_t index = 0; + + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + index = get_break_index(dbg, bpno); + if(index == MRB_DEBUG_BREAK_INVALID_NO) { + return MRB_DEBUG_BREAK_INVALID_NO; + } + + dbg->bp[index].enable = FALSE; + + return MRB_DEBUG_OK; +} + +int32_t +mrb_debug_disable_break_all( mrb_state *mrb, mrb_debug_context *dbg ) +{ + uint32_t i; + + if((mrb == NULL) || (dbg == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + for(i = 0 ; i < dbg->bpnum; i++) { + dbg->bp[i].enable = FALSE; + } + + return MRB_DEBUG_OK; +} + +static mrb_bool +check_start_pc_for_line( mrb_irep *irep, mrb_code *pc, uint16_t line ) +{ + if( pc > irep->iseq ) { + if( line == mrb_debug_get_line(irep, (uint32_t)(pc - irep->iseq - 1))) { + return FALSE; + } + } + return TRUE; +} + +int32_t +mrb_debug_check_breakpoint_line( mrb_state *mrb, mrb_debug_context *dbg, const char *file, uint16_t line ) +{ + mrb_debug_breakpoint *bp; + mrb_debug_linepoint *line_p; + int i; + + if((mrb == NULL) || (dbg == NULL) || (file == NULL) || (line <= 0)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if(!check_start_pc_for_line(dbg->irep, dbg->pc, line)) { + return MRB_DEBUG_OK; + } + + bp = dbg->bp; + for(i=0; itype) { + case MRB_DEBUG_BPTYPE_LINE: + if(bp->enable == TRUE) { + line_p = &bp->point.linepoint; + if((strcmp(line_p->file, file) == 0) && (line_p->lineno == line)) { + return bp->bpno; + } + } + break; + case MRB_DEBUG_BPTYPE_METHOD: + break; + case MRB_DEBUG_BPTYPE_NONE: + default: + return MRB_DEBUG_OK; + } + bp++; + } + return MRB_DEBUG_OK; +} + + +int32_t +mrb_debug_check_breakpoint_method( mrb_state *mrb, mrb_debug_context *dbg, struct RClass *class_obj, mrb_sym method_sym, mrb_bool* isCfunc ) +{ + mrb_debug_breakpoint *bp; + int32_t bpno; + int i; + + if((mrb == NULL) || (dbg == NULL) || (class_obj == NULL)) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + bp = dbg->bp; + for(i=0; itype == MRB_DEBUG_BPTYPE_METHOD) { + if(bp->enable == TRUE) { + bpno = compare_break_method(mrb, bp, class_obj, method_sym, isCfunc); + if(bpno > 0) { + return bpno; + } + } + } + else if(bp->type == MRB_DEBUG_BPTYPE_NONE) { + break; + } + bp++; + } + + return 0; +} + + diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h new file mode 100755 index 0000000000..8bb39cb503 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h @@ -0,0 +1,26 @@ +/* +** apibreak.h +** +*/ + +#ifndef APIBREAK_H_ +#define APIBREAK_H_ + +#include "mruby.h" +#include "mrdb.h" + +int32_t mrb_debug_set_break_line( mrb_state *, mrb_debug_context *, const char *, uint16_t ); +int32_t mrb_debug_set_break_method( mrb_state *, mrb_debug_context *, const char *, const char * ); +int32_t mrb_debug_get_breaknum( mrb_state *, mrb_debug_context * ); +int32_t mrb_debug_get_break_all( mrb_state *, mrb_debug_context *, uint32_t, mrb_debug_breakpoint bp[]); +int32_t mrb_debug_get_break( mrb_state *, mrb_debug_context *, uint32_t, mrb_debug_breakpoint * ); +int32_t mrb_debug_delete_break( mrb_state *, mrb_debug_context *, uint32_t ); +int32_t mrb_debug_delete_break_all( mrb_state *, mrb_debug_context * ); +int32_t mrb_debug_enable_break( mrb_state *, mrb_debug_context *, uint32_t ); +int32_t mrb_debug_enable_break_all( mrb_state *, mrb_debug_context * ); +int32_t mrb_debug_disable_break( mrb_state *, mrb_debug_context *, uint32_t ); +int32_t mrb_debug_disable_break_all( mrb_state *, mrb_debug_context * ); +int32_t mrb_debug_check_breakpoint_line( mrb_state *, mrb_debug_context *, const char *, uint16_t ); +int32_t mrb_debug_check_breakpoint_method( mrb_state *, mrb_debug_context *, struct RClass *, mrb_sym, mrb_bool* ); + +#endif /* APIBREAK_H_ */ diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c new file mode 100755 index 0000000000..53f5fe4efc --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c @@ -0,0 +1,232 @@ +/* + * apilist.c + */ + +#include +#include +#include + +#include "mrdb.h" +#include "mrdberror.h" +#include "apilist.h" +#include "mruby/compile.h" +#include "mruby/irep.h" +#include "mruby/debug.h" + +#define LINE_BUF_SIZE MAX_COMMAND_LINE + +typedef struct source_file { + char *path; + uint16_t lineno; + FILE *fp; +} source_file; + +static void +source_file_free(mrb_state *mrb, source_file *file) +{ + if (file != NULL) { + if (file->path != NULL) { + mrb_free(mrb, file->path); + } + if (file->fp != NULL) { + fclose(file->fp); + file->fp = NULL; + } + mrb_free(mrb, file); + } +} + +static char* +build_path(mrb_state *mrb, const char *dir, const char *base) +{ + int len; + char *path = NULL; + + len = strlen(base) + 1; + + if (strcmp(dir, ".")) { + len += strlen(dir) + strlen("/"); + } + + if ((path = mrb_malloc(mrb, len)) != NULL) { + memset(path, 0, len); + + if (strcmp(dir, ".")) { + strcat(path, dir); + strcat(path, "/"); + } + strcat(path, base); + } + return path; +} + +static char* +dirname(mrb_state *mrb, const char *path) +{ + size_t len; + char *p, *dir; + + if (path == NULL) { + return NULL; + } + + p = strrchr(path, '/'); + len = p != NULL ? p - path : strlen(path); + + if ((dir = mrb_malloc(mrb, len + 1)) != NULL) { + strncpy(dir, path, len); + } + return dir; +} + +static source_file* +source_file_new(mrb_state *mrb, mrb_debug_context *dbg, char *filename) +{ + source_file *file = NULL; + + if ((file = mrb_malloc(mrb, sizeof(source_file))) == NULL) { + return NULL; + } + + memset(file, '\0', sizeof(source_file)); + file->fp = fopen(filename, "rb"); + + if (file->fp == NULL) { + source_file_free(mrb, file); + return NULL; + } + + file->lineno = 1; + file->path = mrb_malloc(mrb, strlen(filename) + 1); + strcpy(file->path, filename); + return file; +} + +static mrb_bool +remove_newlines(char *s, FILE *fp) +{ + char c, *p; + size_t len; + + if ((len = strlen(s)) == 0) { + return FALSE; + } + + p = s + len - 1; + + if (*p != '\r' && *p != '\n') { + return FALSE; + } + + if (*p == '\r') { + /* peek the next character and skip '\n' */ + if ((unsigned char)(c = fgetc(fp)) != '\n') { + ungetc(c, fp); + } + } + + /* remove trailing newline characters */ + while (s <= p && (*p == '\r' || *p == '\n')) { + *p-- = '\0'; + } + + return TRUE; +} + +static void +show_lines(source_file *file, uint16_t line_min, uint16_t line_max) +{ + char buf[LINE_BUF_SIZE]; + int show_lineno = 1, found_newline = 0, is_printed = 0; + + if (file->fp == NULL) { + return; + } + + while (fgets(buf, sizeof(buf), file->fp) != NULL) { + found_newline = remove_newlines(buf, file->fp); + + if (line_min <= file->lineno) { + if (show_lineno) { + printf("%-8d", file->lineno); + } + show_lineno = found_newline; + printf(found_newline ? "%s\n" : "%s", buf); + is_printed = 1; + } + + if (found_newline) { + if (line_max < ++file->lineno) { + break; + } + } + } + + if (is_printed && !found_newline) { + printf("\n"); + } +} + +char* +mrb_debug_get_source(mrb_state *mrb, mrdb_state *mrdb, const char *srcpath, const char *filename) +{ + int i; + FILE *fp; + const char *search_path[3]; + char *path = NULL; + + search_path[0] = srcpath; + search_path[1] = dirname(mrb, mrb_debug_get_filename(mrdb->dbg->root_irep, 0)); + search_path[2] = "."; + + for (i = 0; i < 3; i++) { + if (search_path[i] == NULL) { + continue; + } + + if ((path = build_path(mrb, search_path[i], filename)) == NULL) { + continue; + } + + if ((fp = fopen(path, "rb")) == NULL) { + mrb_free(mrb, path); + path = NULL; + continue; + } + fclose(fp); + break; + } + return path; +} + +int32_t +mrb_debug_list(mrb_state *mrb, mrb_debug_context *dbg, char *filename, uint16_t line_min, uint16_t line_max) +{ + char *ext; + source_file *file; + + if (mrb == NULL || dbg == NULL || filename == NULL) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + ext = strrchr(filename, '.'); + + if (ext == NULL || strcmp(ext, ".rb")) { + printf("List command only supports .rb file.\n"); + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if (line_min > line_max) { + return MRB_DEBUG_INVALID_ARGUMENT; + } + + if ((file = source_file_new(mrb, dbg, filename)) != NULL) { + show_lines(file, line_min, line_max); + source_file_free(mrb, file); + return MRB_DEBUG_OK; + } + else { + printf("Invalid source file named %s.\n", filename); + return MRB_DEBUG_INVALID_ARGUMENT; + } +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h new file mode 100755 index 0000000000..4580b23508 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h @@ -0,0 +1,14 @@ +/* + * apilist.h + */ + +#ifndef APILIST_H_ +#define APILIST_H_ + +#include "mruby.h" +#include "mrdb.h" + +int32_t mrb_debug_list(mrb_state *, mrb_debug_context *, char *, uint16_t, uint16_t); +char* mrb_debug_get_source(mrb_state *, mrdb_state *, const char *, const char *); + +#endif /* APILIST_H_ */ diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c new file mode 100755 index 0000000000..23b321f7c0 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c @@ -0,0 +1,78 @@ +/* +** apiprint.c +** +*/ + +#include +#include "mrdb.h" +#include "mruby/value.h" +#include "mruby/class.h" +#include "mruby/compile.h" +#include "mruby/error.h" +#include "mruby/numeric.h" +#include "mruby/string.h" +#include "apiprint.h" + +static void +mrdb_check_syntax(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size_t len) +{ + mrbc_context *c; + + c = mrbc_context_new(mrb); + c->no_exec = TRUE; + c->capture_errors = TRUE; + c->filename = (char*)dbg->prvfile; + c->lineno = dbg->prvline; + + /* Load program */ + mrb_load_nstring_cxt(mrb, expr, len, c); + + mrbc_context_free(mrb, c); +} + +mrb_value +mrb_debug_eval(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size_t len, mrb_bool *exc) +{ + void *tmp; + mrb_value ruby_code; + mrb_value s; + mrb_value v; + mrb_value recv; + + /* disable code_fetch_hook */ + tmp = mrb->code_fetch_hook; + mrb->code_fetch_hook = NULL; + + mrdb_check_syntax(mrb, dbg, expr, len); + if (mrb->exc) { + v = mrb_obj_value(mrb->exc); + mrb->exc = 0; + } + else { + /* + * begin + * expr + * rescue => e + * e + * end + */ + ruby_code = mrb_str_new_cstr(mrb, "begin\n"); + ruby_code = mrb_str_cat(mrb, ruby_code, expr, len); + ruby_code = mrb_str_cat_cstr(mrb, ruby_code, "\nrescue => e\ne\nend"); + + recv = dbg->regs[0]; + + v = mrb_funcall(mrb, recv, "instance_eval", 1, ruby_code); + } + + if (exc) { + *exc = mrb_obj_is_kind_of(mrb, v, mrb->eException_class); + } + + s = mrb_funcall(mrb, v, "inspect", 0); + + /* enable code_fetch_hook */ + mrb->code_fetch_hook = tmp; + + return s; +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h new file mode 100755 index 0000000000..81d685e0c4 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h @@ -0,0 +1,13 @@ +/* + * apiprint.h + */ + +#ifndef APIPRINT_H_ +#define APIPRINT_H_ + +#include "mruby.h" +#include "mrdb.h" + +mrb_value mrb_debug_eval(mrb_state*, mrb_debug_context*, const char*, size_t, mrb_bool*); + +#endif /* APIPRINT_H_ */ diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c new file mode 100755 index 0000000000..9764d4e076 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c @@ -0,0 +1,426 @@ +/* +** cmdbreak.c +** +*/ + +#include +#include +#include "mruby.h" +#include "mruby/dump.h" +#include "mruby/debug.h" +#include "mruby/string.h" +#include "mrdb.h" +#include "mrdberror.h" +#include "apibreak.h" + +#define BREAK_SET_MSG_LINE "Breakpoint %d: file %s, line %d.\n" +#define BREAK_SET_MSG_METHOD "Breakpoint %d: method %s.\n" +#define BREAK_SET_MSG_CLASS_METHOD "Breakpoint %d: class %s, method %s.\n" +#define BREAK_INFO_MSG_HEADER "Num Type Enb What" +#define BREAK_INFO_MSG_LINEBREAK "%-8ubreakpoint %s at %s:%u\n" +#define BREAK_INFO_MSG_METHODBREAK "%-8ubreakpoint %s in %s:%s\n" +#define BREAK_INFO_MSG_METHODBREAK_NOCLASS "%-8ubreakpoint %s in %s\n" +#define BREAK_INFO_MSG_ENABLE "y" +#define BREAK_INFO_MSG_DISABLE "n" + +#define BREAK_ERR_MSG_INVALIDARG "Internal error." +#define BREAK_ERR_MSG_BLANK "Try \'help break\' for more information." +#define BREAK_ERR_MSG_RANGEOVER "The line number range is from 1 to 65535." +#define BREAK_ERR_MSG_NUMOVER "Exceeded the setable number of breakpoint." +#define BREAK_ERR_MSG_NOOVER "Breakno is over the available number.Please 'quit' and restart mrdb." +#define BREAK_ERR_MSG_INVALIDSTR "String \'%s\' is invalid.\n" +#define BREAK_ERR_MSG_INVALIDLINENO "Line %d in file \"%s\" is unavailable.\n" +#define BREAK_ERR_MSG_INVALIDCLASS "Class name \'%s\' is invalid.\n" +#define BREAK_ERR_MSG_INVALIDMETHOD "Method name \'%s\' is invalid.\n" +#define BREAK_ERR_MSG_INVALIDFILE "Source file named \"%s\" is unavailable.\n" +#define BREAK_ERR_MSG_INVALIDBPNO "warning: bad breakpoint number at or near '%s'\n" +#define BREAK_ERR_MSG_INVALIDBPNO_INFO "Args must be numbers variables." +#define BREAK_ERR_MSG_NOBPNO "No breakpoint number %d.\n" +#define BREAK_ERR_MSG_NOBPNO_INFO "No breakpoint matching '%d'\n" +#define BREAK_ERR_MSG_NOBPNO_INFOALL "No breakpoints." + +#define LINENO_MAX_DIGIT 6 +#define BPNO_LETTER_NUM 9 + +typedef int32_t (*all_command_func)(mrb_state *, mrb_debug_context *); +typedef int32_t (*select_command_func)(mrb_state *, mrb_debug_context *, uint32_t); + +static void +print_api_common_error(int32_t error) +{ + switch(error) { + case MRB_DEBUG_INVALID_ARGUMENT: + puts(BREAK_ERR_MSG_INVALIDARG); + break; + default: + break; + } +} + +#undef STRTOUL +#define STRTOUL(ul,s) \ + ul = 0; \ + for(int i=0; ISDIGIT(s[i]); i++) ul = 10*ul + (s[i] -'0'); + +static int32_t +parse_breakpoint_no(char* args) +{ + char* ps = args; + uint32_t l; + + if((*ps == '0')||(strlen(ps) >= BPNO_LETTER_NUM)) { + return 0; + } + + while( !(ISBLANK(*ps)||ISCNTRL(*ps)) ) { + if(!ISDIGIT(*ps)) { + return 0; + } + ps++; + } + + STRTOUL(l, args); + return l; +} + +static mrb_bool +exe_set_command_all(mrb_state *mrb, mrdb_state *mrdb, all_command_func func) +{ + int32_t ret = MRB_DEBUG_OK; + + if(mrdb->wcnt == 1) { + ret = func(mrb, mrdb->dbg); + print_api_common_error(ret); + return TRUE; + } + return FALSE; +} + +static void +exe_set_command_select(mrb_state *mrb, mrdb_state *mrdb, select_command_func func) +{ + char* ps; + int32_t ret = MRB_DEBUG_OK; + int32_t bpno = 0; + int32_t i; + + for(i=1; iwcnt; i++) { + ps = mrdb->words[i]; + bpno = parse_breakpoint_no(ps); + if(bpno == 0) { + printf(BREAK_ERR_MSG_INVALIDBPNO, ps); + break; + } + ret = func(mrb, mrdb->dbg, (uint32_t)bpno); + if(ret == MRB_DEBUG_BREAK_INVALID_NO) { + printf(BREAK_ERR_MSG_NOBPNO, bpno); + } + else if(ret != MRB_DEBUG_OK) { + print_api_common_error(ret); + } + } +} + +mrb_debug_bptype +check_bptype(char* args) +{ + char* ps = args; + + if(ISBLANK(*ps)||ISCNTRL(*ps)) { + puts(BREAK_ERR_MSG_BLANK); + return MRB_DEBUG_BPTYPE_NONE; + } + + if(!ISDIGIT(*ps)) { + return MRB_DEBUG_BPTYPE_METHOD; + } + + while( !(ISBLANK(*ps)||ISCNTRL(*ps)) ) { + if(!ISDIGIT(*ps)) { + printf(BREAK_ERR_MSG_INVALIDSTR, args); + return MRB_DEBUG_BPTYPE_NONE; + } + ps++; + } + + if((*args == '0')||(strlen(args) >= LINENO_MAX_DIGIT)) { + puts(BREAK_ERR_MSG_RANGEOVER); + return MRB_DEBUG_BPTYPE_NONE; + } + + return MRB_DEBUG_BPTYPE_LINE; +} + +static void +print_breakpoint(mrb_debug_breakpoint *bp) +{ + char* enable_letter[] = {BREAK_INFO_MSG_DISABLE, BREAK_INFO_MSG_ENABLE}; + + if(bp->type == MRB_DEBUG_BPTYPE_LINE) { + printf(BREAK_INFO_MSG_LINEBREAK, + bp->bpno, enable_letter[bp->enable], bp->point.linepoint.file, bp->point.linepoint.lineno); + } + else { + if(bp->point.methodpoint.class_name == NULL) { + printf(BREAK_INFO_MSG_METHODBREAK_NOCLASS, + bp->bpno, enable_letter[bp->enable], bp->point.methodpoint.method_name); + } + else { + printf(BREAK_INFO_MSG_METHODBREAK, + bp->bpno, enable_letter[bp->enable], bp->point.methodpoint.class_name, bp->point.methodpoint.method_name); + } + } +} + +static void +info_break_all(mrb_state *mrb, mrdb_state *mrdb) +{ + int32_t bpnum = 0; + int32_t i = 0; + int32_t ret = MRB_DEBUG_OK; + mrb_debug_breakpoint *bp_list; + + bpnum = mrb_debug_get_breaknum(mrb, mrdb->dbg); + if(bpnum < 0) { + print_api_common_error(bpnum); + return; + } + else if(bpnum == 0) { + puts(BREAK_ERR_MSG_NOBPNO_INFOALL); + return; + } + bp_list = (mrb_debug_breakpoint*)mrb_malloc(mrb, bpnum * sizeof(mrb_debug_breakpoint)); + + ret = mrb_debug_get_break_all(mrb, mrdb->dbg, (uint32_t)bpnum, bp_list); + if(ret < 0) { + print_api_common_error(ret); + return; + } + puts(BREAK_INFO_MSG_HEADER); + for(i = 0 ; i < bpnum ; i++) { + print_breakpoint(&bp_list[i]); + } + + mrb_free(mrb, bp_list); +} + +static void +info_break_select(mrb_state *mrb, mrdb_state *mrdb) +{ + int32_t ret = MRB_DEBUG_OK; + int32_t bpno = 0; + char* ps = mrdb->command; + mrb_debug_breakpoint bp; + mrb_bool isFirst = TRUE; + int32_t i; + + for(i=2; iwcnt; i++) { + ps = mrdb->words[i]; + bpno = parse_breakpoint_no(ps); + if(bpno == 0) { + puts(BREAK_ERR_MSG_INVALIDBPNO_INFO); + break; + } + + ret = mrb_debug_get_break(mrb, mrdb->dbg, bpno, &bp); + if(ret == MRB_DEBUG_BREAK_INVALID_NO) { + printf(BREAK_ERR_MSG_NOBPNO_INFO, bpno); + break; + } + else if(ret != MRB_DEBUG_OK) { + print_api_common_error(ret); + break; + } + else if(isFirst == TRUE) { + isFirst = FALSE; + puts(BREAK_INFO_MSG_HEADER); + } + print_breakpoint(&bp); + } +} + +mrb_debug_bptype +parse_breakcommand(mrdb_state *mrdb, const char **file, uint32_t *line, char **cname, char **method) +{ + mrb_debug_context *dbg = mrdb->dbg; + char *args; + char *body; + mrb_debug_bptype type; + uint32_t l; + + if(mrdb->wcnt <= 1) { + puts(BREAK_ERR_MSG_BLANK); + return MRB_DEBUG_BPTYPE_NONE; + } + + args = mrdb->words[1]; + if((body = strchr(args, ':')) == NULL) { + body = args; + type = check_bptype(body); + } else { + if(body == args) { + printf(BREAK_ERR_MSG_INVALIDSTR, args); + return MRB_DEBUG_BPTYPE_NONE; + } + *body = '\0'; + type = check_bptype(++body); + } + + switch(type) { + case MRB_DEBUG_BPTYPE_LINE: + STRTOUL(l, body); + if( l <= 65535 ) { + *line = l; + *file = (body == args)? mrb_debug_get_filename(dbg->irep, (uint32_t)(dbg->pc - dbg->irep->iseq)): args; + } else { + puts(BREAK_ERR_MSG_RANGEOVER); + type = MRB_DEBUG_BPTYPE_NONE; + } + break; + case MRB_DEBUG_BPTYPE_METHOD: + if(body == args) { + /* method only */ + if( ISUPPER(*body)||ISLOWER(*body)||(*body == '_') ) { + *method = body; + *cname = NULL; + } else { + printf(BREAK_ERR_MSG_INVALIDMETHOD, args); + type = MRB_DEBUG_BPTYPE_NONE; + } + } else { + if( ISUPPER(*args) ) { + switch(*body) { + case '@': case '$': case '?': case '.': case ',': case ':': + case ';': case '#': case '\\': case '\'': case '\"': + printf(BREAK_ERR_MSG_INVALIDMETHOD, body); + type = MRB_DEBUG_BPTYPE_NONE; + break; + default: + *method = body; + *cname = args; + break; + } + } else { + printf(BREAK_ERR_MSG_INVALIDCLASS, args); + type = MRB_DEBUG_BPTYPE_NONE; + } + } + break; + case MRB_DEBUG_BPTYPE_NONE: + default: + break; + } + + return type; +} + +dbgcmd_state +dbgcmd_break(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_debug_bptype type; + mrb_debug_context *dbg = mrdb->dbg; + const char *file = NULL; + uint32_t line = 0; + char *cname = NULL; + char *method = NULL; + int32_t ret; + + type = parse_breakcommand(mrdb, &file, &line, &cname, &method); + switch (type) { + case MRB_DEBUG_BPTYPE_LINE: + ret = mrb_debug_set_break_line(mrb, dbg, file, line); + break; + case MRB_DEBUG_BPTYPE_METHOD: + ret = mrb_debug_set_break_method(mrb, dbg, cname, method); + break; + case MRB_DEBUG_BPTYPE_NONE: + default: + return DBGST_PROMPT; + } + + if (ret >= 0) { + if (type == MRB_DEBUG_BPTYPE_LINE) { + printf(BREAK_SET_MSG_LINE, ret, file, line); + } else if ((type == MRB_DEBUG_BPTYPE_METHOD)&&(cname == NULL)) { + printf(BREAK_SET_MSG_METHOD, ret, method); + } else { + printf(BREAK_SET_MSG_CLASS_METHOD, ret, cname, method); + } + } else { + switch (ret) { + case MRB_DEBUG_BREAK_INVALID_LINENO: + printf(BREAK_ERR_MSG_INVALIDLINENO, line, file); + break; + case MRB_DEBUG_BREAK_INVALID_FILE: + printf(BREAK_ERR_MSG_INVALIDFILE, file); + break; + case MRB_DEBUG_BREAK_NUM_OVER: + puts(BREAK_ERR_MSG_NUMOVER); + break; + case MRB_DEBUG_BREAK_NO_OVER: + puts(BREAK_ERR_MSG_NOOVER); + break; + case MRB_DEBUG_INVALID_ARGUMENT: + puts(BREAK_ERR_MSG_INVALIDARG); + break; + case MRB_DEBUG_NOBUF: + puts("T.B.D."); + break; + default: + break; + } + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_info_break(mrb_state *mrb, mrdb_state *mrdb) +{ + if(mrdb->wcnt == 2) { + info_break_all(mrb, mrdb); + } + else { + info_break_select(mrb, mrdb); + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_delete(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_bool ret = FALSE; + + ret = exe_set_command_all(mrb, mrdb, mrb_debug_delete_break_all); + if(ret != TRUE) { + exe_set_command_select(mrb, mrdb, mrb_debug_delete_break); + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_enable(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_bool ret = FALSE; + + ret = exe_set_command_all(mrb, mrdb, mrb_debug_enable_break_all); + if(ret != TRUE) { + exe_set_command_select(mrb, mrdb, mrb_debug_enable_break); + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_disable(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_bool ret = FALSE; + + ret = exe_set_command_all(mrb, mrdb, mrb_debug_disable_break_all); + if(ret != TRUE) { + exe_set_command_select(mrb, mrdb, mrb_debug_disable_break); + } + return DBGST_PROMPT; +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c new file mode 100755 index 0000000000..b8d62ac07e --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c @@ -0,0 +1,500 @@ +/* +** cmdmisc.c - mruby debugger miscellaneous command functions +** +*/ + +#include +#include +#include + +#include "apilist.h" +#include "mruby/compile.h" + +typedef struct help_msg { + const char *cmd1; + const char *cmd2; + const char *short_msg; + const char *long_msg; +} help_msg; + +static help_msg help_msg_list[] = { + { + "b[reak]", NULL, "Set breakpoint", + "Usage: break [file:]line\n" + " break [class:]method\n" + "\n" + "Set breakpoint at specified line or method.\n" + "If \'[file:]line\' is specified, break at start of code for that line (in a file).\n" + "If \'[class:]method\' is specified, break at start of code for that method (of the class).\n" + }, + { + "c[ontinue]", NULL, "Continue program being debugged", + "Usage: continue [N]\n" + "\n" + "Continue program stopped by a breakpoint.\n" + "If N, which is non negative value, is passed,\n" + "proceed program until the N-th breakpoint is comming.\n" + "If N is not passed, N is assumed 1.\n" + }, + { + "d[elete]", NULL, "Delete some breakpoints", + "Usage: delete [bpno1 [bpno2 [... [bpnoN]]]]\n" + "\n" + "Delete some breakpoints.\n" + "Arguments are breakpoint numbers with spaces in between.\n" + "To delete all breakpoints, give no argument.\n" + }, + { + "dis[able]", NULL, "Disable some breakpoints", + "Usage: disable [bpno1 [bpno2 [... [bpnoN]]]]\n" + "\n" + "Disable some breakpoints.\n" + "Arguments are breakpoint numbers with spaces in between.\n" + "To disable all breakpoints, give no argument.\n" + }, + { + "en[able]", NULL, "Enable some breakpoints", + "Usage: enable [bpno1 [bpno2 [... [bpnoN]]]]\n" + "\n" + "Enable some breakpoints.\n" + "Arguments are breakpoint numbers with spaces in between.\n" + "To enable all breakpoints, give no argument.\n" + }, + { + "ev[al]", NULL, "Evaluate expression", + "Usage: eval expr\n" + "\n" + "It evaluates and prints the value of the mruby expression.\n" + "This is equivalent to the \'print\' command.\n" + }, + { + "h[elp]", NULL, "Print this help", + "Usage: help [command]\n" + "\n" + "With no arguments, help displays a short list of commands.\n" + "With a command name as help argument, help displays how to use that command.\n" + }, + { + "i[nfo]", "b[reakpoints]", "Status of breakpoints", + "Usage: info breakpoints [bpno1 [bpno2 [... [bpnoN]]]]\n" + "\n" + "Status of specified breakpoints (all user-settable breakpoints if no argument).\n" + "Arguments are breakpoint numbers with spaces in between.\n" + }, + { + "l[ist]", NULL, "List specified line", + "Usage: list\n" + " list first[,last]\n" + " list filename:first[,last]\n" + "\n" + "Print lines from a source file.\n" + "\n" + "With first and last, list prints lines from first to last.\n" + "When last is empty, it stands for ten lines away from first.\n" + "With filename, list prints lines in the specified source file.\n" + }, + { + "p[rint]", NULL, "Print value of expression", + "Usage: print expr\n" + "\n" + "It evaluates and prints the value of the mruby expression.\n" + "This is equivalent to the \'eval\' command.\n" + }, + { + "q[uit]", NULL, "Exit mrdb", + "Usage: quit\n" + "\n" + "Exit mrdb.\n" + }, + { + "r[un]", NULL, "Start debugged program", + "Usage: run\n" + "\n" + "Start debugged program.\n" + }, + { + "s[tep]", NULL, "Step program until it reaches a different source line", + "Usage: step\n" + "\n" + "Step program until it reaches a different source line.\n" + }, + { NULL, NULL, NULL, NULL } +}; + +typedef struct listcmd_parser_state { + mrb_bool parse_error; + mrb_bool has_line_min; + mrb_bool has_line_max; + char *filename; + uint16_t line_min; + uint16_t line_max; +} listcmd_parser_state; + +static listcmd_parser_state* +listcmd_parser_state_new(mrb_state *mrb) +{ + listcmd_parser_state *st = mrb_malloc(mrb, sizeof(listcmd_parser_state)); + memset(st, 0, sizeof(listcmd_parser_state)); + return st; +} + +static void +listcmd_parser_state_free(mrb_state *mrb, listcmd_parser_state *st) +{ + if (st != NULL) { + if (st->filename != NULL) { + mrb_free(mrb, st->filename); + } + mrb_free(mrb, st); + } +} + +static mrb_bool +parse_uint(char **sp, uint16_t *n) +{ + char *p; + int i; + + if (*sp == NULL || **sp == '\0') { + return FALSE; + } + + for (p = *sp; *p != '\0' && ISDIGIT(*p); p++) ; + + if (p != *sp && (i = atoi(*sp)) >= 0) { + *n = (uint16_t)i; + *sp = p; + return TRUE; + } + return FALSE; +} + +static mrb_bool +skip_char(char **sp, char c) +{ + if (*sp != NULL && **sp == c) { + ++*sp; + return TRUE; + } + return FALSE; +} + +static mrb_bool +parse_lineno(mrb_state *mrb, char **sp, listcmd_parser_state *st) +{ + if (*sp == NULL || **sp == '\0') { + return FALSE; + } + + st->has_line_min = FALSE; + st->has_line_max = FALSE; + + if (parse_uint(sp, &st->line_min)) { + st->has_line_min = TRUE; + } + else { + return FALSE; + } + + if (skip_char(sp, ',')) { + if (parse_uint(sp, &st->line_max)) { + st->has_line_max = TRUE; + } + else { + st->parse_error = TRUE; + return FALSE; + } + } + return TRUE; +} + +static mrb_bool +parse_filename(mrb_state *mrb, char **sp, listcmd_parser_state *st) +{ + char *p; + int len; + + if (st->filename != NULL) { + mrb_free(mrb, st->filename); + st->filename = NULL; + } + + if ((p = strchr(*sp, ':')) != NULL) { + len = p - *sp; + } + else { + len = strlen(*sp); + } + + if (len > 0) { + st->filename = mrb_malloc(mrb, len + 1); + strncpy(st->filename, *sp, len); + st->filename[len] = '\0'; + *sp += len; + return TRUE; + } + else { + return FALSE; + } +} + +char* +replace_ext(mrb_state *mrb, const char *filename, const char *ext) +{ + size_t len; + char *p, *s; + + if (filename == NULL) { + return NULL; + } + + if ((p = strrchr(filename, '.')) != NULL && strchr(p, '/') == NULL) { + len = p - filename; + } + else { + len = strlen(filename); + } + + if ((s = mrb_malloc(mrb, len + strlen(ext) + 1)) != NULL) { + memset(s, '\0', len + strlen(ext) + 1); + strncpy(s, filename, len); + strcat(s, ext); + } + return s; +} + +static mrb_bool +parse_listcmd_args(mrb_state *mrb, mrdb_state *mrdb, listcmd_parser_state *st) +{ + char *p; + + switch (mrdb->wcnt) { + case 2: + p = mrdb->words[1]; + + /* mrdb->words[1] ::= | ':' | */ + if (!parse_lineno(mrb, &p, st)) { + if (parse_filename(mrb, &p, st)) { + if (skip_char(&p, ':')) { + if (!parse_lineno(mrb, &p, st)) { + st->parse_error = TRUE; + } + } + } + else { + st->parse_error = TRUE; + } + } + if (*p != '\0') { + st->parse_error = TRUE; + } + break; + case 1: + case 0: + /* do nothing */ + break; + default: + st->parse_error = TRUE; + printf("too many arguments\n"); + break; + } + + if (!st->parse_error) { + if (!st->has_line_min) { + st->line_min = (!st->filename && mrdb->dbg->prvline > 0) ? mrdb->dbg->prvline : 1; + } + + if (!st->has_line_max) { + st->line_max = st->line_min + 9; + } + + if (st->filename == NULL) { + if (mrdb->dbg->prvfile && strcmp(mrdb->dbg->prvfile, "-")) { + st->filename = replace_ext(mrb, mrdb->dbg->prvfile, ".rb"); + } + } + } + + if (st->parse_error || st->filename == NULL) { + return FALSE; + } + + return TRUE; +} + +static mrb_bool +check_cmd_pattern(const char *pattern, const char *cmd) +{ + char *lbracket, *rbracket, *p, *q; + + if (pattern == NULL && cmd == NULL) { + return TRUE; + } + if (pattern == NULL || cmd == NULL) { + return FALSE; + } + if((lbracket = strchr(pattern, '[')) == NULL) { + return !strcmp(pattern, cmd); + } + if ((rbracket = strchr(pattern, ']')) == NULL) { + return FALSE; + } + if (strncmp(pattern, cmd, lbracket - pattern)) { + return FALSE; + } + + p = lbracket + 1; + q = (char *)cmd + (lbracket - pattern); + + for ( ; p < rbracket && *q != '\0'; p++, q++) { + if (*p != *q) { + break; + } + } + return *q == '\0'; +} + +static help_msg* +get_help_msg(char *cmd1, char *cmd2) +{ + help_msg *p; + + if (cmd1 == NULL) { + return NULL; + } + for (p = help_msg_list; p->cmd1 != NULL; p++) { + if (check_cmd_pattern(p->cmd1, cmd1) && check_cmd_pattern(p->cmd2, cmd2)) { + return p; + } + } + return NULL; +} + +static mrb_bool +show_short_help(void) +{ + help_msg *p; + + printf("Commands\n"); + + for (p = help_msg_list; p->cmd1 != NULL; p++) { + if (p->cmd2 == NULL) { + printf(" %s -- %s\n", p->cmd1, p->short_msg); + } + else { + printf(" %s %s -- %s\n", p->cmd1, p->cmd2, p->short_msg); + } + } + return TRUE; +} + +static mrb_bool +show_long_help(char *cmd1, char *cmd2) +{ + help_msg *help; + + if ((help = get_help_msg(cmd1, cmd2)) == NULL) { + return FALSE; + } + printf("%s", help->long_msg); + return TRUE; +} + +dbgcmd_state +dbgcmd_list(mrb_state *mrb, mrdb_state *mrdb) +{ + char *filename; + listcmd_parser_state *st = listcmd_parser_state_new(mrb); + + if (parse_listcmd_args(mrb, mrdb, st)) { + if ((filename = mrb_debug_get_source(mrb, mrdb, mrdb->srcpath, st->filename)) == NULL) { + filename = st->filename; + } + mrb_debug_list(mrb, mrdb->dbg, filename, st->line_min, st->line_max); + listcmd_parser_state_free(mrb, st); + + if (filename != NULL && filename != st->filename) { + mrb_free(mrb, filename); + } + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_help(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_bool is_valid; + int i; + + switch (mrdb->wcnt) { + case 0: + case 1: + is_valid = show_short_help(); + break; + case 2: + is_valid = show_long_help(mrdb->words[1], NULL); + break; + case 3: + is_valid = show_long_help(mrdb->words[1], mrdb->words[2]); + break; + default: + is_valid = FALSE; + break; + } + + if (!is_valid) { + printf("Invalid command \""); + for (i = 1; i < mrdb->wcnt; i++) { + printf("%s%s", i == 1 ? "" : " ", mrdb->words[i]); + } + printf("\". Try \"help\".\n"); + } + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_quit(mrb_state *mrb, mrdb_state *mrdb) +{ + switch (mrdb->dbg->xm) { + case DBG_RUN: + case DBG_STEP: + case DBG_NEXT: + while (1) { + char c; + int buf; + + printf("The program is running. Exit anyway? (y or n) "); + + if ((buf = getchar()) == EOF) { + mrdb->dbg->xm = DBG_QUIT; + break; + } + c = buf; + while (buf != '\n' && (buf = getchar()) != EOF) ; + + if (c == 'y' || c == 'Y') { + mrdb->dbg->xm = DBG_QUIT; + break; + } + else if (c == 'n' || c == 'N') { + break; + } + else { + printf("Please answer y or n.\n"); + } + } + break; + default: + mrdb->dbg->xm = DBG_QUIT; + break; + } + + if (mrdb->dbg->xm == DBG_QUIT) { + struct RClass *exc; + exc = mrb_define_class(mrb, "DebuggerExit", mrb_class_get(mrb, "Exception")); + mrb_raise(mrb, exc, "Exit mrdb."); + } + return DBGST_PROMPT; +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c new file mode 100755 index 0000000000..16b811edb6 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c @@ -0,0 +1,58 @@ +/* +** cmdprint.c - mruby debugger print command functions +** +*/ + +#include +#include "mrdb.h" +#include "mruby/value.h" +#include "mruby/class.h" +#include "mruby/compile.h" +#include "mruby/error.h" +#include "mruby/numeric.h" +#include "mruby/string.h" +#include "apiprint.h" + +dbgcmd_state +dbgcmd_print(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_value expr; + mrb_value result; + mrb_value s; + uint8_t wcnt; + int ai; + + if (mrdb->wcnt <= 1) { + puts("Parameter not specified."); + return DBGST_PROMPT; + } + + ai = mrb_gc_arena_save(mrb); + + /* eval expr */ + expr = mrb_str_new_cstr(mrb, NULL); + for (wcnt=1; wcntwcnt; wcnt++) { + expr = mrb_str_cat_cstr(mrb, expr, " "); + expr = mrb_str_cat_cstr(mrb, expr, mrdb->words[wcnt]); + } + + result = mrb_debug_eval(mrb, mrdb->dbg, RSTRING_PTR(expr), RSTRING_LEN(expr), NULL); + + /* $print_no = result */ + s = mrb_str_cat_cstr(mrb, result, "\0"); + printf("$%lu = %s\n", (unsigned long)mrdb->print_no++, RSTRING_PTR(s)); + + if (mrdb->print_no == 0) { + mrdb->print_no = 1; + } + + mrb_gc_arena_restore(mrb, ai); + + return DBGST_PROMPT; +} + +dbgcmd_state +dbgcmd_eval(mrb_state *mrb, mrdb_state *mrdb) +{ + return dbgcmd_print(mrb, mrdb); +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c new file mode 100755 index 0000000000..8d8340560f --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c @@ -0,0 +1,54 @@ +/* +** cmdrun.c - mruby debugger run command functions +** +*/ + +#include "mruby/opcode.h" +#include "mrdb.h" + +dbgcmd_state +dbgcmd_run(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_debug_context *dbg = mrdb->dbg; + + if( dbg->xm == DBG_INIT ){ + dbg->xm = DBG_RUN; + } else { + dbg->xm = DBG_QUIT; + if( dbg->xphase == DBG_PHASE_RUNNING ){ + struct RClass *exc; + puts("Start it from the beginning."); + exc = mrb_define_class(mrb, "DebuggerRestart", mrb_class_get(mrb, "Exception")); + mrb_raise(mrb, exc, "Restart mrdb."); + } + } + + return DBGST_RESTART; +} + +dbgcmd_state +dbgcmd_continue(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_debug_context *dbg = mrdb->dbg; + int ccnt = 1; + + if( mrdb->wcnt > 1 ){ + sscanf(mrdb->words[1], "%d", &ccnt); + } + dbg->ccnt = (uint16_t)(ccnt > 0 ? ccnt : 1); /* count of continue */ + + if( dbg->xphase == DBG_PHASE_AFTER_RUN ){ + puts("The program is not running."); + dbg->xm = DBG_QUIT; + } else { + dbg->xm = DBG_RUN; + } + return DBGST_CONTINUE; +} + +dbgcmd_state +dbgcmd_step(mrb_state *mrb, mrdb_state *mrdb) +{ + mrdb->dbg->xm = DBG_STEP; + return DBGST_CONTINUE; +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c new file mode 100755 index 0000000000..ec5a68dc12 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c @@ -0,0 +1,746 @@ +/* +** mrdb.c - mruby debugger +** +*/ + +#include +#include +#include +#include + +#include "mruby.h" +#include "mruby/dump.h" +#include "mruby/debug.h" +#include "mruby/class.h" +#include "mruby/opcode.h" +#include "mruby/variable.h" + +#include "mrdb.h" +#include "apibreak.h" +#include "apilist.h" + +void mrb_show_version(mrb_state *); +void mrb_show_copyright(mrb_state *); +void mrdb_state_free(mrb_state *); + +static mrb_debug_context *_debug_context = NULL; +static mrdb_state *_mrdb_state = NULL; + +struct _args { + FILE *rfp; + char* fname; + char* srcpath; + int argc; + char** argv; + mrb_bool mrbfile : 1; +}; + +typedef struct debug_command { + const char *cmd1; + const char *cmd2; + uint8_t len1; + uint8_t len2; + uint8_t div; + debug_command_id id; + debug_command_func func; +} debug_command; + +static const debug_command debug_command_list[] = { + {"break", NULL, 1, 0, 0, DBGCMD_BREAK, dbgcmd_break}, /* b[reak] */ + {"continue", NULL, 1, 0, 0, DBGCMD_CONTINUE, dbgcmd_continue}, /* c[ontinue] */ + {"delete", NULL, 1, 0, 1, DBGCMD_DELETE, dbgcmd_delete}, /* d[elete] */ + {"disable", NULL, 3, 0, 1, DBGCMD_DISABLE, dbgcmd_disable}, /* dis[able] */ + {"enable", NULL, 2, 0, 1, DBGCMD_ENABLE, dbgcmd_enable}, /* en[able] */ + {"eval", NULL, 2, 0, 0, DBGCMD_EVAL, dbgcmd_eval}, /* ev[al] */ + {"help", NULL, 1, 0, 1, DBGCMD_HELP, dbgcmd_help}, /* h[elp] */ + {"info", "breakpoints", 1, 1, 1, DBGCMD_INFO_BREAK, dbgcmd_info_break}, /* i[nfo] b[reakpoints] */ + {"list", NULL, 1, 0, 1, DBGCMD_LIST, dbgcmd_list}, /* l[ist] */ + {"print", NULL, 1, 0, 0, DBGCMD_PRINT, dbgcmd_print}, /* p[rint] */ + {"quit", NULL, 1, 0, 0, DBGCMD_QUIT, dbgcmd_quit}, /* q[uit] */ + {"run", NULL, 1, 0, 0, DBGCMD_RUN, dbgcmd_run}, /* r[un] */ + {"step", NULL, 1, 0, 1, DBGCMD_STEP, dbgcmd_step}, /* s[tep] */ + {NULL} +}; + + +static void +usage(const char *name) +{ + static const char *const usage_msg[] = { + "switches:", + "-b load and execute RiteBinary (mrb) file", + "-d specify source directory", + "--version print the version", + "--copyright print the copyright", + NULL + }; + const char *const *p = usage_msg; + + printf("Usage: %s [switches] programfile\n", name); + while (*p) { + printf(" %s\n", *p++); + } +} + +static int +parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args) +{ + char **origargv = argv; + static const struct _args args_zero = { 0 }; + + *args = args_zero; + + for (argc--,argv++; argc > 0; argc--,argv++) { + char *item; + if (argv[0][0] != '-') break; + + item = argv[0] + 1; + switch (*item++) { + case 'b': + args->mrbfile = TRUE; + break; + case 'd': + if (item[0]) { + goto append_srcpath; + } + else if (argc > 1) { + argc--; argv++; + item = argv[0]; +append_srcpath: + if (!args->srcpath) { + size_t buflen; + char *buf; + + buflen = strlen(item) + 1; + buf = (char *)mrb_malloc(mrb, buflen); + memcpy(buf, item, buflen); + args->srcpath = buf; + } + else { + size_t srcpathlen; + size_t itemlen; + + srcpathlen = strlen(args->srcpath); + itemlen = strlen(item); + args->srcpath = + (char *)mrb_realloc(mrb, args->srcpath, srcpathlen + itemlen + 2); + args->srcpath[srcpathlen] = '\n'; + memcpy(args->srcpath + srcpathlen + 1, item, itemlen + 1); + } + } + else { + printf("%s: No path specified for -d\n", *origargv); + return EXIT_SUCCESS; + } + break; + case '-': + if (strcmp((*argv) + 2, "version") == 0) { + mrb_show_version(mrb); + exit(EXIT_SUCCESS); + } + else if (strcmp((*argv) + 2, "copyright") == 0) { + mrb_show_copyright(mrb); + exit(EXIT_SUCCESS); + } + default: + return EXIT_FAILURE; + } + } + + if (args->rfp == NULL) { + if (*argv == NULL) { + printf("%s: Program file not specified.\n", *origargv); + return EXIT_FAILURE; + } + else { + args->rfp = fopen(argv[0], args->mrbfile ? "rb" : "r"); + if (args->rfp == NULL) { + printf("%s: Cannot open program file. (%s)\n", *origargv, *argv); + return EXIT_FAILURE; + } + args->fname = argv[0]; + argc--; argv++; + } + } + args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1)); + memcpy(args->argv, argv, (argc+1) * sizeof(char*)); + args->argc = argc; + + return EXIT_SUCCESS; +} + +static void +cleanup(mrb_state *mrb, struct _args *args) +{ + if (args->rfp) + fclose(args->rfp); + if (args->srcpath) + mrb_free(mrb, args->srcpath); + if (args->argv) + mrb_free(mrb, args->argv); + mrdb_state_free(mrb); + mrb_close(mrb); +} + +static mrb_debug_context* +mrb_debug_context_new(mrb_state *mrb) +{ + mrb_debug_context *dbg = mrb_malloc(mrb, sizeof(mrb_debug_context)); + + memset(dbg, 0, sizeof(mrb_debug_context)); + + dbg->xm = DBG_INIT; + dbg->xphase = DBG_PHASE_BEFORE_RUN; + dbg->next_bpno = 1; + + return dbg; +} + +mrb_debug_context* +mrb_debug_context_get(mrb_state *mrb) +{ + if (!_debug_context) { + _debug_context = mrb_debug_context_new(mrb); + } + return _debug_context; +} + +void +mrb_debug_context_set(mrb_debug_context *dbg) +{ + _debug_context = dbg; +} + +void +mrb_debug_context_free(mrb_state *mrb) +{ + if (_debug_context) { + mrb_free(mrb, _debug_context); + _debug_context = NULL; + } +} + +static mrdb_state* +mrdb_state_new(mrb_state *mrb) +{ + mrdb_state *mrdb = mrb_malloc(mrb, sizeof(mrb_state)); + + memset(mrdb, 0, sizeof(mrb_state)); + + mrdb->dbg = mrb_debug_context_get(mrb); + mrdb->command = mrb_malloc(mrb, MAX_COMMAND_LINE+1); + mrdb->print_no = 1; + + return mrdb; +} + +mrdb_state* +mrdb_state_get(mrb_state *mrb) +{ + if (!_mrdb_state) { + _mrdb_state = mrdb_state_new(mrb); + } + return _mrdb_state; +} + +void +mrdb_state_set(mrdb_state *mrdb) +{ + _mrdb_state = mrdb; +} + +void +mrdb_state_free(mrb_state *mrb) +{ + mrb_debug_context_free(mrb); + if (_mrdb_state) { + mrb_free(mrb, _mrdb_state->command); + mrb_free(mrb, _mrdb_state); + _mrdb_state = NULL; + } +} + +static char* +get_command(mrb_state *mrb, mrdb_state *mrdb) +{ + int i; + int c; + + for (i=0; icommand[i] = c; + } + + if (i == 0 && feof(stdin)) { + clearerr(stdin); + strcpy(mrdb->command, "quit"); + i += strlen("quit"); + } + + if (i == MAX_COMMAND_LINE) { + for ( ; (c=getchar()) != EOF && c !='\n'; i++) ; + } + + if (i > MAX_COMMAND_LINE) { + printf("command line too long.\n"); + i = 0; /* discard command data */ + } + mrdb->command[i] = '\0'; + + return mrdb->command; +} + +static char* +pick_out_word(mrb_state *mrb, char **pp) +{ + char *ps; + + for (ps=*pp; ISBLANK(*ps); ps++) ; + if (*ps == '\0') { + return NULL; + } + + if (*ps == '\"' || *ps == '\'') { + *pp = strchr(ps+1, *ps); + if (*pp) (*pp)++; + } + else { + *pp = strpbrk(ps, " \t"); + } + + if (!*pp) { + *pp = ps + strlen(ps); + } + + if (**pp != '\0') { + **pp = '\0'; + (*pp)++; + } + + return ps; +} + +static debug_command* +parse_command(mrb_state *mrb, mrdb_state *mrdb, char *buf) +{ + debug_command *cmd = NULL; + char *p = buf; + size_t wlen; + + /* get word #1 */ + mrdb->words[0] = pick_out_word(mrb, &p); + if (!mrdb->words[0]) { + return NULL; + } + mrdb->wcnt = 1; + /* set remain parameter */ + for ( ; *p && ISBLANK(*p); p++) ; + if (*p) { + mrdb->words[mrdb->wcnt++] = p; + } + + /* check word #1 */ + for (cmd=(debug_command*)debug_command_list; cmd->cmd1; cmd++) { + wlen = strlen(mrdb->words[0]); + if (wlen >= cmd->len1 && + strncmp(mrdb->words[0], cmd->cmd1, wlen) == 0) { + break; + } + } + + if (cmd->cmd2) { + if (mrdb->wcnt > 1) { + /* get word #2 */ + mrdb->words[1] = pick_out_word(mrb, &p); + if (mrdb->words[1]) { + /* update remain parameter */ + for ( ; *p && ISBLANK(*p); p++) ; + if (*p) { + mrdb->words[mrdb->wcnt++] = p; + } + } + } + + /* check word #1,#2 */ + for ( ; cmd->cmd1; cmd++) { + wlen = strlen(mrdb->words[0]); + if (wlen < cmd->len1 || + strncmp(mrdb->words[0], cmd->cmd1, wlen)) { + continue; + } + + if (!cmd->cmd2) break; /* word #1 only */ + + if (mrdb->wcnt == 1) continue; /* word #2 not specified */ + + wlen = strlen(mrdb->words[1]); + if (wlen >= cmd->len2 && + strncmp(mrdb->words[1], cmd->cmd2, wlen) == 0) { + break; /* word #1 and #2 */ + } + } + } + + /* divide remain parameters */ + if (cmd->cmd1 && cmd->div) { + p = mrdb->words[--mrdb->wcnt]; + for ( ; mrdb->wcntwcnt++) { + mrdb->words[mrdb->wcnt] = pick_out_word(mrb, &p); + if (!mrdb->words[mrdb->wcnt]) { + break; + } + } + } + + return cmd->cmd1 ? cmd : NULL; +} + +static void +print_info_stopped_break(mrb_state *mrb, mrdb_state *mrdb) +{ + mrb_debug_breakpoint bp; + int32_t ret; + uint16_t lineno; + const char *file; + const char *method_name; + const char *class_name; + + ret = mrb_debug_get_break(mrb, mrdb->dbg, mrdb->dbg->stopped_bpno, &bp); + if(ret == 0) { + switch(bp.type) { + case MRB_DEBUG_BPTYPE_LINE: + file = bp.point.linepoint.file; + lineno = bp.point.linepoint.lineno; + printf("Breakpoint %d, at %s:%d\n", bp.bpno, file, lineno); + break; + case MRB_DEBUG_BPTYPE_METHOD: + method_name = bp.point.methodpoint.method_name; + class_name = bp.point.methodpoint.class_name; + if(class_name == NULL) { + printf("Breakpoint %d, %s\n", bp.bpno, method_name); + } + else { + printf("Breakpoint %d, %s:%s\n", bp.bpno, class_name, method_name); + } + if(mrdb->dbg->isCfunc) { + printf("Stopped before calling the C function.\n"); + } + break; + default: + break; + } + } +} + +static void +print_info_stopped_step_next(mrb_state *mrb, mrdb_state *mrdb) +{ + const char* file = mrdb->dbg->prvfile; + uint16_t lineno = mrdb->dbg->prvline; + printf("%s:%d\n", file, lineno); +} + +static void +print_info_stopped_code(mrb_state *mrb, mrdb_state *mrdb) +{ + char* file = mrb_debug_get_source(mrb, mrdb, mrdb->srcpath, mrdb->dbg->prvfile); + uint16_t lineno = mrdb->dbg->prvline; + if(file != NULL) { + mrb_debug_list(mrb, mrdb->dbg, file, lineno, lineno); + mrb_free(mrb, file); + } +} + +static void +print_info_stopped(mrb_state *mrb, mrdb_state *mrdb) +{ + switch(mrdb->dbg->bm) { + case BRK_BREAK: + print_info_stopped_break(mrb, mrdb); + print_info_stopped_code(mrb, mrdb); + break; + case BRK_STEP: + case BRK_NEXT: + print_info_stopped_step_next(mrb, mrdb); + print_info_stopped_code(mrb, mrdb); + break; + default: + break; + } +} + +static debug_command* +get_and_parse_command(mrb_state *mrb, mrdb_state *mrdb) +{ + debug_command *cmd = NULL; + char *p; + int i; + + while (!cmd) { + for (p=NULL; !p || *p=='\0'; ) { + printf("(%s:%d) ", mrdb->dbg->prvfile, mrdb->dbg->prvline); + p = get_command(mrb, mrdb); + } + + cmd = parse_command(mrb, mrdb, p); +#ifdef _DBG_MRDB_PARSER_ + for (i=0; iwcnt; i++) { + printf("%d: %s\n", i, mrdb->words[i]); + } +#endif + if (!cmd) { + printf("invalid command ("); + for (i=0; iwcnt; i++) { + if (i>0) { + printf(" "); + } + printf("%s", mrdb->words[i]); + } + puts(")"); + } + } + return cmd; +} + +static const int32_t +check_method_breakpoint(mrb_state *mrb, mrb_irep *irep, mrb_code *pc, mrb_value *regs) +{ + struct RClass* c; + mrb_sym sym; + int32_t bpno; + mrb_bool isCfunc; + + mrb_debug_context *dbg = mrb_debug_context_get(mrb); + + isCfunc = FALSE; + bpno = dbg->method_bpno; + dbg->method_bpno = 0; + + switch(GET_OPCODE(*pc)) { + case OP_SEND: + case OP_SENDB: + c = mrb_class(mrb, regs[GETARG_A(*pc)]); + sym = irep->syms[GETARG_B(*pc)]; + break; + case OP_SUPER: + c = mrb->c->ci->target_class->super; + sym = mrb->c->ci->mid; + break; + default: + sym = 0; + break; + } + if(sym != 0) { + dbg->method_bpno = mrb_debug_check_breakpoint_method(mrb, dbg, c, sym, &isCfunc); + if(isCfunc) { + bpno = dbg->method_bpno; + dbg->method_bpno = 0; + } + } + dbg->isCfunc = isCfunc; + return bpno; +} + +static void +mrb_code_fetch_hook(mrb_state *mrb, mrb_irep *irep, mrb_code *pc, mrb_value *regs) +{ + const char *file; + int32_t line; + int32_t bpno; + + mrb_debug_context *dbg = mrb_debug_context_get(mrb); + + mrb_assert(dbg); + + dbg->irep = irep; + dbg->pc = pc; + dbg->regs = regs; + + if(dbg->xphase == DBG_PHASE_RESTART) { + dbg->root_irep = irep; + dbg->prvfile = NULL; + dbg->prvline = 0; + dbg->xm = DBG_RUN; + dbg->xphase = DBG_PHASE_RUNNING; + } + + file = mrb_debug_get_filename(irep, (uint32_t)(pc - irep->iseq)); + line = mrb_debug_get_line(irep, (uint32_t)(pc - irep->iseq)); + + switch (dbg->xm) { + case DBG_STEP: + case DBG_NEXT: // temporary + if (dbg->prvfile == file && dbg->prvline == line) { + return; + } + dbg->method_bpno = 0; + dbg->bm = BRK_STEP; + break; + + case DBG_RUN: + bpno = check_method_breakpoint(mrb, irep, pc, regs); + if (bpno > 0) { + dbg->stopped_bpno = bpno; + dbg->bm = BRK_BREAK; + break; + } + if (dbg->prvfile != file || dbg->prvline != line) { + bpno = mrb_debug_check_breakpoint_line(mrb, dbg, file, line); + if (bpno > 0) { + dbg->stopped_bpno = bpno; + dbg->bm = BRK_BREAK; + break; + } + } + dbg->prvfile = file; + dbg->prvline = line; + return; + case DBG_INIT: + dbg->root_irep = irep; + dbg->bm = BRK_INIT; + if (!file || line < 0) { + puts("Cannot get debugging information."); + } + break; + + default: + return; + } + + dbg->prvfile = file; + dbg->prvline = line; + + if(dbg->bm == BRK_BREAK && --dbg->ccnt > 0) { + return; + } + dbg->break_hook(mrb, dbg); + + dbg->xphase = DBG_PHASE_RUNNING; +} + +static mrdb_exemode +mrb_debug_break_hook(mrb_state *mrb, mrb_debug_context *dbg) +{ + debug_command *cmd; + dbgcmd_state st = DBGST_CONTINUE; + mrdb_state *mrdb = mrdb_state_get(mrb); + + print_info_stopped(mrb, mrdb); + + while (1) { + cmd = get_and_parse_command(mrb, mrdb); + mrb_assert(cmd); + + st = cmd->func(mrb, mrdb); + + if( (st == DBGST_CONTINUE) || (st == DBGST_RESTART) ) break; + } + return dbg->xm; +} + +int +main(int argc, char **argv) +{ + mrb_state *mrb = mrb_open(); + int n = -1; + struct _args args; + mrb_value v; + mrdb_state *mrdb; + mrdb_state *mrdb_backup; + mrb_debug_context* dbg_backup; + debug_command *cmd; + + if (mrb == NULL) { + fputs("Invalid mrb_state, exiting mruby\n", stderr); + return EXIT_FAILURE; + } + + l_restart: + + /* parse command parameters */ + n = parse_args(mrb, argc, argv, &args); + if (n == EXIT_FAILURE || args.rfp == NULL) { + cleanup(mrb, &args); + usage(argv[0]); + return n; + } + + /* initialize debugger information */ + mrdb = mrdb_state_get(mrb); + mrb_assert(mrdb && mrdb->dbg); + mrdb->srcpath = args.srcpath; + + if(mrdb->dbg->xm == DBG_QUIT) { + mrdb->dbg->xphase = DBG_PHASE_RESTART; + } + else { + mrdb->dbg->xphase = DBG_PHASE_BEFORE_RUN; + } + mrdb->dbg->xm = DBG_INIT; + mrdb->dbg->ccnt = 1; + + /* setup hook functions */ + mrb->code_fetch_hook = mrb_code_fetch_hook; + mrdb->dbg->break_hook = mrb_debug_break_hook; + + if (args.mrbfile) { /* .mrb */ + v = mrb_load_irep_file(mrb, args.rfp); + } + else { /* .rb */ + mrbc_context *cc = mrbc_context_new(mrb); + mrbc_filename(mrb, cc, args.fname); + v = mrb_load_file_cxt(mrb, args.rfp, cc); + mrbc_context_free(mrb, cc); + } + if (mrdb->dbg->xm == DBG_QUIT && !mrb_undef_p(v) && mrb->exc) { + const char *classname = mrb_obj_classname(mrb, mrb_obj_value(mrb->exc)); + if (!strcmp(classname, "DebuggerExit")) { + cleanup(mrb, &args); + return 0; + } + if (!strcmp(classname, "DebuggerRestart")) { + mrdb_backup = mrdb_state_get(mrb); + dbg_backup = mrb_debug_context_get(mrb); + + mrdb_state_set(NULL); + mrb_debug_context_set(NULL); + + cleanup(mrb, &args); + mrb = mrb_open(); + + mrdb_state_set(mrdb_backup); + mrb_debug_context_set(dbg_backup); + + goto l_restart; + } + } + puts("mruby application exited."); + mrdb->dbg->xphase = DBG_PHASE_AFTER_RUN; + if (!mrb_undef_p(v)) { + if (mrb->exc) { + mrb_print_error(mrb); + } + else { + printf(" => "); + mrb_p(mrb, v); + } + } + + mrdb->dbg->prvfile = "-"; + mrdb->dbg->prvline = 0; + + while (1) { + cmd = get_and_parse_command(mrb, mrdb); + mrb_assert(cmd); + + if (cmd->id == DBGCMD_QUIT) { + break; + } + + if( cmd->func(mrb, mrdb) == DBGST_RESTART ) goto l_restart; + } + + cleanup(mrb, &args); + + return 0; +} diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h new file mode 100755 index 0000000000..9e92ce8794 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h @@ -0,0 +1,163 @@ +/* +** mrdb.h - mruby debugger +** +*/ + +#ifndef MRDB_H +#define MRDB_H + +#include "mruby.h" + +#include "mrdbconf.h" + +#ifdef _MSC_VER +# define __func__ __FUNCTION__ +#endif + +#define MAX_COMMAND_WORD (16) + +typedef enum debug_command_id { + DBGCMD_RUN, + DBGCMD_CONTINUE, + DBGCMD_NEXT, + DBGCMD_STEP, + DBGCMD_BREAK, + DBGCMD_INFO_BREAK, + DBGCMD_WATCH, + DBGCMD_INFO_WATCH, + DBGCMD_ENABLE, + DBGCMD_DISABLE, + DBGCMD_DELETE, + DBGCMD_PRINT, + DBGCMD_DISPLAY, + DBGCMD_INFO_DISPLAY, + DBGCMD_DELETE_DISPLAY, + DBGCMD_EVAL, + DBGCMD_BACKTRACE, + DBGCMD_LIST, + DBGCMD_HELP, + DBGCMD_QUIT, + DBGCMD_UNKNOWN +} debug_command_id; + +typedef enum dbgcmd_state { + DBGST_CONTINUE, + DBGST_PROMPT, + DBGST_COMMAND_ERROR, + DBGST_MAX, + DBGST_RESTART +} dbgcmd_state; + +typedef enum mrdb_exemode { + DBG_INIT, + DBG_RUN, + DBG_STEP, + DBG_NEXT, + DBG_QUIT, +} mrdb_exemode; + +typedef enum mrdb_exephase { + DBG_PHASE_BEFORE_RUN, + DBG_PHASE_RUNNING, + DBG_PHASE_AFTER_RUN, + DBG_PHASE_RESTART, +} mrdb_exephase; + +typedef enum mrdb_brkmode { + BRK_INIT, + BRK_BREAK, + BRK_STEP, + BRK_NEXT, + BRK_QUIT, +} mrdb_brkmode; + +typedef enum { + MRB_DEBUG_BPTYPE_NONE, + MRB_DEBUG_BPTYPE_LINE, + MRB_DEBUG_BPTYPE_METHOD, +} mrb_debug_bptype; + +struct mrb_irep; +struct mrbc_context; +struct mrb_debug_context; + +typedef struct mrb_debug_linepoint { + const char *file; + uint16_t lineno; +} mrb_debug_linepoint; + +typedef struct mrb_debug_methodpoint { + const char *class_name; + const char *method_name; +} mrb_debug_methodpoint; + +typedef struct mrb_debug_breakpoint { + uint32_t bpno; + uint8_t enable; + mrb_debug_bptype type; + union point { + mrb_debug_linepoint linepoint; + mrb_debug_methodpoint methodpoint; + } point; +} mrb_debug_breakpoint; + +typedef struct mrb_debug_context { + struct mrb_irep *root_irep; + struct mrb_irep *irep; + mrb_code *pc; + mrb_value *regs; + + const char *prvfile; + int32_t prvline; + + mrdb_exemode xm; + mrdb_exephase xphase; + mrdb_brkmode bm; + int16_t bmi; + + uint16_t ccnt; + uint16_t scnt; + + mrb_debug_breakpoint bp[MAX_BREAKPOINT]; + uint32_t bpnum; + int32_t next_bpno; + int32_t method_bpno; + int32_t stopped_bpno; + mrb_bool isCfunc; + + mrdb_exemode (*break_hook)(mrb_state *mrb, struct mrb_debug_context *dbg); + +} mrb_debug_context; + +typedef struct mrdb_state { + char *command; + uint8_t wcnt; + uint8_t pi; + char *words[MAX_COMMAND_WORD]; + const char *srcpath; + uint32_t print_no; + + mrb_debug_context *dbg; +} mrdb_state; + +typedef dbgcmd_state (*debug_command_func)(mrb_state*, mrdb_state*); + +/* cmdrun.c */ +dbgcmd_state dbgcmd_run(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_continue(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_step(mrb_state*, mrdb_state*); +/* cmdbreak.c */ +dbgcmd_state dbgcmd_break(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_info_break(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_delete(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_enable(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_disable(mrb_state*, mrdb_state*); +/* cmdprint.c */ +dbgcmd_state dbgcmd_print(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_eval(mrb_state*, mrdb_state*); +/* cmdmisc.c */ +dbgcmd_state dbgcmd_list(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_help(mrb_state*, mrdb_state*); +dbgcmd_state dbgcmd_quit(mrb_state*, mrdb_state*); + +#endif diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h new file mode 100755 index 0000000000..f17f9c57d6 --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h @@ -0,0 +1,16 @@ +/* +** mrdbconf.h - mruby debugger configuration +** +*/ + +#ifndef MRDBCONF_H +#define MRDBCONF_H + +/* configuration options: */ +/* maximum size for command buffer */ +#define MAX_COMMAND_LINE 1024 + +/* maximum number of setable breakpoint */ +#define MAX_BREAKPOINT 5 + +#endif diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h new file mode 100755 index 0000000000..c7812b0d6d --- /dev/null +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h @@ -0,0 +1,20 @@ +/* +** mrdberror.h - mruby debugger error code +** +*/ + +#ifndef MRDBERROR_H +#define MRDBERROR_H + +#define MRB_DEBUG_OK (0) +#define MRB_DEBUG_NOBUF (-1) +#define MRB_DEBUG_INVALID_ARGUMENT (-2) + +#define MRB_DEBUG_BREAK_INVALID_LINENO (-11) +#define MRB_DEBUG_BREAK_INVALID_FILE (-12) +#define MRB_DEBUG_BREAK_INVALID_NO (-13) +#define MRB_DEBUG_BREAK_NUM_OVER (-14) +#define MRB_DEBUG_BREAK_NO_OVER (-15) + +#endif +