Ptrace 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/Ptrace.rb ADDED
@@ -0,0 +1,571 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
3
+ # Ruby additions to Ptrace extension
4
+
5
+ require 'Ptrace_ext' # Load C extension wrapping ptrace(3)
6
+ require 'forwardable'
7
+
8
+ =begin rdoc
9
+ Note: Debugger is a class composed of singleton methods, defined in the C
10
+ extension module.
11
+ =end
12
+ module Ptrace
13
+
14
+ # -----------------------------------------------------------------------
15
+ =begin rdoc
16
+ =end
17
+ class Error < RuntimeError
18
+ end
19
+
20
+ =begin rdoc
21
+ "No such process" returned in errno.
22
+ =end
23
+ class InvalidProcessError < Error
24
+ end
25
+
26
+ =begin rdoc
27
+ "Operation not permitted" returned in errno.
28
+ =end
29
+ class OperationNotPermittedError < Error
30
+ end
31
+
32
+ =begin rdoc
33
+ Requested command is not listed in PTRACE_COMMANDS and is considered
34
+ unsupported.
35
+ =end
36
+ class OperationNotSupportedError < Error
37
+ end
38
+
39
+ # -----------------------------------------------------------------------
40
+ =begin rdoc
41
+ Hash mapping ptrace symbols to command numbers. This is filled by the
42
+ Debugger based on the #defines in sys/ptrace.h.
43
+ =end
44
+ PTRACE_COMMANDS = Debugger.commands
45
+
46
+ # -----------------------------------------------------------------------
47
+ =begin rdoc
48
+ =end
49
+ # all ptrace options
50
+ class Options
51
+ def initialize(pid)
52
+ @pid = pid
53
+ end
54
+
55
+ def write
56
+ opts = []
57
+
58
+ begin
59
+ Debugger.send_data( Debugger.commands[:set_options], @pid, nil, opts )
60
+ rescue RuntimeError => e
61
+ case e.message
62
+ when 'PTRACE: Operation not permitted'
63
+ raise OperationNotPermittedError.new(e.message)
64
+ when 'PTRACE: No such process'
65
+ raise InvalidProcessError.new(e.message)
66
+ else
67
+ raise
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ # -----------------------------------------------------------------------
74
+ =begin rdoc
75
+ A region of Memory in the target process.
76
+
77
+ Usage:
78
+ mem = MemArea.new(MemArea::DATA, pid)
79
+ val = mem.peek(0x0804100)
80
+ mem.poke(0x0804100, 0x0)
81
+ =end
82
+ class MemArea
83
+
84
+ =begin rdoc
85
+ Target user area.
86
+ =end
87
+ MEM_USER = 1
88
+ =begin rdoc
89
+ Target text (code) area.
90
+ =end
91
+ MEM_TEXT = 2
92
+ =begin rdoc
93
+ Target data area.
94
+ =end
95
+ MEM_DATA = 3
96
+
97
+ =begin rdoc
98
+ Valid memory region types.
99
+ =end
100
+ TYPES = [MEM_USER, MEM_TEXT, MEM_DATA]
101
+
102
+ =begin rdoc
103
+ Type of memory region.
104
+ =end
105
+ attr_reader :mem_type
106
+ =begin rdoc
107
+ PID of process owning this memory region.
108
+ =end
109
+ attr_reader :pid
110
+
111
+ =begin rdoc
112
+ Create a new memory region of the specified type for process 'pid'.
113
+ =end
114
+ def initialize(type, pid)
115
+ @mem_type = type
116
+ @pid = pid
117
+ case type
118
+ when MEM_USER
119
+ @getter_sym = :peekusr
120
+ @setter_sym = :pokeusr
121
+ when MEM_TEXT
122
+ @getter_sym = :peektext
123
+ @setter_sym = :poketext
124
+ when MEM_DATA
125
+ @getter_sym = :peekdata
126
+ @setter_sym = :pokedata
127
+ end
128
+ end
129
+
130
+ =begin rdoc
131
+ Read a word of data from address 'addr' in memory region.
132
+ This can raise an OperationNotPermittedError if access is denied, or an
133
+ InvalidProcessError if the target process has exited.
134
+ =end
135
+ def peek(addr)
136
+ ptrace_send(:peek, @getter_sym, addr)
137
+ end
138
+
139
+ =begin rdoc
140
+ Write a word of data to address 'addr' in memory region.
141
+ This can raise an OperationNotPermittedError if access is denied, or an
142
+ InvalidProcessError if the target process has exited.
143
+ =end
144
+ def poke(addr, value)
145
+ ptrace_send(:poke, @setter_sym, addr, value)
146
+ end
147
+
148
+ private
149
+
150
+ def ptrace_send( sym, cmd, addr, arg=nil )
151
+ begin
152
+ raise OperationNotSupportedError if (not PTRACE_COMMANDS.include? cmd)
153
+ args = [PTRACE_COMMANDS[cmd], @pid, addr]
154
+ args << arg if arg
155
+ Debugger.send( sym, *args )
156
+ rescue RuntimeError => e
157
+ case e.message
158
+ when 'PTRACE: Operation not permitted'
159
+ raise OperationNotPermittedError.new(e.message)
160
+ when 'PTRACE: No such process'
161
+ raise InvalidProcessError.new(e.message)
162
+ else
163
+ raise
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ # -----------------------------------------------------------------------
170
+ =begin rdoc
171
+ =end
172
+ class Signal
173
+ =begin rdoc
174
+ =end
175
+ # create signinfo
176
+ def initialize()
177
+ end
178
+
179
+ =begin rdoc
180
+ =end
181
+ # send a signal to the child
182
+ def send
183
+ # TODO: begin/rescue
184
+ #Debugger.signal
185
+ end
186
+
187
+ =begin rdoc
188
+ =end
189
+ # read a signal from the child. returns a Signal data struct.
190
+ def self.get(pid)
191
+ # TODO: begin/rescue
192
+ # send message
193
+ #Debugger.signal=
194
+ # Signal.new
195
+ end
196
+
197
+ end
198
+
199
+ # -----------------------------------------------------------------------
200
+ =begin rdoc
201
+ The CPU registers for the process. This acts as a Hash mapping register names
202
+ to contents. The Hash acts as a snapshot of the CPU state; the registers are
203
+ read from the process using read(), and written to the process using write().
204
+
205
+ Usage:
206
+ regs = RegSet.new(RegSet::GEN, pid)
207
+ regs.read
208
+ puts regs.inspect
209
+ regs['eax'] = 0x0
210
+ regs.write
211
+ =end
212
+ class RegSet
213
+ extend Forwardable
214
+ extend Enumerable
215
+
216
+ =begin rdoc
217
+ General register set (EAX and friends in x86).
218
+ =end
219
+ GEN = 0
220
+ =begin rdoc
221
+ Floating-point register set (ST(0) and friends in x86).
222
+ =end
223
+ FP = 1
224
+ =begin rdoc
225
+ Extended floating-point register set (fpx on Linux).
226
+ =end
227
+ EXT = 2
228
+ =begin rdoc
229
+ Valid register set types.
230
+ =end
231
+ TYPES = [GEN, FP, EXT]
232
+
233
+ =begin rdoc
234
+ Method names for read accessor, keyed by register type.
235
+ =end
236
+ GETTER_SYMS = [:regs, :fpregs, :fpxregs ]
237
+ =begin rdoc
238
+ Method names for write accessor, keyed by register type.
239
+ =end
240
+ SETTER_SYMS = [:regs=, :fpregs=, :fpxregs= ]
241
+
242
+ =begin rdoc
243
+ Type of this register set.
244
+ =end
245
+ attr_reader :reg_type
246
+ =begin rdoc
247
+ PID of process owning this register set.
248
+ =end
249
+ attr_reader :pid
250
+
251
+ =begin rdoc
252
+ Create a new register set of the specified type for process 'pid'.
253
+ =end
254
+ def initialize(type, pid)
255
+ @reg_type = type
256
+ @getter = GETTER_SYMS[type]
257
+ @setter = SETTER_SYMS[type]
258
+ @pid = pid
259
+ @regs = {}
260
+ end
261
+
262
+ =begin rdoc
263
+ Read the current state of the CPU registers from the process. This fills the
264
+ contents of the RegSet Hash, and returns the Hash.
265
+ This can raise an OperationNotPermittedError if access is denied, or an
266
+ InvalidProcessError if the target process has exited.
267
+ =end
268
+ def read
269
+ @regs = ptrace_send(@getter)
270
+ end
271
+
272
+ =begin rdoc
273
+ Write the contents of the RegSet Hash to the process CPU registers.
274
+ This can raise an OperationNotPermittedError if access is denied, or an
275
+ InvalidProcessError if the target process has exited.
276
+ =end
277
+ def write
278
+ ptrace_send(@setter, @regs)
279
+ end
280
+
281
+ def_delegators :@regs, :[], :[]=, :clear, :each, :delete, :each_key,
282
+ :each_pair, :each_value, :empty?, :fetch, :has_key?,
283
+ :has_value?, :include?, :index, :indexes, :invert,
284
+ :key?, :keys, :length?, :member, :reject, :replace,
285
+ :shift, :size, :sort, :store, :to_a, :to_s, :update,
286
+ :value?, :values
287
+
288
+ private
289
+
290
+ def ptrace_send( sym, arg=nil )
291
+ begin
292
+ args = [@pid]
293
+ args << arg if arg
294
+ Debugger.send( sym, *args )
295
+ rescue RuntimeError => e
296
+ case e.message
297
+ when 'PTRACE: Operation not permitted'
298
+ raise OperationNotPermittedError.new(e.message)
299
+ when 'PTRACE: No such process'
300
+ raise InvalidProcessError.new(e.message)
301
+ else
302
+ raise
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ # -----------------------------------------------------------------------
309
+ =begin rdoc
310
+ A target process managed by ptrace.
311
+
312
+ Usage:
313
+ tgt = Target.attach(pid)
314
+ loop do
315
+ begin
316
+ tgt.step
317
+ puts tgt.regs.read.inspect
318
+ rescue Ptrace::InvalidProcessError
319
+ break
320
+ end
321
+ end
322
+
323
+ tgt = Target.launch(cmd)
324
+ loop do
325
+ begin
326
+ state = tgt.syscall_state
327
+ puts state.inspect
328
+ rescue Ptrace::InvalidProcessError
329
+ break
330
+ end
331
+ end
332
+ =end
333
+ class Target
334
+
335
+ =begin rdoc
336
+ PID of target process.
337
+ =end
338
+ attr_accessor :pid
339
+ =begin rdoc
340
+ General (CPU) registers for process.
341
+ =end
342
+ attr_accessor :regs
343
+ =begin rdoc
344
+ FPU registers for process.
345
+ =end
346
+ attr_accessor :fpregs
347
+ =begin rdoc
348
+ extended FPU registers for process.
349
+ =end
350
+ attr_accessor :fpregs
351
+ =begin rdoc
352
+ Text (code) segment for process.
353
+ =end
354
+ attr_accessor :text
355
+ =begin rdoc
356
+ Data segment for process.
357
+ =end
358
+ attr_accessor :data
359
+ =begin rdoc
360
+ Target 'user' (task) area.
361
+ =end
362
+ attr_accessor :user
363
+ =begin rdoc
364
+ Ptrace options.
365
+ =end
366
+ attr_accessor :options
367
+
368
+ =begin rdoc
369
+ Create a Ptrace::Target object for process 'pid'. The process is assumed to
370
+ have been launched or attached to by ptrace, e.g. using Target.launch or
371
+ Target.attach.
372
+ =end
373
+ def initialize(pid)
374
+ @pid = pid
375
+ @text = MemArea.new(MemArea::MEM_TEXT, pid)
376
+ @data = MemArea.new(MemArea::MEM_DATA, pid)
377
+ @user = MemArea.new(MemArea::MEM_USER, pid)
378
+ @regs = RegSet.new(RegSet::GEN, pid)
379
+ @fpregs = RegSet.new(RegSet::FP, pid)
380
+ @fpxregs = RegSet.new(RegSet::Ext, pid)
381
+ @options = Options.new(pid)
382
+ @valid = true
383
+ end
384
+
385
+ =begin rdoc
386
+ PT_ATTACH : Attach to running process 'pid' and return a Ptrace::Target object.
387
+ Raises an exception if the attach fails.
388
+ =end
389
+ def self.attach(pid)
390
+ tgt = Target.new(pid)
391
+ begin
392
+ Ptrace::Debugger.send_cmd( Ptrace::Debugger.commands[:attach], pid, nil)
393
+ Process.waitpid(pid)
394
+ rescue RuntimeError => e
395
+ case e.message
396
+ when 'PTRACE: Operation not permitted'
397
+ raise OperationNotPermittedError.new(e.message)
398
+ when 'PTRACE: No such process'
399
+ raise InvalidProcessError.new(e.message)
400
+ else
401
+ raise
402
+ end
403
+ end
404
+ return tgt
405
+ end
406
+
407
+ =begin rdoc
408
+ PT_TRACE_ME : Launch command 'cmd' and return a Ptrace::Target object for
409
+ controlling it.
410
+ Raises an exception if the command cannot be launched; returns nil if
411
+ the command cannot be traced.
412
+ =end
413
+ def self.launch(cmd)
414
+ pid = fork
415
+ if ! pid
416
+ begin
417
+ Ptrace::Debugger.send_cmd(Ptrace::Debugger.commands[:traceme], nil,
418
+ nil)
419
+ exec(cmd)
420
+ rescue RuntimeError => e
421
+ case e.message
422
+ when 'PTRACE: Operation not permitted'
423
+ raise OperationNotPermittedError.new(e.message)
424
+ when 'PTRACE: No such process'
425
+ raise InvalidProcessError.new(e.message)
426
+ else
427
+ raise
428
+ end
429
+ end
430
+
431
+ elsif pid == -1
432
+ return nil
433
+
434
+ else
435
+ Process.waitpid(pid)
436
+ tgt = Target.new(pid)
437
+ return tgt
438
+ end
439
+ end
440
+
441
+ =begin rdoc
442
+ =end
443
+ def signal(sig=nil)
444
+ # if sig, send
445
+ # else:
446
+ Signal.read(pid)
447
+ end
448
+
449
+ =begin rdoc
450
+ =end
451
+ def event_msg
452
+ # send signal
453
+ # For PTRACE_EVENT_EXIT this is the child's exit status. For PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK and PTRACE_EVENT_CLONE this is the PID of the new process.
454
+ # Debugger.event_msg
455
+ end
456
+
457
+ =begin rdoc
458
+ PT_KILL : Terminate the process.
459
+ Note: This makes the Ptrace::Target object invalid.
460
+ =end
461
+ def kill
462
+ ptrace_send( :kill )
463
+ Process.waitpid(@pid)
464
+ @valid = false
465
+ end
466
+
467
+ =begin rdoc
468
+ PT_DETACH : Detach from the process.
469
+ Note: This makes the Ptrace::Target object invalid.
470
+ =end
471
+ def detach
472
+ ptrace_send( :detach )
473
+ Process.waitpid(@pid)
474
+ @valid = false
475
+ end
476
+
477
+ =begin rdoc
478
+ PT_STEP : Step a single instruction.
479
+ =end
480
+ def step
481
+ ptrace_send( :singlestep )
482
+ Process.waitpid(@pid)
483
+ end
484
+
485
+ =begin rdoc
486
+ PT_SYSCALL : Execute until the start or end of the next system call.
487
+
488
+ Usage:
489
+ tgt.syscall
490
+ in_regs = tgt.regs.read
491
+ tgt.syscall
492
+ out_regs = tgt.regs.read
493
+ =end
494
+ def syscall
495
+ ptrace_send( :syscall )
496
+ Process.waitpid(@pid)
497
+ end
498
+
499
+ =begin rdoc
500
+ Wrapper for recording syscalls. This issues a PT_SYSCALL to stop the target at
501
+ the next syscall, records the 'in' register set, issues a PT_SYSCALL to stop the
502
+ target after the syscall returns, records the 'out' register set, and
503
+ returns a Hash { :in, :out } of the register sets. The target is stopped
504
+ on return from this syscall.
505
+ =end
506
+ def syscall_state
507
+ begin
508
+ state = {}
509
+ syscall
510
+ state[:in] = @regs.read
511
+ syscall
512
+ state[:out] = @regs.read
513
+ state
514
+ rescue InvalidProcessError
515
+ # Program exited without a syscall
516
+ return state
517
+ end
518
+ end
519
+
520
+ =begin rdoc
521
+ PT_CONTINUE: Continue execution of target.
522
+ =end
523
+ def cont
524
+ ptrace_send( :cont )
525
+ Process.waitpid(@pid)
526
+ end
527
+
528
+ =begin rdoc
529
+ PT_SYSEMU
530
+ =end
531
+ def sysemu
532
+ ptrace_send( :sysemu )
533
+ end
534
+
535
+ =begin rdoc
536
+ PT_SYSEMU_SINGLESTEP
537
+ =end
538
+ def sysemu_step
539
+ ptrace_send( :sysemu_singlestep )
540
+ end
541
+
542
+ =begin rdoc
543
+ =end
544
+ # misc
545
+ def options=
546
+ # TODO
547
+ # ptrace_send_data( :setoptions, )
548
+ end
549
+
550
+ private
551
+
552
+ def ptrace_send( cmd, arg=nil )
553
+ begin
554
+ raise OperationNotSupportedError if (not PTRACE_COMMANDS.include? cmd)
555
+ raise OperationNotPermittedError if (not @valid)
556
+
557
+ Debugger.send_cmd( PTRACE_COMMANDS[cmd], @pid, arg )
558
+ rescue RuntimeError => e
559
+ case e.message
560
+ when 'PTRACE: Operation not permitted'
561
+ raise OperationNotPermittedError.new(e.message)
562
+ when 'PTRACE: No such process'
563
+ raise InvalidProcessError.new(e.message)
564
+ else
565
+ raise
566
+ end
567
+ end
568
+ end
569
+
570
+ end
571
+ end