319 lines
6.9 KiB
Ruby
319 lines
6.9 KiB
Ruby
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
#
|
|
# Licence is LGPL, see LICENCE in the top-level directory
|
|
|
|
#
|
|
# this is a little script to navigate in a disassembler dump
|
|
#
|
|
|
|
# copypasted from lindebug.rb
|
|
module Ansi
|
|
CursHome = "\e[H".freeze
|
|
ClearLineAfter = "\e[0K"
|
|
ClearLineBefore = "\e[1K"
|
|
ClearLine = "\e[2K"
|
|
ClearScreen = "\e[2J"
|
|
def self.set_cursor_pos(y=1,x=1) "\e[#{y};#{x}H" end
|
|
Reset = "\e[m"
|
|
Colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :aoeu, :reset]
|
|
def self.color(*args)
|
|
fg = true
|
|
"\e[" << args.map { |a|
|
|
case a
|
|
when :bold; 2
|
|
when :negative; 7
|
|
when :normal; 22
|
|
when :positive; 27
|
|
else
|
|
if col = Colors.index(a)
|
|
add = (fg ? 30 : 40)
|
|
fg = false
|
|
col+add
|
|
end
|
|
end
|
|
}.compact.join(';') << 'm'
|
|
end
|
|
def self.hline(len) "\e(0"<<'q'*len<<"\e(B" end
|
|
|
|
TIOCGWINSZ = 0x5413
|
|
TCGETS = 0x5401
|
|
TCSETS = 0x5402
|
|
CANON = 2
|
|
ECHO = 8
|
|
def self.get_terminal_size
|
|
s = ''.ljust(8)
|
|
$stdin.ioctl(TIOCGWINSZ, s) >= 0 ? s.unpack('SS') : [80, 25]
|
|
end
|
|
def self.set_term_canon(bool)
|
|
tty = ''.ljust(256)
|
|
$stdin.ioctl(TCGETS, tty)
|
|
if bool
|
|
tty[12] &= ~(ECHO|CANON)
|
|
else
|
|
tty[12] |= ECHO|CANON
|
|
end
|
|
$stdin.ioctl(TCSETS, tty)
|
|
end
|
|
|
|
ESC_SEQ = {'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left,
|
|
'1~' => :home, '2~' => :inser, '3~' => :suppr, '4~' => :end,
|
|
'5~' => :pgup, '6~' => :pgdown,
|
|
'P' => :f1, 'Q' => :f2, 'R' => :f3, 'S' => :f4,
|
|
'15~' => :f5, '17~' => :f6, '18~' => :f7, '19~' => :f8,
|
|
'20~' => :f9, '21~' => :f10, '23~' => :f11, '24~' => :f12,
|
|
'[A' => :f1, '[B' => :f2, '[C' => :f3, '[D' => :f4, '[E' => :f5,
|
|
'H' => :home, 'F' => :end,
|
|
}
|
|
def self.getkey
|
|
c = $stdin.getc
|
|
return c if c != ?\e
|
|
c = $stdin.getc
|
|
if c != ?[ and c != ?O
|
|
$stdin.ungetc c
|
|
return ?\e
|
|
end
|
|
seq = ''
|
|
loop do
|
|
c = $stdin.getc
|
|
seq << c
|
|
case c; when ?a..?z, ?A..?Z, ?~; break end
|
|
end
|
|
ESC_SEQ[seq] || seq
|
|
end
|
|
end
|
|
|
|
class Viewer
|
|
attr_accessor :text, :pos, :x, :y
|
|
|
|
Color = {
|
|
:normal => Ansi.color(:white, :black, :normal),
|
|
:comment => Ansi.color(:blue),
|
|
:label => Ansi.color(:green),
|
|
:hilight => Ansi.color(:yellow),
|
|
}
|
|
|
|
|
|
def initialize(text)
|
|
text = File.read(text) if File.exist? text rescue nil
|
|
@text = text.gsub("\t", " "*8).to_a.map { |l| l.chomp }
|
|
@pos = @posh = 0
|
|
@x = @y = 0
|
|
@mode = :navig
|
|
@searchtext = 'x'
|
|
@posstack = []
|
|
@h, @w = Ansi.get_terminal_size
|
|
@h -= 2
|
|
@w -= 1
|
|
if y = @text.index('entrypoint:')
|
|
view(0, y)
|
|
end
|
|
end
|
|
|
|
def main_loop
|
|
Ansi.set_term_canon(true)
|
|
$stdout.write Ansi::ClearScreen
|
|
begin
|
|
loop do
|
|
refresh if not s = IO.select([$stdin], nil, nil, 0)
|
|
handle_key(Ansi.getkey)
|
|
end
|
|
ensure
|
|
Ansi.set_term_canon(false)
|
|
$stdout.write Ansi.set_cursor_pos(@h+2, 0) + Ansi::ClearLineAfter
|
|
end
|
|
end
|
|
|
|
def refresh
|
|
case @mode
|
|
when :navig
|
|
refresh_navig
|
|
when :search
|
|
refresh_search
|
|
end
|
|
end
|
|
|
|
def refresh_navig
|
|
str = ''
|
|
#str << Ansi::ClearScreen
|
|
str << Ansi.set_cursor_pos(0, 0)
|
|
hl = readtext
|
|
(0..@h).each { |h|
|
|
l = @text[@pos+h] || ''
|
|
str << outline(l, hl) << Ansi::ClearLineAfter << "\n"
|
|
}
|
|
str << Ansi.set_cursor_pos(@y+1, @x+1)
|
|
$stdout.write str
|
|
end
|
|
|
|
def refresh_search
|
|
$stdout.write '' << Ansi.set_cursor_pos(@h+2, 1) << '/' << @searchtext << Ansi::ClearLineAfter
|
|
end
|
|
|
|
def outline(l, hl=nil)
|
|
l = l[@posh, @w] || ''
|
|
hlr = /\b#{Regexp.escape(hl)}\b/i if hl
|
|
case l
|
|
when /^\/\//; Color[:comment] + l + Color[:normal]
|
|
when /^\S+:$/; Color[:label] + l + Color[:normal]
|
|
when /^(.*)(;.*)$/
|
|
str = $1
|
|
cmt = $2
|
|
str.gsub!(hlr, Color[:hilight]+hl+Color[:normal]) if hl
|
|
str + Color[:comment] + cmt + Color[:normal]
|
|
else
|
|
l = l.gsub(hlr, Color[:hilight]+hl+Color[:normal]) if hl
|
|
l
|
|
end
|
|
end
|
|
|
|
def search_prev
|
|
return if @searchtext == ''
|
|
y = @pos+@y-1
|
|
loop do
|
|
y = @text.length-1 if not @text[y] or y < 0
|
|
if x = (@text[y] =~ /#@searchtext/i)
|
|
view(x, y)
|
|
return
|
|
end
|
|
y -= 1
|
|
break if y == @pos+@y
|
|
end
|
|
end
|
|
|
|
def search_next
|
|
return if @searchtext == ''
|
|
y = @pos+@y+1
|
|
loop do
|
|
y = 0 if not @text[y]
|
|
if x = (@text[y] =~ /#@searchtext/i)
|
|
view(x, y)
|
|
return
|
|
end
|
|
break if y == @pos+@y or (y >= @text.length and not @text[@pos+@y])
|
|
y += 1
|
|
end
|
|
end
|
|
|
|
def view(x, y)
|
|
@posh, @x = 0, x
|
|
if @x > @w
|
|
@posh = @w-@x
|
|
@x = @w
|
|
end
|
|
if @pos+@h < y
|
|
@y = @h/2-1
|
|
@pos = y-@y
|
|
elsif @pos > y
|
|
@y = 1
|
|
@pos = y-@y
|
|
else
|
|
@y = y-@pos
|
|
end
|
|
end
|
|
|
|
def readtext
|
|
return if not l = @text[@pos+@y]
|
|
x = (l.rindex(/\W/, [@posh+@x-1, 0].max) || -1)+1
|
|
t = l[x..-1][/^\w+/]
|
|
t if t and @posh+@x < x+t.length
|
|
end
|
|
|
|
def handle_key(k)
|
|
case @mode
|
|
when :navig
|
|
handle_key_navig(k)
|
|
when :search
|
|
handle_key_search(k)
|
|
end
|
|
end
|
|
|
|
def handle_key_search(k)
|
|
case k
|
|
when ?\n; @mode = :navig ; @posstack << [@posh, @pos, @x, @y] ; search_next
|
|
when 0x20..0x7e; @searchtext << k
|
|
when :backspace, 0x7f; @searchtext.chop!
|
|
end
|
|
end
|
|
|
|
def handle_key_navig(k)
|
|
case k
|
|
when :f1
|
|
if not @posstack.empty?
|
|
@posh, @pos, @x, @y = @posstack.pop
|
|
end
|
|
when ?\n
|
|
return if not label = readtext
|
|
return if label.empty? or not newy = @text.index(@text.find { |l| l[0, label.length] == label }) or newy == @pos+@y
|
|
@posstack << [@posh, @pos, @x, @y]
|
|
view(0, newy)
|
|
when :up
|
|
if @y > 0; @y -= 1
|
|
elsif @pos > 0; @pos -= 1
|
|
end
|
|
when :down
|
|
if @y < @h; @y += 1
|
|
elsif @pos < text.length-@h; @pos += 1
|
|
end
|
|
when :home
|
|
@x = @posh = 0
|
|
when :end
|
|
@x = @text[@pos+@y].length
|
|
@posh, @x = @x-@w, @w if @x > @w
|
|
when :left
|
|
x = @text[@pos+@y].rindex(/\W\w/, [@posh+@x-2, 0].max)
|
|
x = x ? x+1 : @posh+@x-1
|
|
x = @posh+@x-3 if x < @posh+@x-3
|
|
x = 0 if x < 0
|
|
if x < @posh; @posh, @x = x, 0
|
|
else @x = x-@posh
|
|
end
|
|
#if @x > 0; @x -= 1
|
|
#elsif @posh > 0; @posh -= 1
|
|
#end
|
|
when :right
|
|
x = @text[@pos+@y].index(/\W\w/, @posh+@x)
|
|
x = x ? x+1 : @posh+@x+1
|
|
x = @posh+@x+3 if x > @posh+@x+3
|
|
if x > @posh+@w; @posh, @x = x-@w, @w
|
|
else
|
|
@x = x-@posh
|
|
@posh, @x = @x-@w, @w if @x > @w
|
|
end
|
|
#if @x < @w; @x += 1
|
|
#elsif @posh+@w < (@text[@pos, @h].map { |l| l.length }.max); @posh += 1
|
|
#end
|
|
when :pgdown
|
|
if @y < @h/2; @y += @h/2
|
|
elsif @pos < @text.length-3*@h/2; @pos += @h/2 ; @y = @h
|
|
else @pos = [0, @text.length-@h].max ; @y = @h
|
|
end
|
|
when :pgup
|
|
if @y > @h/2; @y -= @h/2
|
|
elsif @pos > @h/2; @pos -= @h/2 ; @y = 0
|
|
else @pos = @y = 0
|
|
end
|
|
when ?q; exit
|
|
when ?o; @text.insert(@pos+@y+1, '')
|
|
when ?O; @text.insert(@pos+@y, '') ; handle_key_navig(:down)
|
|
when :suppr; @text.delete_at(@pos+@y) if @text[@pos+@y] == ''
|
|
when ?D; @text.delete_at(@pos+@y)
|
|
when ?/
|
|
@mode = :search
|
|
@searchtext = ''
|
|
when ?*
|
|
@searchtext = readtext || ''
|
|
search_next
|
|
when ?n; search_next
|
|
when ?N; search_prev
|
|
when :f5
|
|
ARGV << '--reload'
|
|
load $0
|
|
end
|
|
end
|
|
end
|
|
|
|
if $0 == __FILE__ and not ARGV.delete '--reload'
|
|
Viewer.new(ARGF.read).main_loop
|
|
end
|