Class: GDB::GDB

Inherits:
Object
  • Object
show all
Defined in:
lib/gdb/gdb.rb

Overview

For launching a gdb process.

Returns:

  • (String)

    Returns what gdb displayed after executing this command.

Constant Summary collapse

SCRIPTS_PATH =

Absolute path to the python scripts.

File.join(__dir__, 'scripts').freeze

Instance Method Summary collapse

Constructor Details

#initialize(arguments, gdb: 'gdb') ⇒ GDB

To launch a gdb instance.

Examples:

gdb = GDB::GDB.new('-q -nh bash')
gdb = GDB::GDB.new('arm.elf', gdb: 'gdb-multiarch')

Parameters:

  • arguments (String)

    The command line arguments to pass to gdb. See examples.

  • gdb (String) (defaults to: 'gdb')

    Name of gdb.

Raises:

  • (Errno::ENOENT)

34
35
36
37
38
39
40
41
42
43
# File 'lib/gdb/gdb.rb', line 34

def initialize(arguments, gdb: 'gdb')
  gdb_bin = ::GDB::Util.which(gdb)
  raise Errno::ENOENT, gdb if gdb_bin.nil?

  arguments = "--command=#{File.join(SCRIPTS_PATH, 'gdbinit.py')} #{arguments}" # XXX
  @tube = spawn("#{gdb_bin} #{arguments}")
  pre = @tube.readuntil('GDBRuby:')
  @prompt = @tube.readuntil("\n").strip
  @tube.unget(pre + @tube.readuntil(@prompt))
end

Instance Method Details

#alive?Boolean Also known as: running?

Is the process running?

Actually judged by if #pid returns zero.

Returns:

  • (Boolean)

    True for process is running.


152
153
154
# File 'lib/gdb/gdb.rb', line 152

def alive?
  !pid.zero?
end

#break(point) ⇒ String Also known as: b

Set breakpoints.

This method does some magic, see examples.

Examples:

gdb = GDB::GDB.new('bash')
gdb.break('main')
#=> "Breakpoint 1 at 0x41eed0"
gdb.break(0x41eed0)
#=> "Note: breakpoint 1 also set at pc 0x41eed0.\r\nBreakpoint 2 at 0x41eed0"

Parameters:

  • point (Integer, String)

    If Integer is given, will be translated as set break point at address point, i.e. equivalent to break *<integer>. If String is given, equivalent to invoke execute(“break <point>”).

Returns:

  • (String)

    Returns what gdb displayed after executing this command.


85
86
87
88
89
90
# File 'lib/gdb/gdb.rb', line 85

def break(point)
  case point
  when Integer then execute("break *#{point}")
  when String then execute("break #{point}")
  end
end

#closevoid Also known as: quit

This method returns an undefined value.

Terminate the gdb process.


310
311
312
313
314
315
316
# File 'lib/gdb/gdb.rb', line 310

def close
  return if @tube.closed?

  @tube.close
  Process.wait(@gdb_pid)
  nil
end

#continueString

Note:

This method may block the IO if no breakpoints are properly set.

Execute continue command.

Returns:

  • (String)

    Returns what gdb displayed after executing this command.


173
174
175
176
# File 'lib/gdb/gdb.rb', line 173

def continue
  check_alive!
  execute('continue')
end

#execute(cmd) ⇒ String Also known as: exec

Execute a command in gdb.

Examples:

gdb = GDB::GDB.new('bash')
gdb.execute('b main')
#=> "Breakpoint 1 at 0x41eed0"
gdb.execute('run')
gdb.execute('print $rsi')
#=> "$1 = 0x7fffffffdef8"

Parameters:

  • cmd (String)

    Command to be executed.

Returns:

  • (String)

    Returns what gdb displayed after executing this command.


59
60
61
62
63
64
65
# File 'lib/gdb/gdb.rb', line 59

def execute(cmd)
  # clear tube if not in interactive mode
  @tube.clear unless interacting?

  @tube.puts(cmd)
  @tube.readuntil(@prompt).strip
end

#info(args = '') ⇒ String

Execute info command.

Examples:

gdb = GDB::GDB.new('spec/binaries/amd64.elf')
gdb.break('main')
gdb.run
puts gdb.info('proc stat')
# process 32537
# cmdline = '/home/gdb-ruby/spec/binaries/amd64.elf'
# cwd = '/home/gdb-ruby'
# exe = '/home/gdb-ruby/spec/binaries/amd64.elf'
gdb.close

Parameters:

  • args (String) (defaults to: '')

    Arguments to pass to info command.

Returns:

  • (String)

    Returns what gdb displayed after executing this command.


195
196
197
# File 'lib/gdb/gdb.rb', line 195

def info(args = '')
  execute("info #{args}")
end

#interactvoid

This method returns an undefined value.

Enter gdb interactive mode. GDB will be closed after interaction.


299
300
301
302
303
304
305
# File 'lib/gdb/gdb.rb', line 299

def interact
  return if interacting?

  @interacting = true
  $stdin.raw { @tube.interact(method(:output_hook)) }
  close
