_updatecode()
click to toggle source
def _updatecode
if @codeptr
addr = @codeptr
elsif @oldregs[@rs.register_pc] and @oldregs[@rs.register_pc] < @rs.pc and @oldregs[@rs.register_pc] + 8 >= @rs.pc
addr = @oldregs[@rs.register_pc]
else
addr = @rs.pc
end
@codeptr = addr
addrsz = @rs.register_size[@rs.register_pc]
addrfmt = "%0#{addrsz/4}X"
if not @rs.addr2module(addr) and @rs.shortname !~ /remote/
base = addr & ((1 << addrsz) - 0x1000)
@noelfsig ||= {}
if not @noelfsig[base] and base < ((1 << addrsz) - 0x1_0000)
self.statusline = " scanning for elf header at #{addrfmt % base}"
128.times {
@statusline = " scanning for elf header at #{addrfmt % base}"
if not @noelfsig[base] and @rs[base, Metasm::ELF::MAGIC.length] == Metasm::ELF::MAGIC
@rs.loadsyms(base, base.to_s(16))
break
else
@noelfsig[base] = true
end
base -= 0x1000
break if base < 0
}
self.statusline = nil
end
end
text = ''
text << Color[:border]
title = @rs.addrname(addr)
pre = [@console_width-100, 6].max
post = @console_width - (pre + title.length + 2)
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
seg = ''
seg = ('%04X' % @rs['cs']) << ':' if @rs.cpu.shortname =~ /ia32|x64/
cnt = @win_code_height
while (cnt -= 1) > 0
if @rs.symbols[addr]
text << (' ' << @rs.symbols[addr] << :) << Ansi::ClearLineAfter << "\n"
break if (cnt -= 1) <= 0
end
text << Color[:hilight] if addr == @rs.pc
text << seg
if @rs.shortname =~ /remote/ and @rs.realmode
text << (addrfmt % (addr - 16*@rs['cs']))
else
text << (addrfmt % addr)
end
di = @rs.di_at(addr)
di = nil if di and addr < @rs.pc and addr+di.bin_length > @rs.pc
len = (di ? di.bin_length : 1)
text << ' '
text << @rs.memory[addr, [len, 10].min].to_s.unpack('C*').map { |c| '%02X' % c }.join.ljust(22)
if di
text <<
if addr == @rs.pc
"*#{di.instruction}".ljust([@console_width-(addrsz/4+seg.length+24), 0].max)
else
" #{di.instruction}" << Ansi::ClearLineAfter
end
else
text << ' <unk>' << Ansi::ClearLineAfter
end
text << Color[:normal] if addr == @rs.pc
addr += len
text << "\n"
end
text
end
_updatedata()
click to toggle source
def _updatedata
addrsz = @rs.register_size[@rs.register_pc]
addrfmt = "%0#{addrsz/4}X"
@dataptr &= ((1 << addrsz) - 1)
addr = @dataptr
text = ''
text << Color[:border]
title = @rs.addrname(addr)
pre = [@console_width-100, 6].max
post = [@console_width - (pre + title.length + 2), 0].max
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
seg = ''
seg = ('%04X' % @rs['ds']) << ':' if @rs.cpu.shortname =~ /^ia32/
cnt = @win_data_height
while (cnt -= 1) > 0
raw = @rs.memory[addr, 16].to_s
text << seg << (addrfmt % addr) << ' '
case @datafmt
when 'db'; text << raw[0,8].unpack('C*').map { |c| '%02x ' % c }.join << ' ' <<
raw[8,8].to_s.unpack('C*').map { |c| '%02x ' % c }.join
when 'dw'; text << raw.unpack('S*').map { |c| '%04x ' % c }.join
when 'dd'; text << raw.unpack('L*').map { |c| '%08x ' % c }.join
end
text << ' ' << raw.unpack('C*').map { |c| (0x20..0x7e).include?(c) ? c : 0x2e }.pack('C*')
text << Ansi::ClearLineAfter << "\n"
addr += 16
end
text
end
_updateprompt()
click to toggle source
def _updateprompt
text = ''
text << Color[:border] << Ansi.hline(@console_width) << Color[:normal] << "\n"
@log_off = @promptlog.length - 2 if @log_off >= @promptlog.length
@log_off = 0 if @log_off < 0
len = @win_prpt_height - 2
len.times { |i|
i += @promptlog.length - @log_off - len
text << ((@promptlog[i] if i >= 0) || '')
text << Ansi::ClearLineAfter << "\n"
}
text << ':' << @promptbuf << Ansi::ClearLineAfter << "\n"
text << Color[:status] << statusline.chomp.ljust(@console_width) << Color[:normal]
end
_updateregs()
click to toggle source
def _updateregs
pvrsz = 0
words = @rs.register_list.map { |r|
rs = r.to_s.rjust(pvrsz)
pvrsz = rs.length
rv = @rs[r]
["#{rs}=%0#{@rs.register_size[r]/4}X " % rv,
(@oldregs[r] != rv)]
} + @rs.flag_list.map { |fl|
fv = @rs.get_flag(fl)
[fv ? fl.to_s.upcase : fl.to_s.downcase,
(@oldregs[fl] != fv)]
}
text = ' '
linelen = 1
owr = @win_reg_height
@win_reg_height = 1
words.each { |w, changed|
if linelen + w.length >= @console_width - 1
text << (' '*([@console_width-linelen, 0].max)) << "\n "
linelen = 1
@win_reg_height += 1
end
text << Color[:changed] if changed
text << w
text << Color[:normal] if changed
text << ' '
linelen += w.length+1
}
resize if owr != @win_reg_height
text << (' '*([@console_width-linelen, 0].max)) << "\n"
end
add_log(l)
click to toggle source
def add_log(l)
log l
puts l if not @running
update rescue puts l
end
cont(*a)
click to toggle source
def cont(*a)
self.statusline = ' target running...'
preupdate
@rs.continue_wait(*a)
updatecodeptr
@statusline = nil
end
display_screen(screenlines, cursx, cursy)
click to toggle source
display the text buffer screenlines to the screen, leaves the cursor at
(cursx, cursy), converts cursor pos from 0-base to 1-base screenlines is a
big text buffer with 1 line per tobeshown screen line (e.g. no Ansi cursor pos) screenlines must be screen wide
def display_screen(screenlines, cursx, cursy)
@oldscreenbuf ||= []
lines = screenlines.lines
oldlines = @oldscreenbuf
@oldscreenbuf = lines
screenlines = lines.zip(oldlines).map { |l, ol| l == ol ? "\n" : l }.join
while screenlines[-1] == \n
screenlines.chop!
end
starty = 1
while screenlines[0] == \n
screenlines = screenlines[1..-1]
starty += 1
end
$stdout.write Ansi.set_cursor_pos(starty, 1) + screenlines + Ansi.set_cursor_pos(cursy+1, cursx+1)
$stdout.flush
end
exec_prompt()
click to toggle source
def exec_prompt
@log_off = 0
log ':'+@promptbuf
return if @promptbuf == ''
str = @promptbuf
@prompthistory << @promptbuf
@prompthistory.shift if @prompthistory.length > @prompthistlen
@promptbuf = ''
@promptpos = @promptbuf.length
cmd, str = str.split(/\s+/, 2)
if @command.has_key? cmd
@command[cmd].call(str.to_s)
else
if cmd and (poss = @command.keys.find_all { |c| c[0, cmd.length] == cmd }).length == 1
@command[poss.first].call(str.to_s)
else
log 'unknown command'
end
end
end
fini_screen()
click to toggle source
def fini_screen
Ansi.set_term_canon(false)
$stdout.write Ansi.color(:normal, :reset)
$stdout.flush
end
handle_keypress(k)
click to toggle source
def handle_keypress(k)
case k
when \4; log 'exiting'; return true
when \e; @focus = :prompt
when :f5; cont
when :f6; syscall
when :f10; stepover
when :f11; singlestep
when :f12; stepout
when :up
case @focus
when :prompt
if not @histptr
@prompthistory << @promptbuf
@histptr = 2
else
@histptr += 1
@histptr = 1 if @histptr > @prompthistory.length
end
@promptbuf = @prompthistory[-@histptr].dup
@promptpos = @promptbuf.length
when :data
@dataptr -= 16
when :code
@codeptr ||= @rs.pc
@codeptr -= (1..10).find { |off|
di = @rs.di_at(@codeptr-off)
di.bin_length == off if di
} || 10
end
when :down
case @focus
when :prompt
if not @histptr
@prompthistory << @promptbuf
@histptr = @prompthistory.length
else
@histptr -= 1
@histptr = @prompthistory.length if @histptr < 1
end
@promptbuf = @prompthistory[-@histptr].dup
@promptpos = @promptbuf.length
when :data
@dataptr += 16
when :code
@codeptr ||= @rs.pc
di = @rs.di_at(@codeptr)
@codeptr += (di ? (di.bin_length || 1) : 1)
end
when :left; @promptpos -= 1 if @promptpos > 0
when :right; @promptpos += 1 if @promptpos < @promptbuf.length
when :home; @promptpos = 0
when :end; @promptpos = @promptbuf.length
when :backspace, \x7f; @promptbuf[@promptpos-=1, 1] = '' if @promptpos > 0
when :suppr; @promptbuf[@promptpos, 1] = '' if @promptpos < @promptbuf.length
when :pgup
case @focus
when :prompt; @log_off += @win_prpt_height-3
when :data; @dataptr -= 16*(@win_data_height-1)
when :code
@codeptr ||= @rs.pc
(@win_code_height-1).times {
@codeptr -= (1..10).find { |off|
di = @rs.di_at(@codeptr-off)
di.bin_length == off if di
} || 10
}
end
when :pgdown
case @focus
when :prompt; @log_off -= @win_prpt_height-3
when :data; @dataptr += 16*(@win_data_height-1)
when :code
@codeptr ||= @rs.pc
(@win_code_height-1).times { @codeptr += ((o = @rs.di_at(@codeptr)) ? [o.bin_length, 1].max : 1) }
end
when \t
if not @promptbuf[0, @promptpos].include? ' '
poss = @command.keys.find_all { |c| c[0, @promptpos] == @promptbuf[0, @promptpos] }
if poss.length > 1
log poss.sort.join(' ')
elsif poss.length == 1
@promptbuf[0, @promptpos] = poss.first + ' '
@promptpos = poss.first.length+1
end
end
when \n
@histptr = nil
begin
exec_prompt
rescue Exception
log "error: #$!", *$!.backtrace
end
when \ ..~
@promptbuf[@promptpos, 0] = k.chr
@promptpos += 1
else log "unknown key pressed #{k.inspect}"
end
nil
end
init_rs()
click to toggle source
def init_rs
@codeptr = @dataptr = @rs.pc
end
init_screen()
click to toggle source
def init_screen
Ansi.set_term_canon(true)
@win_reg_height = 2
@win_data_height = 20
@win_code_height = 20
resize
end
load_commands()
click to toggle source
def load_commands
@command['kill'] = lambda { |str|
@rs.kill
@running = false
log 'killed'
}
@command['quit'] = @command['detach'] = @command['exit'] = lambda { |str|
@rs.detach
@running = false
}
@command['closeui'] = lambda { |str|
@running = false
}
@command['bpx'] = lambda { |str|
@rs.bpx @rs.resolve(str)
}
@command['bphw'] = @command['hwbp'] = lambda { |str|
type, str = str.split(/\s+/, 2)
@rs.hwbp @rs.resolve(str.to_s), type
}
@command['bt'] = lambda { |str| @rs.stacktrace { |a,t| add_log "#{'%x' % a} #{t}" } }
@command['d'] = lambda { |str| @dataptr = @rs.resolve(str) if str.length > 0 }
@command['db'] = lambda { |str| @datafmt = 'db' ; @dataptr = @rs.resolve(str) if str.length > 0 }
@command['dw'] = lambda { |str| @datafmt = 'dw' ; @dataptr = @rs.resolve(str) if str.length > 0 }
@command['dd'] = lambda { |str| @datafmt = 'dd' ; @dataptr = @rs.resolve(str) if str.length > 0 }
@command['r'] = lambda { |str|
r, str = str.split(/\s+/, 2)
if r == 'fl'
@rs.toggle_flag(str.to_sym)
elsif not @rs[r]
log "bad reg #{r}"
elsif str and str.length > 0
@rs[r] = @rs.resolve(str)
else
log "#{r} = #{@rs[r]}"
end
}
@command['g'] = lambda { |str|
@rs.go @rs.resolve(str)
}
@command['u'] = lambda { |str| @codeptr = @rs.resolve(str) }
@command['ruby'] = lambda { |str| instance_eval str }
@command['wd'] = lambda { |str|
@focus = :data
if str.length > 0
@win_data_height = @rs.resolve(str)
resize
end
}
@command['wc'] = lambda { |str|
@focus = :code
if str.length > 0
@win_code_height = @rs.resolve(str)
resize
end
}
@command['wp'] = lambda { |str| @focus = :prompt }
@command['?'] = lambda { |str|
val = @rs.resolve(str)
log "#{val} 0x#{val.to_s(16)} #{[val].pack('L').inspect}"
}
@command['syscall'] = lambda { |str|
@rs.syscall_wait(str)
}
end
log(*strs)
click to toggle source
def log(*strs)
strs.each { |str|
raise str.inspect if not str.kind_of? ::String
str = str.chomp
if str.length > @console_width
str.scan(/.{0,#@console_width}/) { |str_| log str_ }
return
end
@promptlog << str
@promptlog.shift if @promptlog.length > @promptloglen
}
end
main_loop()
click to toggle source
def main_loop
begin
begin
init_screen
init_rs
main_loop_inner
rescue Errno::ESRCH
log "target does not exist anymore"
ensure
fini_screen
$stdout.print Ansi.set_cursor_pos(@console_height, 1)
end
rescue
puts $!, $!.backtrace
end
puts @promptlog.last
end
main_loop_inner()
click to toggle source
def main_loop_inner
@prompthistory = ['']
@histptr = nil
@running = true
update
while @running
if not IO.select [$stdin], nil, nil, 0
begin
update
rescue Errno::ESRCH
break
end
end
break if handle_keypress(Ansi.getkey)
end
end
once(name, defretval=nil) { || ... }
click to toggle source
yields but keep from reentring (return defretval in this case)
def once(name, defretval=nil)
@once ||= {}
if not @once[name]
@once[name] = true
begin
defretval = yield
ensure
@once[name] = false
end
end
defretval
end
optimize_screen(buf)
click to toggle source
optimize string to display to stdout currently only skips unchanged lines
could also match end of lines (spaces), but would need to check for color
codes etc
def optimize_screen(buf)
end
preupdate()
click to toggle source
def preupdate
@rs.register_list.each { |r| @oldregs[r] = @rs[r] }
@rs.flag_list.each { |fl| @oldregs[fl] = @rs.get_flag(fl) }
end
resize()
click to toggle source
def resize
@console_height, @console_width = Ansi.get_terminal_size
@win_data_height = 1 if @win_data_height < 1
@win_code_height = 1 if @win_code_height < 1
if @win_data_height + @win_code_height + @win_reg_height + 3 > @console_height
@win_data_height = @console_height/2 - 4
@win_code_height = @console_height/2 - 4
end
@win_prpt_height = @console_height-(@win_data_height+@win_code_height+@win_reg_height) - 1
@oldscreenbuf = []
update
end
singlestep()
click to toggle source
def singlestep
self.statusline = ' target singlestepping...'
preupdate
@rs.singlestep_wait
updatecodeptr
@statusline = nil
end
statusline()
click to toggle source
def statusline
@statusline ||= ' Enter a command (help for help)'
end
statusline=(s)
click to toggle source
def statusline=(s)
@statusline = s
update
end
stepout()
click to toggle source
def stepout
self.statusline = ' target running...'
preupdate
@rs.stepout_wait
updatecodeptr
@statusline = nil
end
stepover()
click to toggle source
def stepover
self.statusline = ' target running...'
preupdate
@rs.stepover_wait
updatecodeptr
@statusline = nil
end
syscall()
click to toggle source
def syscall
self.statusline = ' target running to next syscall...'
preupdate
@rs.syscall_wait
updatecodeptr
@statusline = nil
end
update()
click to toggle source
def update
return if not @running
display_screen updateregs + updatedata + updatecode + updateprompt, @promptpos+1, @console_height-2
end
updatecode()
click to toggle source
def updatecode
once(:updatecode, "...\n"*@win_code_height) { _updatecode }
end
updatecodeptr()
click to toggle source
def updatecodeptr
@codeptr ||= @rs.pc
if @codeptr > @rs.pc or @codeptr < @rs.pc - 6*@win_code_height
@codeptr = @rs.pc
elsif @codeptr != @rs.pc
addr = @codeptr
addrs = []
while addr < @rs.pc
addrs << addr
o = ((di = @rs.di_at(addr)) ? di.bin_length : 0)
addr += ((o == 0) ? 1 : o)
end
if addrs.length > @win_code_height-4
@codeptr = addrs[-(@win_code_height-4)]
end
end
updatedataptr
end
updatedata()
click to toggle source
def updatedata
once(:updatedata, "...\n"*@win_data_height) { _updatedata }
end
updatedataptr()
click to toggle source
updateprompt()
click to toggle source
def updateprompt
once(:updateprompt, "\n"*@win_prpt_height) { _updateprompt }
end
updateregs()
click to toggle source
def updateregs
once(:updateregs, "\n\n") { _updateregs }
end
win_code_start()
click to toggle source
def win_code_start; win_data_start+win_data_height end
win_data_start()
click to toggle source
def win_data_start; @win_reg_height end
win_prpt_start()
click to toggle source
def win_prpt_start; win_code_start+win_code_height end