rgdb 0.0.1.245
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +676 -0
- data/ChangeLog +5 -0
- data/README +43 -0
- data/Rakefile +24 -0
- data/bin/rgdb +46 -0
- data/lib/gdb.rb +413 -0
- data/lib/rgdb.rb +75 -0
- data/lib/rgdb/version.rb +27 -0
- metadata +70 -0
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= rgdb - GDB inside IRB
|
2
|
+
|
3
|
+
== VERSION
|
4
|
+
|
5
|
+
This documentation refers to rgdb version 0.0.1
|
6
|
+
|
7
|
+
|
8
|
+
== DESCRIPTION
|
9
|
+
|
10
|
+
Runs Jamis Buck's GDB wrapper inside an IRB session. Inspired by Jonathan
|
11
|
+
Leighton's rgdb. But contrary to his solution, this one runs the IRB session
|
12
|
+
in the context of the GDB::Ruby object.
|
13
|
+
|
14
|
+
See
|
15
|
+
* http://weblog.jamisbuck.org/2006/9/25/gdb-wrapper-for-ruby
|
16
|
+
* http://dev.turnipspatch.com:9013/trac/browser/rgdb/trunk?rev=31
|
17
|
+
|
18
|
+
|
19
|
+
== AUTHORS
|
20
|
+
|
21
|
+
* Jens Wille <mailto:jens.wille@uni-koeln.de>
|
22
|
+
|
23
|
+
|
24
|
+
== LICENSE AND COPYRIGHT
|
25
|
+
|
26
|
+
Copyright (C) 2008 University of Cologne,
|
27
|
+
Albertus-Magnus-Platz, 50932 Cologne, Germany
|
28
|
+
|
29
|
+
rgdb is free software: you can redistribute it and/or modify it under the terms
|
30
|
+
of the GNU General Public License as published by the Free Software Foundation,
|
31
|
+
either version 3 of the License, or (at your option) any later version.
|
32
|
+
|
33
|
+
rgdb is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
34
|
+
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
35
|
+
PURPOSE. See the GNU General Public License for more details.
|
36
|
+
|
37
|
+
You should have received a copy of the GNU General Public License along with
|
38
|
+
rgdb. If not, see <http://www.gnu.org/licenses/>.
|
39
|
+
|
40
|
+
=== GDB
|
41
|
+
|
42
|
+
The GDB library is copyright (C) 2006 Jamis Buck <mailto:jamis@37signals.com>,
|
43
|
+
released in the public domain.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require %q{lib/rgdb/version}
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'hen'
|
5
|
+
|
6
|
+
Hen.lay! {{
|
7
|
+
:rubyforge => {
|
8
|
+
:project => %q{prometheus},
|
9
|
+
:package => %q{rgdb}
|
10
|
+
},
|
11
|
+
|
12
|
+
:gem => {
|
13
|
+
:version => RGDB::VERSION,
|
14
|
+
:summary => %q{GDB inside IRB},
|
15
|
+
:files => FileList['lib/**/*.rb', 'bin/*'].to_a,
|
16
|
+
:extra_files => FileList['[A-Z]*'].to_a,
|
17
|
+
:dependencies => %w[]
|
18
|
+
}
|
19
|
+
}}
|
20
|
+
rescue LoadError
|
21
|
+
abort "Please install the 'hen' gem first."
|
22
|
+
end
|
23
|
+
|
24
|
+
### Place your custom Rake tasks here.
|
data/bin/rgdb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
###############################################################################
|
5
|
+
# #
|
6
|
+
# rgdb -- GDB inside IRB #
|
7
|
+
# #
|
8
|
+
# Copyright (C) 2008 University of Cologne, #
|
9
|
+
# Albertus-Magnus-Platz, #
|
10
|
+
# 50932 Cologne, Germany #
|
11
|
+
# #
|
12
|
+
# Authors: #
|
13
|
+
# Jens Wille <jens.wille@uni-koeln.de> #
|
14
|
+
# #
|
15
|
+
# rgdb is free software; you can redistribute it and/or modify it under the #
|
16
|
+
# terms of the GNU General Public License as published by the Free Software #
|
17
|
+
# Foundation; either version 3 of the License, or (at your option) any later #
|
18
|
+
# version. #
|
19
|
+
# #
|
20
|
+
# rgdb is distributed in the hope that it will be useful, but WITHOUT ANY #
|
21
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
22
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
|
23
|
+
# details. #
|
24
|
+
# #
|
25
|
+
# You should have received a copy of the GNU General Public License along #
|
26
|
+
# with rgdb. If not, see <http://www.gnu.org/licenses/>. #
|
27
|
+
# #
|
28
|
+
###############################################################################
|
29
|
+
#++
|
30
|
+
|
31
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
32
|
+
|
33
|
+
require 'rgdb'
|
34
|
+
|
35
|
+
USAGE = "Usage: #{$0} [-h|--help] <pid>"
|
36
|
+
|
37
|
+
case arg = ARGV.shift
|
38
|
+
when '-h', '--help'
|
39
|
+
puts USAGE
|
40
|
+
when '--version'
|
41
|
+
puts "rgdb v#{RGDB::VERSION}"
|
42
|
+
when /\A\d+\z/
|
43
|
+
RGDB.start(arg, __FILE__)
|
44
|
+
else
|
45
|
+
abort USAGE
|
46
|
+
end
|
data/lib/gdb.rb
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
# gdb.rb -- a simple wrapper around the GDB console to make inspecting
|
2
|
+
# ruby processes less painful.
|
3
|
+
#
|
4
|
+
# Author: Jamis Buck <jamis@37signals.com>
|
5
|
+
#
|
6
|
+
# This library is in the public domain. You are free to use it, modify
|
7
|
+
# it, redistribute it, or ignore it, as you wish, with absolutely no
|
8
|
+
# restrictions.
|
9
|
+
#
|
10
|
+
# CAVEATS:
|
11
|
+
# * The current incarnation is not very error tolerant. If you run
|
12
|
+
# into problems, try running it with the $gdb_version variable set
|
13
|
+
# to true.
|
14
|
+
# * Sometimes, the current implementation will kill the process it
|
15
|
+
# attaches to. I suspect it has something to do with garbage
|
16
|
+
# collection kicking in... I leave it as an exercise for the reader.
|
17
|
+
#
|
18
|
+
# See the bottom of this file for a handful of unit tests.
|
19
|
+
|
20
|
+
require 'rbconfig'
|
21
|
+
|
22
|
+
class Integer
|
23
|
+
# Monkey patch the Integer class to make it easier to compute
|
24
|
+
# word offsets. "5.ints" gives you the size (in bytes) of 5
|
25
|
+
# integers (words) on the current platform.
|
26
|
+
def ints
|
27
|
+
self * 1.size
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# The GDB module provides a "raw", low-level, language-independent
|
32
|
+
# interface to GDB (GDB::Interface) as well as a higher-level
|
33
|
+
# interface specifically for Ruby programs (GDB::Ruby). Theoretically,
|
34
|
+
# GDB::Interface could be extended to support other languages
|
35
|
+
# than Ruby.
|
36
|
+
module GDB
|
37
|
+
# This module is used to extend objects (like Strings) that originate
|
38
|
+
# in the remote instance. This lets us reuse them easily in subsequent
|
39
|
+
# calls to the remote instance.
|
40
|
+
module RemoteObject
|
41
|
+
attr_accessor :address
|
42
|
+
end
|
43
|
+
|
44
|
+
# The basic interface for wrapping GDB. You could concievably use this
|
45
|
+
# to interace with arbitrary remote processes, though it is best used
|
46
|
+
# by extending it to add platform-specific functionality.
|
47
|
+
class Interface
|
48
|
+
# Creates a new GDB::Interface that connects to process-id and
|
49
|
+
# executable.
|
50
|
+
def initialize(executable, pid)
|
51
|
+
@gdb = IO.popen("gdb -q #{executable} #{pid} 2>&1", "r+")
|
52
|
+
read_to_prompt
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the (32-bit) integer at the given address
|
56
|
+
def int_at(address)
|
57
|
+
command("x/1dw #{address}").first.split(/:/).last.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the string located at the given address
|
61
|
+
def string_at(address)
|
62
|
+
command("x/1s *#{address}").first.match(/"(.*)"/)[1]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the (8-bit) double precision floating point value at
|
66
|
+
# the given address.
|
67
|
+
def double_at(address)
|
68
|
+
command("x/1fg #{address}").first.split(/:/).last.to_f
|
69
|
+
end
|
70
|
+
|
71
|
+
# Executes the given command and returns the output as an array
|
72
|
+
# of lines. The command must be in GDB's syntax. If $gdb_verbose
|
73
|
+
# is true, this will print the command to STDOUT as well.
|
74
|
+
def command(cmd)
|
75
|
+
puts "(gdb) #{cmd}" if $gdb_verbose
|
76
|
+
@gdb.puts(cmd.strip)
|
77
|
+
read_to_prompt
|
78
|
+
end
|
79
|
+
|
80
|
+
# Call a C function. The +function+ parameter must include any
|
81
|
+
# parameters, all of which must be in valid C syntax. +opts+
|
82
|
+
# should include a <code>:return</code> key, which indicates
|
83
|
+
# what the expected return type of the function is:
|
84
|
+
#
|
85
|
+
# * <code>:int</code>
|
86
|
+
# * <code>:string</code>
|
87
|
+
# * <code>:void</code>
|
88
|
+
#
|
89
|
+
# Currently, the default is <code>:void</code>. The method
|
90
|
+
# returns a value of the expected type.
|
91
|
+
def call(function, opts={})
|
92
|
+
cast = case opts[:return]
|
93
|
+
when :int then "int"
|
94
|
+
when :string then "char*"
|
95
|
+
when nil, :void then "void"
|
96
|
+
else Kernel.raise "unsupported return type #{opts[:return].inspect}"
|
97
|
+
end
|
98
|
+
|
99
|
+
result = command("call (#{cast})#{function}")
|
100
|
+
|
101
|
+
case opts[:return]
|
102
|
+
when :int
|
103
|
+
if md = result.first.match(/= (-?\d+)/)
|
104
|
+
md[1].to_i
|
105
|
+
else
|
106
|
+
Kernel.raise "couldn't match integer result from #{result.inspect}"
|
107
|
+
end
|
108
|
+
when :string then result.first.match(/"(.*)"/)[1]
|
109
|
+
else nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Reads all lines emitted by GDB until a recognized prompt is encountered.
|
114
|
+
# Currently recognized prompts are:
|
115
|
+
#
|
116
|
+
# * "(gdb) "
|
117
|
+
# * " >"
|
118
|
+
#
|
119
|
+
# If your version of GDB uses a different kind of prompt, you should
|
120
|
+
# modify this method to include it.
|
121
|
+
#
|
122
|
+
# This method returns the lines read as an array. If $gdb_verbose is
|
123
|
+
# true, the lines will be echoed to STDOUT.
|
124
|
+
def read_to_prompt
|
125
|
+
lines = []
|
126
|
+
line = ""
|
127
|
+
while result = IO.select([@gdb])
|
128
|
+
next if result.empty?
|
129
|
+
c = @gdb.read(1)
|
130
|
+
break if c.nil?
|
131
|
+
line << c
|
132
|
+
break if line == "(gdb) " || line == " >"
|
133
|
+
if line[-1] == ?\n
|
134
|
+
lines << line
|
135
|
+
line = ""
|
136
|
+
end
|
137
|
+
end
|
138
|
+
puts lines.map { |l| "> #{l}" } if $gdb_verbose
|
139
|
+
lines
|
140
|
+
end
|
141
|
+
|
142
|
+
# A convenience method for executing GDB commands. Any unrecognized
|
143
|
+
# message is packaged up and invoked by GDB.
|
144
|
+
def method_missing(sym, *args)
|
145
|
+
cmd = sym.to_s
|
146
|
+
cmd << " #{args.join(' ')}" unless args.empty?
|
147
|
+
command(cmd)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# This is an extension of the Interface class that implements Ruby-specific
|
152
|
+
# functionality.
|
153
|
+
class Ruby < Interface
|
154
|
+
# A wrapper for a remote Object instance.
|
155
|
+
class Object
|
156
|
+
attr_reader :gdb
|
157
|
+
attr_reader :address
|
158
|
+
|
159
|
+
# Given a gdb instance and an address, determine what type of Object
|
160
|
+
# is at the given address, and return it.
|
161
|
+
def self.from_ptr(gdb, address)
|
162
|
+
case
|
163
|
+
when (address & 0x01) == 1 then address >> 1
|
164
|
+
when address == 0 then false
|
165
|
+
when address == 2 then true
|
166
|
+
when address == 4 then nil
|
167
|
+
when (address & 0xff) == 0x0e then gdb.id2name(address)
|
168
|
+
else
|
169
|
+
case (type = (gdb.int_at(address) & 0x3f))
|
170
|
+
when 2 then Object.new(gdb, address)
|
171
|
+
when 6 then gdb.double_at(address+2.ints)
|
172
|
+
when 7 then
|
173
|
+
string = gdb.string_at(address+3.ints)
|
174
|
+
string.extend(RemoteObject)
|
175
|
+
string.address = address
|
176
|
+
string
|
177
|
+
when 9 then Array.new(gdb, address)
|
178
|
+
when 11 then Hash.new(gdb, address)
|
179
|
+
else Object.new(gdb, address)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Given a value, return an integer representing the VALUE of the
|
185
|
+
# object on the remote instance.
|
186
|
+
def to_gdb_value(value)
|
187
|
+
return value.address if value.respond_to?(:address)
|
188
|
+
|
189
|
+
case value
|
190
|
+
when Fixnum then ((value << 1)+1).to_s
|
191
|
+
when false then "0"
|
192
|
+
when true then "2"
|
193
|
+
when nil then "4"
|
194
|
+
when String then gdb.call("rb_str_new2(#{value.inspect})", :return => :int).to_s
|
195
|
+
else Kernel.raise "not supported yet: #{value.class.name}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Creates a new remote Object reference for the given gdb instance
|
200
|
+
# and address.
|
201
|
+
def initialize(gdb, address)
|
202
|
+
@gdb = gdb
|
203
|
+
@address = address.to_i
|
204
|
+
end
|
205
|
+
|
206
|
+
# Invokes a method on the remote Object reference, returning the
|
207
|
+
# result as another Object.
|
208
|
+
def send(sym, *args)
|
209
|
+
command = "rb_funcall(#{address}, #{gdb.intern(sym.to_s)}, #{args.length}"
|
210
|
+
args = args.map { |arg| to_gdb_value(arg) }.join(", ")
|
211
|
+
command << ", #{args}" unless args.empty?
|
212
|
+
command << ")"
|
213
|
+
|
214
|
+
gdb.object_from(command)
|
215
|
+
end
|
216
|
+
|
217
|
+
# A convenience method for #send
|
218
|
+
def method_missing(sym, *args)
|
219
|
+
send(sym, *args)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# A representation of a remote Array instance.
|
224
|
+
class Array < Object
|
225
|
+
# Query the length of the remote Array instance
|
226
|
+
def length
|
227
|
+
@length ||= gdb.int_at(address+2.ints)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Return the object at the given index
|
231
|
+
def [](index)
|
232
|
+
gdb.object_from("rb_ary_entry(#{address}, #{index})")
|
233
|
+
end
|
234
|
+
|
235
|
+
# Iterate over each element of the remote array
|
236
|
+
def each
|
237
|
+
length.times { |idx| yield self[idx] }
|
238
|
+
end
|
239
|
+
|
240
|
+
# Convert the remote Array into a local (true) Array instance.
|
241
|
+
def to_ary
|
242
|
+
ary = []
|
243
|
+
each { |item| ary << item }
|
244
|
+
ary
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Wraps a remote Hash instance. All it really provides, though, is a
|
249
|
+
# way to build a local Hash instance that mirrors it.
|
250
|
+
class Hash < Object
|
251
|
+
# Return a ::Hash instance that has the same keys and values as
|
252
|
+
# the remote hash instance. Default values are not preserved.
|
253
|
+
def to_h
|
254
|
+
keys.to_ary.inject({}) do |hash, key|
|
255
|
+
hash[key] = self[key]
|
256
|
+
hash
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Create a new GDB::Ruby instance that attaches to the given
|
262
|
+
# process ID. By default, the same ruby interpreter is used
|
263
|
+
# as is running the GDB::Ruby script, but you can override
|
264
|
+
# that by using the second parameter.
|
265
|
+
def initialize(pid, ruby=nil)
|
266
|
+
ruby ||= File.join(Config::CONFIG["bindir"],
|
267
|
+
Config::CONFIG["RUBY_INSTALL_NAME"] +
|
268
|
+
Config::CONFIG["EXEEXT"])
|
269
|
+
@interns = {}
|
270
|
+
@names = {}
|
271
|
+
super(ruby, pid)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns the symbol for the given numberic ID, using the remote
|
275
|
+
# environment.
|
276
|
+
def id2name(id)
|
277
|
+
@names[id] ||= call("rb_id2name(#{id >> 8})", :return => :string).to_sym
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns the numeric ID on the remote host that represents the
|
281
|
+
# internalized string.
|
282
|
+
def intern(string)
|
283
|
+
@interns[string] ||= call("rb_intern(#{string.inspect})", :return => :int)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Executes the given command (C API) and returns the result as a wrapped
|
287
|
+
# GDB::Ruby::Object.
|
288
|
+
def object_from(cmd)
|
289
|
+
Object.from_ptr(self, call("#{cmd}", :return => :int))
|
290
|
+
end
|
291
|
+
|
292
|
+
# Evaluates the given string in the remote environment, returning the
|
293
|
+
# resulting object. Exceptions are silently ignored.
|
294
|
+
def eval(string)
|
295
|
+
object_from("rb_eval_string_protect(#{string.inspect}, (int*)0)")
|
296
|
+
end
|
297
|
+
|
298
|
+
# Returns a hash of the local variables that are active in the remote
|
299
|
+
# process, at it's current scope.
|
300
|
+
def local_variables
|
301
|
+
eval("local_variables").to_ary.inject({}) do |hash, name|
|
302
|
+
hash[name] = eval(name)
|
303
|
+
hash
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns an array of the backtrace of the remote process.
|
308
|
+
def backtrace
|
309
|
+
object_from("backtrace(-1)").to_ary
|
310
|
+
end
|
311
|
+
|
312
|
+
# Raises an exception in the remote process. The +exc+ parameter
|
313
|
+
# must be one of the core exception classes, like Exception,
|
314
|
+
# RuntimeError, and so forth.
|
315
|
+
def raise(exc, message)
|
316
|
+
call "rb_raise((int)rb_e#{exc}, #{message.inspect})"
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns a hash of the classes known to the remote instance,
|
320
|
+
# with the number of instances of each of the classes.
|
321
|
+
def object_space
|
322
|
+
eval("h={}; ObjectSpace.each_object { |obj| h[obj.class.name] ||= 0; h[obj.class.name] += 1 }; h").to_h
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
if __FILE__ == $0
|
328
|
+
require 'test/unit'
|
329
|
+
|
330
|
+
SENTINEL = "/tmp/gdb-ruby-test.pid"
|
331
|
+
FLAG_FILE = "/tmp/gdb-ruby-test.flag"
|
332
|
+
|
333
|
+
SCRIPT = <<EOS
|
334
|
+
class ZxyVlqrt; end
|
335
|
+
glooble = ZxyVlqrt.new
|
336
|
+
|
337
|
+
def a(n)
|
338
|
+
b(n, n+1)
|
339
|
+
end
|
340
|
+
|
341
|
+
def b(x, y)
|
342
|
+
z = x + y
|
343
|
+
File.open("#{SENTINEL}", "w") { |f| f.write($$) }
|
344
|
+
loop { sleep 1 }
|
345
|
+
end
|
346
|
+
|
347
|
+
begin
|
348
|
+
a(5)
|
349
|
+
rescue RuntimeError => e
|
350
|
+
File.open("#{FLAG_FILE}", "w") { |f| f.write(e.message) }
|
351
|
+
end
|
352
|
+
EOS
|
353
|
+
|
354
|
+
SCRIPT_NAME = "/tmp/gdb-ruby-test.rb"
|
355
|
+
|
356
|
+
class GDBRubyTest < Test::Unit::TestCase
|
357
|
+
def setup
|
358
|
+
File.open(SCRIPT_NAME, "w") { |f| f.write(SCRIPT) }
|
359
|
+
start_script
|
360
|
+
@gdb = GDB::Ruby.new(@pid)
|
361
|
+
end
|
362
|
+
|
363
|
+
def teardown
|
364
|
+
@gdb.quit
|
365
|
+
kill_script
|
366
|
+
File.delete(FLAG_FILE) rescue nil
|
367
|
+
File.delete(SENTINEL) rescue nil
|
368
|
+
File.delete(SCRIPT_NAME) rescue nil
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_local_variables
|
372
|
+
expect = {"x" => 5, "y" => 6, "z" => 11}
|
373
|
+
assert_equal(expect, @gdb.local_variables)
|
374
|
+
end
|
375
|
+
|
376
|
+
def test_backtrace
|
377
|
+
expect = ["#{SCRIPT_NAME}:11:in `sleep'",
|
378
|
+
"#{SCRIPT_NAME}:11:in `b'",
|
379
|
+
"#{SCRIPT_NAME}:11:in `b'",
|
380
|
+
"#{SCRIPT_NAME}:5:in `a'",
|
381
|
+
"#{SCRIPT_NAME}:15"]
|
382
|
+
|
383
|
+
assert_equal expect, @gdb.backtrace
|
384
|
+
end
|
385
|
+
|
386
|
+
def test_raise
|
387
|
+
assert !File.exist?(FLAG_FILE)
|
388
|
+
@gdb.raise RuntimeError, "going boom"
|
389
|
+
assert File.exist?(FLAG_FILE)
|
390
|
+
assert_equal "going boom", File.read(FLAG_FILE)
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_object_space
|
394
|
+
space = @gdb.object_space
|
395
|
+
assert_equal 1, space["ZxyVlqrt"]
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
|
400
|
+
def start_script
|
401
|
+
if (@pid = fork)
|
402
|
+
sleep 0.1 while !File.exist?(SENTINEL)
|
403
|
+
return
|
404
|
+
end
|
405
|
+
|
406
|
+
exec "ruby", SCRIPT_NAME
|
407
|
+
end
|
408
|
+
|
409
|
+
def kill_script
|
410
|
+
Process.kill("TERM", @pid)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|