end

#pidInteger

Get the process's pid.

This method implemented by invoking python print(gdb.selected_inferior().pid).

Returns:

  • (Integer)

    The pid of the process. If the process is not running, zero is returned.


163
164
165
# File 'lib/gdb/gdb.rb', line 163

def pid
  @pid = python_p('gdb.selected_inferior().pid').to_i
end

#python_p(cmd) ⇒ String

To simplify the frequency call of python print(xxx).

Parameters:

  • cmd (String)

    python command.

Returns:

  • (String)

    Execution result.


291
292
293
# File 'lib/gdb/gdb.rb', line 291

def python_p(cmd)
  execute("python print(#{cmd})")
end

#read_memory(addr, num_elements, options = {}) {|io| ... } ⇒ Object+ Also known as: readm

Read current process's memory.

Examples:

gdb = GDB::GDB.new('spec/binaries/amd64.elf')
gdb.break('main')
gdb.run
gdb.read_memory('amd64.elf', 4)
#=> "\x7fELF"
# example of fetching argv
gdb = GDB::GDB.new('spec/binaries/amd64.elf')
gdb.break('main')
gdb.run('pusheen the cat')
gdb.read_memory(0x400000, 4)
#=> "\x7fELF"
argc = gdb.register(:rdi)
#=> 4
args = gdb.read_memory(gdb.register(:rsi), argc, as: :u64)
Array.new(3) do |i|
  gdb.read_memory(args[i + 1], 1) do |m|
    str = ''
    loop do
      c = m.read(1)
      break if c == "\x00"
      str << c
    end
    str
  end
end
#=> ["pusheen", "the", "cat"]

# or, use our build-in types of gem +memory_io+.
gdb.read_memory(args[1], 3, as: :c_str)
#=> ["pusheen", "the", "cat"]

Parameters:

  • addr (Integer, String)

    Address to start to read. addr can be a string like 'heap+0x10'. Supported variables are names in /proc/$pid/maps such as heap/libc/stack/ld.

  • num_elements (Integer)

    Number of elements to read. If num_elements equals to 1, an object read will be returned. Otherwise, an array with size num_elements will be returned.

  • [Symbol, (Hash)

    a customizable set of options

Yield Parameters:

  • io (IO)

    The IO object that points to addr, read from it.

Yield Returns:

  • (Object)

    Whatever you read from io.

Returns:

  • (Object, Array<Object>)

    Return types are decided by value of num_elements and option as.


257
258
259
260
261
# File 'lib/gdb/gdb.rb', line 257

def read_memory(addr, num_elements, options = {}, &block)
  check_alive! # this would set @pid
  options[:as] = block if block_given?
  MemoryIO.attach(@pid).read(addr, num_elements, **options)
end

#register(reg_name) ⇒ Integer Also known as: reg

TODO:

Handle when reg_name is not a general-purpose register.

Get current value of register

Parameters:

  • reg_name (String, Symbol)

Returns:

  • (Integer)

    Value of desired register.


125
126
127
128
# File 'lib/gdb/gdb.rb', line 125

def register(reg_name)
  check_alive!
  Integer(python_p("gdb.parse_and_eval('$#{reg_name}')").split.first)
end

#run(args = '') ⇒ String Also known as: r

Note:

If breakpoints are not set properly and cause gdb hangs, this method hangs as well.

Run the process.

Examples:

gdb = GDB::GDB.new('bash')
puts gdb.run('-c "echo 111"')
# Starting program: /bin/bash -c "echo 111"
# 111
# [Inferior 1 (process 3229) exited normally]
#=> nil

Parameters:

  • args (String) (defaults to: '')

    Arguments to pass to run command.

Returns:

  • (String)

    Returns what gdb displayed after executing this command.


111
112
113
# File 'lib/gdb/gdb.rb', line 111

def run(args = '')
  execute("run #{args}")
end

#text_baseInteger Also known as: code_base

Note:

This will also set a variable $text in gdb.

Get the process's text base.

Returns:

  • (Integer)

    The base address.


138
139
140
141
142
143
# File 'lib/gdb/gdb.rb', line 138

def text_base
  check_alive!
  base = Integer(execute('info proc stat').scan(/Start of text: (.*)/).flatten.first)
  execute("set $text = #{base}")
  base
end

#write_memory(addr, objects, options = {}, &block) ⇒ void Also known as: writem

This method returns an undefined value.

Write an object to process at specific address.

Parameters:

  • addr (Integer, String)

    Target address. addr can be a string like 'heap+0x10'. Supported variables are names in /proc/$pid/maps such as heap/libc/stack/ld.

  • objects (Objects, Array<Objects>)

    Objects to be written.

  • [Symbol, (Hash)

    a customizable set of options


277
278
279
280
281
# File 'lib/gdb/gdb.rb', line 277

def write_memory(addr, objects, options = {}, &block)
  check_alive! # this would set @pid
  options[:as] = block if block_given?
  MemoryIO.attach(@pid).write(addr, objects, **options)
end