metasm 1.0.0
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/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
# this generates a signature file for all function in the given library
|
|
7
|
+
# usage: gensigs.rb some_lib.a somefile.o somelib.lib > mylib.fsigs
|
|
8
|
+
#
|
|
9
|
+
# to be used with the match_libsigs disassembler plugin
|
|
10
|
+
|
|
11
|
+
# TODO handle COFF symbols (for .lib)
|
|
12
|
+
|
|
13
|
+
require 'metasm'
|
|
14
|
+
|
|
15
|
+
include Metasm
|
|
16
|
+
|
|
17
|
+
AutoExe.register_signature("!<arch>\n", COFFArchive)
|
|
18
|
+
AutoExe.register_signature("\x4c\x01", COFF) # no signature, check the machine field = i386
|
|
19
|
+
|
|
20
|
+
# minimum number of raw bytes to allow in a signature
|
|
21
|
+
$min_sigbytes = 8
|
|
22
|
+
|
|
23
|
+
# read a binary file, print a signature for all symbols found
|
|
24
|
+
def create_sig(exe)
|
|
25
|
+
func = case exe
|
|
26
|
+
when COFFArchive; :create_sig_arch
|
|
27
|
+
when PE, COFF; :create_sig_coff
|
|
28
|
+
when ELF; :create_sig_elf
|
|
29
|
+
else raise 'unsupported file format'
|
|
30
|
+
end
|
|
31
|
+
send(func, exe) { |sym, edata|
|
|
32
|
+
sig = edata.data.unpack('H*').first
|
|
33
|
+
edata.reloc.each { |o, r|
|
|
34
|
+
# TODO if the reloc points to a known func (eg WinMain), keep the info
|
|
35
|
+
sz = r.length
|
|
36
|
+
sig[2*o, 2*sz] = '.' * sz * 2
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
next if sig.gsub('.', '').length < 2*$min_sigbytes
|
|
40
|
+
|
|
41
|
+
puts sym
|
|
42
|
+
sig.scan(/.{1,78}/) { |s| puts ' ' + s }
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# handle coff archives
|
|
47
|
+
def create_sig_arch(exe)
|
|
48
|
+
exe.members.each { |m|
|
|
49
|
+
next if m.name == '/' or m.name == '//'
|
|
50
|
+
obj = m.exe rescue next
|
|
51
|
+
create_sig(obj)
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# scan an elf file
|
|
56
|
+
def create_sig_elf(elf)
|
|
57
|
+
elf.symbols.each { |sym|
|
|
58
|
+
next if sym.type != 'FUNC' or sym.shndx == 'UNDEF'
|
|
59
|
+
if elf.header.type == 'REL'
|
|
60
|
+
next if not data = elf.sections[sym.shndx].encoded
|
|
61
|
+
off = sym.value
|
|
62
|
+
else
|
|
63
|
+
next if not seg = elf.segments.find { |s| s.type == 'LOAD' and sym.value >= s.vaddr and sym.value < s.vaddr+s.memsz }
|
|
64
|
+
next if not data = seg.encoded
|
|
65
|
+
off = sym.value - seg.vaddr
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
len = sym.size
|
|
69
|
+
if len == 0
|
|
70
|
+
len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length
|
|
71
|
+
len -= off
|
|
72
|
+
len = 256 if len > 256
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
yield sym.name, data[off, len]
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# scan a pe/coff file
|
|
80
|
+
def create_sig_coff(coff)
|
|
81
|
+
if coff.kind_of? PE # dll
|
|
82
|
+
# dll
|
|
83
|
+
# TODO
|
|
84
|
+
else
|
|
85
|
+
coff.symbols.to_a.compact.each { |sym|
|
|
86
|
+
next if sym.type != 'FUNCTION'
|
|
87
|
+
next if not sym.sec_nr.kind_of? Integer
|
|
88
|
+
data = coff.sections[sym.sec_nr-1].encoded
|
|
89
|
+
off = sym.value
|
|
90
|
+
len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length
|
|
91
|
+
|
|
92
|
+
yield sym.name, data[off, len]
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if __FILE__ == $0
|
|
98
|
+
$min_sigbytes = ARGV.shift.to_i if ARGV.first =~ /^\d+$/
|
|
99
|
+
targets = ARGV
|
|
100
|
+
targets = Dir['*.a'] + Dir['*.lib'] if targets.empty?
|
|
101
|
+
targets.each { |t| create_sig(AutoExe.decode_file(t)) }
|
|
102
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# this sample script fixes a bug in some GTK libs (eg debian) where at some point
|
|
9
|
+
# when you close a window an invalid memory dereference is done, which crashes the
|
|
10
|
+
# whole metasm GUI
|
|
11
|
+
#
|
|
12
|
+
# bug backtrace:
|
|
13
|
+
# 0f79e6173h libgobject-2.0.so.0!g_type_check_instance+23
|
|
14
|
+
# 0f79e3e38h libgobject-2.0.so.0!g_signal_handlers_disconnect_matched+28
|
|
15
|
+
# 0f70004c3h libgtk-x11-2.0.so.0!gtk_accel_label_set_accel_closure+c3
|
|
16
|
+
# 0f70006d3h libgtk-x11-2.0.so.0!gtk_accel_label_set_accel_widget+b3
|
|
17
|
+
# ...
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
require 'metasm'
|
|
21
|
+
|
|
22
|
+
include Metasm
|
|
23
|
+
|
|
24
|
+
if not pr = OS.current.find_process(ARGV.first)
|
|
25
|
+
abort "cant find target"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
dbg = pr.debugger
|
|
29
|
+
|
|
30
|
+
dbg.continue
|
|
31
|
+
puts "monitoring.." if $VERBOSE
|
|
32
|
+
dbg.wait_target
|
|
33
|
+
|
|
34
|
+
while dbg.state == :stopped
|
|
35
|
+
puts "target #{dbg.state} #{dbg.info}" if $VERBOSE
|
|
36
|
+
if di = dbg.di_at(dbg.pc) and di.to_s =~ /\[(...)\]/ and reg = $1.downcase.to_sym and regval = dbg.get_reg_value(reg) and regval > 0 and regval < 4096
|
|
37
|
+
bt = dbg.stacktrace(2)
|
|
38
|
+
calladdr = bt[1][0]-5
|
|
39
|
+
dbg.disassembler.disassemble_fast(calladdr)
|
|
40
|
+
call = dbg.di_at(calladdr)
|
|
41
|
+
dbg.disassembler.disassemble_fast(call.instruction.args.first.reduce) rescue nil
|
|
42
|
+
if di = dbg.disassembler.decoded[dbg.pc] and from = dbg.disassembler.decoded[di.block.from_normal.first] and from.block.list[-2].to_s =~ /test #{reg}, #{reg}/
|
|
43
|
+
puts "fix almost null deref #{di} (#{reg}=#{regval})" if $VERBOSE
|
|
44
|
+
dbg.set_reg_value(reg, 0)
|
|
45
|
+
dbg.set_reg_value(:eip, from.block.list[-2].address)
|
|
46
|
+
else
|
|
47
|
+
dbg.kill # dont infinite loop ( TODO just dont handle the exception)
|
|
48
|
+
end
|
|
49
|
+
elsif dbg.info =~ /SEGV/
|
|
50
|
+
puts "unhandled segfault #{di}..." if $VERBOSE
|
|
51
|
+
# yep, this actually works
|
|
52
|
+
dbg.set_reg_value(:eip, di.next_addr)
|
|
53
|
+
end
|
|
54
|
+
dbg.continue
|
|
55
|
+
puts "target running" if $VERBOSE
|
|
56
|
+
dbg.wait_target
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "target terminated" if $VERBOSE
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
3
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
4
|
+
#
|
|
5
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
6
|
+
|
|
7
|
+
# this file sets up the RUBYLIB environment variable for windows hosts
|
|
8
|
+
# the variable is set for the current user only
|
|
9
|
+
# the user session may need to be restarted so that the changes take effect
|
|
10
|
+
|
|
11
|
+
# the path to the framework
|
|
12
|
+
metasmpath = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
|
13
|
+
|
|
14
|
+
$: << metasmpath
|
|
15
|
+
require 'metasm'
|
|
16
|
+
|
|
17
|
+
d = Metasm::DynLdr
|
|
18
|
+
|
|
19
|
+
d.new_api_c <<EOS, 'advapi32'
|
|
20
|
+
__stdcall int RegCreateKeyExA(
|
|
21
|
+
void *key,
|
|
22
|
+
char *subkey,
|
|
23
|
+
int resvd,
|
|
24
|
+
char *class,
|
|
25
|
+
int options,
|
|
26
|
+
int access,
|
|
27
|
+
void *security,
|
|
28
|
+
void **keyout,
|
|
29
|
+
void *dispos);
|
|
30
|
+
|
|
31
|
+
__stdcall int RegQueryValueExA(
|
|
32
|
+
void *key,
|
|
33
|
+
char *value,
|
|
34
|
+
int resvd,
|
|
35
|
+
int type,
|
|
36
|
+
void *data,
|
|
37
|
+
int *datalen);
|
|
38
|
+
|
|
39
|
+
__stdcall int RegSetValueExA(
|
|
40
|
+
void *key,
|
|
41
|
+
char *value,
|
|
42
|
+
int resvd,
|
|
43
|
+
int type,
|
|
44
|
+
void *data,
|
|
45
|
+
int datalen);
|
|
46
|
+
|
|
47
|
+
__stdcall int RegCloseKey(
|
|
48
|
+
void *key);
|
|
49
|
+
|
|
50
|
+
#define KEY_ALL_ACCESS 0xf003f
|
|
51
|
+
#define KEY_CURRENT_USER 0x80000001
|
|
52
|
+
#define REG_EXPAND_SZ 2
|
|
53
|
+
EOS
|
|
54
|
+
|
|
55
|
+
key = [0].pack('L')
|
|
56
|
+
ret = d.regcreatekeyexa(d::KEY_CURRENT_USER, 'Environment', 0, 0, 0, d::KEY_ALL_ACCESS, 0, key, 0)
|
|
57
|
+
key = key.unpack('L').first
|
|
58
|
+
abort 'cannot open env key' if ret != 0
|
|
59
|
+
|
|
60
|
+
buf = 0.chr*4096
|
|
61
|
+
buflen = [buf.length].pack('L')
|
|
62
|
+
ret = d.regqueryvalueexa(key, 'RUBYLIB', 0, 0, buf, buflen)
|
|
63
|
+
data = ret == 0 ? buf[0, buflen.unpack('L').first-1] : ''
|
|
64
|
+
|
|
65
|
+
if data.split(';').include? metasmpath
|
|
66
|
+
puts 'already registered'
|
|
67
|
+
else
|
|
68
|
+
data << ';' if not data.empty?
|
|
69
|
+
data << metasmpath << 0
|
|
70
|
+
ret = d.regsetvalueexa(key, 'RUBYLIB', 0, d::REG_EXPAND_SZ, data, data.length)
|
|
71
|
+
if ret == 0
|
|
72
|
+
puts "success - restart your session"
|
|
73
|
+
else
|
|
74
|
+
puts "failed :( - #{Metasm::WinAPI.last_error_msg(ret)}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
d.regclosekey(key)
|
data/samples/lindebug.rb
ADDED
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# this is a linux/x86 debugger with a console interface
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
require 'metasm'
|
|
11
|
+
|
|
12
|
+
module Ansi
|
|
13
|
+
CursHome = "\e[H".freeze
|
|
14
|
+
ClearLineAfter = "\e[0K"
|
|
15
|
+
ClearLineBefore = "\e[1K"
|
|
16
|
+
ClearLine = "\e[2K"
|
|
17
|
+
ClearScreen = "\e[2J"
|
|
18
|
+
def self.set_cursor_pos(y=1,x=1) "\e[#{y};#{x}H" end
|
|
19
|
+
Reset = "\e[m"
|
|
20
|
+
Colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :aoeu, :reset]
|
|
21
|
+
def self.color(*args)
|
|
22
|
+
fg = true
|
|
23
|
+
"\e[" << args.map { |a|
|
|
24
|
+
case a
|
|
25
|
+
when :bold; 2
|
|
26
|
+
when :negative; 7
|
|
27
|
+
when :normal; 22
|
|
28
|
+
when :positive; 27
|
|
29
|
+
else
|
|
30
|
+
if col = Colors.index(a)
|
|
31
|
+
add = (fg ? 30 : 40)
|
|
32
|
+
fg = false
|
|
33
|
+
col+add
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
}.compact.join(';') << 'm'
|
|
37
|
+
end
|
|
38
|
+
def self.hline(len) "\e(0"<<'q'*len<<"\e(B" end
|
|
39
|
+
|
|
40
|
+
TIOCGWINSZ = 0x5413
|
|
41
|
+
TCGETS = 0x5401
|
|
42
|
+
TCSETS = 0x5402
|
|
43
|
+
CANON = 2
|
|
44
|
+
ECHO = 8
|
|
45
|
+
def self.get_terminal_size
|
|
46
|
+
s = ''.ljust(8)
|
|
47
|
+
$stdin.ioctl(TIOCGWINSZ, s) >= 0 ? s.unpack('SS') : [80, 25]
|
|
48
|
+
end
|
|
49
|
+
def self.set_term_canon(bool)
|
|
50
|
+
tty = ''.ljust(256)
|
|
51
|
+
$stdin.ioctl(TCGETS, tty)
|
|
52
|
+
if bool
|
|
53
|
+
tty[12] &= ~(ECHO|CANON)
|
|
54
|
+
else
|
|
55
|
+
tty[12] |= ECHO|CANON
|
|
56
|
+
end
|
|
57
|
+
$stdin.ioctl(TCSETS, tty)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ESC_SEQ = {'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left,
|
|
61
|
+
'1~' => :home, '2~' => :inser, '3~' => :suppr, '4~' => :end,
|
|
62
|
+
'5~' => :pgup, '6~' => :pgdown,
|
|
63
|
+
'P' => :f1, 'Q' => :f2, 'R' => :f3, 'S' => :f4,
|
|
64
|
+
'15~' => :f5, '17~' => :f6, '18~' => :f7, '19~' => :f8,
|
|
65
|
+
'20~' => :f9, '21~' => :f10, '23~' => :f11, '24~' => :f12,
|
|
66
|
+
'[A' => :f1, '[B' => :f2, '[C' => :f3, '[D' => :f4, '[E' => :f5,
|
|
67
|
+
'H' => :home, 'F' => :end,
|
|
68
|
+
}
|
|
69
|
+
def self.getkey
|
|
70
|
+
c = $stdin.getc
|
|
71
|
+
raise 'nonblocking $stdin?' if not c
|
|
72
|
+
return c if c != ?\e
|
|
73
|
+
c = $stdin.getc
|
|
74
|
+
if c != ?[ and c != ?O
|
|
75
|
+
$stdin.ungetc c
|
|
76
|
+
return ?\e
|
|
77
|
+
end
|
|
78
|
+
seq = ''
|
|
79
|
+
loop do
|
|
80
|
+
c = $stdin.getc
|
|
81
|
+
seq << c
|
|
82
|
+
case c; when ?a..?z, ?A..?Z, ?~; break end
|
|
83
|
+
end
|
|
84
|
+
ESC_SEQ[seq] || seq
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Indirect < Metasm::ExpressionType
|
|
89
|
+
attr_accessor :ptr, :sz
|
|
90
|
+
UNPACK_STR = {1 => 'C', 2 => 'S', 4 => 'L'}
|
|
91
|
+
def initialize(ptr, sz) @ptr, @sz = ptr, sz end
|
|
92
|
+
def bind(bd)
|
|
93
|
+
raw = bd['tracer_memory'][@ptr.bind(bd).reduce, @sz]
|
|
94
|
+
Metasm::Expression[raw.unpack(UNPACK_STR[@sz]).first]
|
|
95
|
+
end
|
|
96
|
+
def externals ; @ptr.externals end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class ExprParser < Metasm::Expression
|
|
100
|
+
def self.parse_intfloat(lex, tok)
|
|
101
|
+
case tok.raw
|
|
102
|
+
when 'byte', 'word', 'dword'
|
|
103
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
|
104
|
+
nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == 'ptr'
|
|
105
|
+
if ntok and ntok.raw == '['
|
|
106
|
+
tok.value = Indirect.new(parse(lex), {'byte' => 1, 'word' => 2, 'dword' => 4}[tok.raw])
|
|
107
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
|
108
|
+
nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == ']'
|
|
109
|
+
lex.unreadtok ntok
|
|
110
|
+
end
|
|
111
|
+
else super(lex, tok)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
def self.parse_value(lex)
|
|
115
|
+
nil while tok = lex.readtok and tok.type == :space
|
|
116
|
+
lex.unreadtok tok
|
|
117
|
+
if tok and tok.type == :punct and tok.raw == '['
|
|
118
|
+
tt = tok.dup
|
|
119
|
+
tt.type = :string
|
|
120
|
+
tt.raw = 'dword'
|
|
121
|
+
lex.unreadtok tt
|
|
122
|
+
end
|
|
123
|
+
super(lex)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class LinDebug
|
|
128
|
+
attr_accessor :win_data_height, :win_code_height, :win_prpt_height
|
|
129
|
+
def init_screen
|
|
130
|
+
Ansi.set_term_canon(true)
|
|
131
|
+
@win_data_height = 20
|
|
132
|
+
@win_code_height = 20
|
|
133
|
+
resize
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def fini_screen
|
|
137
|
+
Ansi.set_term_canon(false)
|
|
138
|
+
$stdout.write Ansi.color(:normal, :reset)
|
|
139
|
+
$stdout.flush
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def win_data_start; 2 end
|
|
143
|
+
def win_code_start; win_data_start+win_data_height end
|
|
144
|
+
def win_prpt_start; win_code_start+win_code_height end
|
|
145
|
+
|
|
146
|
+
Color = {:changed => Ansi.color(:cyan, :bold), :border => Ansi.color(:green),
|
|
147
|
+
:normal => Ansi.color(:white, :black, :normal), :hilight => Ansi.color(:blue, :white, :normal),
|
|
148
|
+
:status => Ansi.color(:black, :cyan)}
|
|
149
|
+
|
|
150
|
+
# yields but keep from reentring (return defretval in this case)
|
|
151
|
+
def once(name, defretval=nil)
|
|
152
|
+
@once ||= {}
|
|
153
|
+
if not @once[name]
|
|
154
|
+
@once[name] = true
|
|
155
|
+
begin
|
|
156
|
+
defretval = yield
|
|
157
|
+
ensure
|
|
158
|
+
@once[name] = false
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
defretval
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
attr_accessor :dataptr, :codeptr, :rs, :promptlog, :command
|
|
165
|
+
def initialize(rs)
|
|
166
|
+
@rs = rs
|
|
167
|
+
@rs.logger = self
|
|
168
|
+
@datafmt = 'db'
|
|
169
|
+
@watch = nil
|
|
170
|
+
|
|
171
|
+
@prompthistlen = 20
|
|
172
|
+
@prompthistory = []
|
|
173
|
+
@promptloglen = 200
|
|
174
|
+
@promptlog = []
|
|
175
|
+
@promptbuf = ''
|
|
176
|
+
@promptpos = 0
|
|
177
|
+
@log_off = 0
|
|
178
|
+
@console_width = 80
|
|
179
|
+
|
|
180
|
+
@running = false
|
|
181
|
+
@focus = :prompt
|
|
182
|
+
@command = {}
|
|
183
|
+
load_commands
|
|
184
|
+
trap('WINCH') { resize }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def init_rs
|
|
188
|
+
@codeptr = @dataptr = @rs.regs_cache['eip'] # avoid initial faults
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def main_loop
|
|
192
|
+
begin
|
|
193
|
+
begin
|
|
194
|
+
init_screen
|
|
195
|
+
init_rs
|
|
196
|
+
main_loop_inner
|
|
197
|
+
rescue Errno::ESRCH
|
|
198
|
+
log "target does not exist anymore"
|
|
199
|
+
ensure
|
|
200
|
+
fini_screen
|
|
201
|
+
$stdout.print Ansi.set_cursor_pos(@console_height, 1)
|
|
202
|
+
end
|
|
203
|
+
rescue
|
|
204
|
+
$stdout.puts $!, $!.backtrace
|
|
205
|
+
end
|
|
206
|
+
$stdout.puts @promptlog.last
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# optimize string to display to stdout
|
|
210
|
+
# currently only skips unchanged lines
|
|
211
|
+
# could also match end of lines (spaces), but would need to check for color codes etc
|
|
212
|
+
def optimize_screen(buf)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# display the text buffer screenlines to the screen, leaves the cursor at (cursx, cursy), converts cursor pos from 0-base to 1-base
|
|
216
|
+
# screenlines is a big text buffer with 1 line per tobeshown screen line (e.g. no Ansi cursor pos)
|
|
217
|
+
# screenlines must be screen wide
|
|
218
|
+
def display_screen(screenlines, cursx, cursy)
|
|
219
|
+
|
|
220
|
+
@oldscreenbuf ||= []
|
|
221
|
+
lines = screenlines.to_a
|
|
222
|
+
oldlines = @oldscreenbuf
|
|
223
|
+
@oldscreenbuf = lines
|
|
224
|
+
screenlines = lines.zip(oldlines).map { |l, ol| l == ol ? "\n" : l }.join
|
|
225
|
+
|
|
226
|
+
while screenlines[-1] == ?\n
|
|
227
|
+
screenlines.chop!
|
|
228
|
+
end
|
|
229
|
+
starty = 1
|
|
230
|
+
while screenlines[0] == ?\n
|
|
231
|
+
screenlines = screenlines[1..-1]
|
|
232
|
+
starty += 1
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
$stdout.write Ansi.set_cursor_pos(starty, 1) + screenlines + Ansi.set_cursor_pos(cursy+1, cursx+1)
|
|
236
|
+
|
|
237
|
+
$stdout.flush
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def update
|
|
241
|
+
return if not @running
|
|
242
|
+
display_screen updateregs + updatedata + updatecode + updateprompt, @promptpos+1, @console_height-2
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def updateregs
|
|
246
|
+
once(:updateregs, "\n\n") { _updateregs }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def _updateregs
|
|
250
|
+
text = ''
|
|
251
|
+
text << ' '
|
|
252
|
+
x = 1
|
|
253
|
+
%w[eax ebx ecx edx eip].each { |r|
|
|
254
|
+
text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r]
|
|
255
|
+
text << r << ?=
|
|
256
|
+
text << ('%08X' % @rs.regs_cache[r])
|
|
257
|
+
text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r]
|
|
258
|
+
text << ' '
|
|
259
|
+
x += r.length + 11
|
|
260
|
+
}
|
|
261
|
+
text << (' '*([@console_width-x, 0].max)) << "\n" << ' '
|
|
262
|
+
x = 1
|
|
263
|
+
%w[esi edi ebp esp].each { |r|
|
|
264
|
+
text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r]
|
|
265
|
+
text << r << ?=
|
|
266
|
+
text << ('%08X' % @rs.regs_cache[r])
|
|
267
|
+
text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r]
|
|
268
|
+
text << ' '
|
|
269
|
+
x += r.length + 11
|
|
270
|
+
}
|
|
271
|
+
Rubstop::EFLAGS.sort.each { |off, flag|
|
|
272
|
+
val = @rs.regs_cache['eflags'] & (1<<off)
|
|
273
|
+
flag = flag.upcase if val != 0
|
|
274
|
+
if val != @rs.oldregs['eflags'] & (1 << off)
|
|
275
|
+
text << Color[:changed]
|
|
276
|
+
text << flag
|
|
277
|
+
text << Color[:normal]
|
|
278
|
+
else
|
|
279
|
+
text << flag
|
|
280
|
+
end
|
|
281
|
+
text << ' '
|
|
282
|
+
x += 2
|
|
283
|
+
}
|
|
284
|
+
text << (' '*([@console_width-x, 0].max)) << "\n"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def updatecode
|
|
288
|
+
once(:updatecode, "...\n"*@win_code_height) { _updatecode }
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def _updatecode
|
|
292
|
+
if @codeptr
|
|
293
|
+
addr = @codeptr
|
|
294
|
+
elsif @rs.oldregs['eip'] and @rs.oldregs['eip'] < @rs.regs_cache['eip'] and @rs.oldregs['eip'] + 8 >= @rs.regs_cache['eip']
|
|
295
|
+
addr = @rs.oldregs['eip']
|
|
296
|
+
else
|
|
297
|
+
addr = @rs.regs_cache['eip']
|
|
298
|
+
end
|
|
299
|
+
@codeptr = addr
|
|
300
|
+
|
|
301
|
+
if @rs.findfilemap(addr) == '???'
|
|
302
|
+
base = addr & 0xffff_f000
|
|
303
|
+
@noelfsig ||= {} # cache elfmagic notfound
|
|
304
|
+
if not @noelfsig[base] and base < 0xc000_0000
|
|
305
|
+
self.statusline = " scanning for elf header at #{'%08X' % base}"
|
|
306
|
+
128.times {
|
|
307
|
+
@statusline = " scanning for elf header at #{'%08X' % base}"
|
|
308
|
+
if not @noelfsig[base] and @rs[base, 4] == Metasm::ELF::MAGIC
|
|
309
|
+
@rs.loadsyms(base, base.to_s(16))
|
|
310
|
+
break
|
|
311
|
+
else
|
|
312
|
+
@noelfsig[base] = true # XXX an elf may be mmaped here later..
|
|
313
|
+
end
|
|
314
|
+
base -= 0x1000
|
|
315
|
+
break if base < 0
|
|
316
|
+
}
|
|
317
|
+
self.statusline = nil
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
text = ''
|
|
322
|
+
text << Color[:border]
|
|
323
|
+
title = @rs.findsymbol(addr)
|
|
324
|
+
pre = [@console_width-100, 6].max
|
|
325
|
+
post = @console_width - (pre + title.length + 2)
|
|
326
|
+
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
|
|
327
|
+
|
|
328
|
+
cnt = @win_code_height
|
|
329
|
+
while (cnt -= 1) > 0
|
|
330
|
+
if @rs.symbols[addr]
|
|
331
|
+
text << (' ' << @rs.symbols[addr] << ?:) << Ansi::ClearLineAfter << "\n"
|
|
332
|
+
break if (cnt -= 1) <= 0
|
|
333
|
+
end
|
|
334
|
+
text << Color[:hilight] if addr == @rs.regs_cache['eip']
|
|
335
|
+
text << ('%04X' % @rs.regs_cache['cs']) << ':'
|
|
336
|
+
text << ('%08X' % addr)
|
|
337
|
+
di = @rs.mnemonic_di(addr)
|
|
338
|
+
di = nil if di and addr < @rs.regs_cache['eip'] and addr+di.bin_length > @rs.regs_cache['eip']
|
|
339
|
+
len = (di ? di.bin_length : 1)
|
|
340
|
+
text << ' '
|
|
341
|
+
text << @rs[addr, [len, 10].min].unpack('C*').map { |c| '%02X' % c }.join.ljust(22)
|
|
342
|
+
if di
|
|
343
|
+
text <<
|
|
344
|
+
if addr == @rs.regs_cache['eip']
|
|
345
|
+
"*#{di.instruction}".ljust([@console_width-37, 0].max)
|
|
346
|
+
else
|
|
347
|
+
" #{di.instruction}" << Ansi::ClearLineAfter
|
|
348
|
+
end
|
|
349
|
+
else
|
|
350
|
+
text << ' <unk>' << Ansi::ClearLineAfter
|
|
351
|
+
end
|
|
352
|
+
text << Color[:normal] if addr == @rs.regs_cache['eip']
|
|
353
|
+
addr += len
|
|
354
|
+
text << "\n"
|
|
355
|
+
end
|
|
356
|
+
text
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def updatedata
|
|
360
|
+
once(:updatedata, "...\n"*@win_data_height) { _updatedata }
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def _updatedata
|
|
364
|
+
@dataptr &= 0xffff_ffff
|
|
365
|
+
addr = @dataptr
|
|
366
|
+
|
|
367
|
+
text = ''
|
|
368
|
+
text << Color[:border]
|
|
369
|
+
title = @rs.findsymbol(addr)
|
|
370
|
+
pre = [@console_width-100, 6].max
|
|
371
|
+
post = [@console_width - (pre + title.length + 2), 0].max
|
|
372
|
+
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
|
|
373
|
+
|
|
374
|
+
cnt = @win_data_height
|
|
375
|
+
while (cnt -= 1) > 0
|
|
376
|
+
raw = @rs[addr, 16].to_s
|
|
377
|
+
text << ('%04X' % @rs.regs_cache['ds']) << ':' << ('%08X' % addr) << ' '
|
|
378
|
+
case @datafmt
|
|
379
|
+
when 'db'; text << raw[0,8].unpack('C*').map { |c| '%02x ' % c }.join << ' ' <<
|
|
380
|
+
raw[8,8].to_s.unpack('C*').map { |c| '%02x ' % c }.join
|
|
381
|
+
when 'dw'; text << raw.unpack('S*').map { |c| '%04x ' % c }.join
|
|
382
|
+
when 'dd'; text << raw.unpack('L*').map { |c| '%08x ' % c }.join
|
|
383
|
+
end
|
|
384
|
+
text << ' ' << raw.unpack('C*').map { |c| (0x20..0x7e).include?(c) ? c : ?. }.pack('C*')
|
|
385
|
+
text << Ansi::ClearLineAfter << "\n"
|
|
386
|
+
addr += 16
|
|
387
|
+
end
|
|
388
|
+
text
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def updateprompt
|
|
392
|
+
once(:updateprompt, "\n"*@win_prpt_height) { _updateprompt }
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def _updateprompt
|
|
396
|
+
text = ''
|
|
397
|
+
text << Color[:border] << Ansi.hline(@console_width) << Color[:normal] << "\n"
|
|
398
|
+
|
|
399
|
+
@log_off = @promptlog.length - 2 if @log_off >= @promptlog.length
|
|
400
|
+
@log_off = 0 if @log_off < 0
|
|
401
|
+
len = @win_prpt_height - 2
|
|
402
|
+
len.times { |i|
|
|
403
|
+
i += @promptlog.length - @log_off - len
|
|
404
|
+
text << ((@promptlog[i] if i >= 0) || '')
|
|
405
|
+
text << Ansi::ClearLineAfter << "\n"
|
|
406
|
+
}
|
|
407
|
+
text << ':' << @promptbuf << Ansi::ClearLineAfter << "\n"
|
|
408
|
+
text << Color[:status] << statusline.chomp.ljust(@console_width) << Color[:normal]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def statusline
|
|
412
|
+
@statusline ||= ' Enter a command (help for help)'
|
|
413
|
+
end
|
|
414
|
+
def statusline=(s)
|
|
415
|
+
@statusline = s
|
|
416
|
+
update
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def resize
|
|
420
|
+
@console_height, @console_width = Ansi.get_terminal_size
|
|
421
|
+
@win_data_height = 1 if @win_data_height < 1
|
|
422
|
+
@win_code_height = 1 if @win_code_height < 1
|
|
423
|
+
if @win_data_height + @win_code_height + 5 > @console_height
|
|
424
|
+
@win_data_height = @console_height/2 - 4
|
|
425
|
+
@win_code_height = @console_height/2 - 4
|
|
426
|
+
end
|
|
427
|
+
@win_prpt_height = @console_height-(@win_data_height+@win_code_height+2) - 1
|
|
428
|
+
@oldscreenbuf = []
|
|
429
|
+
update
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def log(*strs)
|
|
433
|
+
strs.each { |str|
|
|
434
|
+
raise str.inspect if not str.kind_of? ::String
|
|
435
|
+
str = str.chomp
|
|
436
|
+
if str.length > @console_width
|
|
437
|
+
# word wrap
|
|
438
|
+
str.scan(/.{0,#@console_width}/) { |str_| log str_ }
|
|
439
|
+
return
|
|
440
|
+
end
|
|
441
|
+
@promptlog << str
|
|
442
|
+
@promptlog.shift if @promptlog.length > @promptloglen
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def puts(*s)
|
|
447
|
+
s.each { |s_| log s_.to_s }
|
|
448
|
+
super(*s) if not @running
|
|
449
|
+
update rescue super(*s)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def mem_binding(expr)
|
|
453
|
+
b = @rs.regs_cache.dup
|
|
454
|
+
ext = expr.externals
|
|
455
|
+
(ext - @rs.regs_cache.keys).each { |ex|
|
|
456
|
+
if not s = @rs.symbols.index(ex)
|
|
457
|
+
near = @rs.symbols.values.grep(/#{ex}/i)
|
|
458
|
+
if near.length > 1
|
|
459
|
+
log "#{ex.inspect} is ambiguous: #{near.inspect}"
|
|
460
|
+
return {}
|
|
461
|
+
elsif near.empty?
|
|
462
|
+
log "unknown value #{ex.inspect}"
|
|
463
|
+
return {}
|
|
464
|
+
else
|
|
465
|
+
log "using #{near.first.inspect} for #{ex.inspect}"
|
|
466
|
+
s = @rs.symbols.index(near.first)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
b[ex] = s
|
|
470
|
+
}
|
|
471
|
+
b['tracer_memory'] = @rs
|
|
472
|
+
b
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def exec_prompt
|
|
476
|
+
@log_off = 0
|
|
477
|
+
log ':'+@promptbuf
|
|
478
|
+
return if @promptbuf == ''
|
|
479
|
+
lex = Metasm::Preprocessor.new.feed @promptbuf
|
|
480
|
+
@prompthistory << @promptbuf
|
|
481
|
+
@prompthistory.shift if @prompthistory.length > @prompthistlen
|
|
482
|
+
@promptbuf = ''
|
|
483
|
+
@promptpos = @promptbuf.length
|
|
484
|
+
argint = lambda {
|
|
485
|
+
begin
|
|
486
|
+
raise if not e = ExprParser.parse(lex)
|
|
487
|
+
rescue
|
|
488
|
+
log 'syntax error'
|
|
489
|
+
return
|
|
490
|
+
end
|
|
491
|
+
e = e.bind(mem_binding(e)).reduce
|
|
492
|
+
if e.kind_of? Integer; e
|
|
493
|
+
else log "could not resolve #{e.inspect}" ; nil
|
|
494
|
+
end
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
cmd = lex.readtok
|
|
498
|
+
cmd = cmd.raw if cmd
|
|
499
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
|
500
|
+
lex.unreadtok ntok
|
|
501
|
+
if @command.has_key? cmd
|
|
502
|
+
@command[cmd].call(lex, argint)
|
|
503
|
+
else
|
|
504
|
+
if cmd and (poss = @command.keys.find_all { |c| c[0, cmd.length] == cmd }).length == 1
|
|
505
|
+
@command[poss.first].call(lex, argint)
|
|
506
|
+
else
|
|
507
|
+
log 'unknown command'
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def updatecodeptr
|
|
513
|
+
@codeptr ||= @rs.regs_cache['eip']
|
|
514
|
+
if @codeptr > @rs.regs_cache['eip'] or @codeptr < @rs.regs_cache['eip'] - 6*@win_code_height
|
|
515
|
+
@codeptr = @rs.regs_cache['eip']
|
|
516
|
+
elsif @codeptr != @rs.regs_cache['eip']
|
|
517
|
+
addr = @codeptr
|
|
518
|
+
addrs = []
|
|
519
|
+
while addr < @rs.regs_cache['eip']
|
|
520
|
+
addrs << addr
|
|
521
|
+
o = ((di = @rs.mnemonic_di(addr)) ? di.bin_length : 0)
|
|
522
|
+
addr += ((o == 0) ? 1 : o)
|
|
523
|
+
end
|
|
524
|
+
if addrs.length > @win_code_height-4
|
|
525
|
+
@codeptr = addrs[-(@win_code_height-4)]
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
updatedataptr
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def updatedataptr
|
|
532
|
+
@dataptr = @watch.bind(mem_binding(@watch)).reduce if @watch
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def singlestep
|
|
536
|
+
self.statusline = ' target singlestepping...'
|
|
537
|
+
@rs.singlestep
|
|
538
|
+
updatecodeptr
|
|
539
|
+
@statusline = nil
|
|
540
|
+
end
|
|
541
|
+
def stepover
|
|
542
|
+
self.statusline = ' target running...'
|
|
543
|
+
@rs.stepover
|
|
544
|
+
updatecodeptr
|
|
545
|
+
@statusline = nil
|
|
546
|
+
end
|
|
547
|
+
def cont(*a)
|
|
548
|
+
self.statusline = ' target running...'
|
|
549
|
+
@rs.cont(*a)
|
|
550
|
+
updatecodeptr
|
|
551
|
+
@statusline = nil
|
|
552
|
+
end
|
|
553
|
+
def stepout
|
|
554
|
+
self.statusline = ' target running...'
|
|
555
|
+
@rs.stepout
|
|
556
|
+
updatecodeptr
|
|
557
|
+
@statusline = nil
|
|
558
|
+
end
|
|
559
|
+
def syscall
|
|
560
|
+
self.statusline = ' target running to next syscall...'
|
|
561
|
+
@rs.syscall
|
|
562
|
+
updatecodeptr
|
|
563
|
+
@statusline = nil
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def main_loop_inner
|
|
567
|
+
@prompthistory = ['']
|
|
568
|
+
@histptr = nil
|
|
569
|
+
@running = true
|
|
570
|
+
update
|
|
571
|
+
while @running
|
|
572
|
+
if not IO.select [$stdin], nil, nil, 0
|
|
573
|
+
begin
|
|
574
|
+
update
|
|
575
|
+
rescue Errno::ESRCH
|
|
576
|
+
break
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
break if handle_keypress(Ansi.getkey)
|
|
580
|
+
end
|
|
581
|
+
@rs.checkbp
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def handle_keypress(k)
|
|
585
|
+
case k
|
|
586
|
+
when 4; log 'exiting'; return true # eof
|
|
587
|
+
when ?\e; focus = :prompt
|
|
588
|
+
when :f5; cont
|
|
589
|
+
when :f6
|
|
590
|
+
syscall
|
|
591
|
+
log @rs.syscallnr.index(@rs.regs_cache['orig_eax']) || @rs.regs_cache['orig_eax'].to_s
|
|
592
|
+
when :f10; stepover
|
|
593
|
+
when :f11; singlestep
|
|
594
|
+
when :f12; stepout
|
|
595
|
+
when :up
|
|
596
|
+
case @focus
|
|
597
|
+
when :prompt
|
|
598
|
+
if not @histptr
|
|
599
|
+
@prompthistory << @promptbuf
|
|
600
|
+
@histptr = 2
|
|
601
|
+
else
|
|
602
|
+
@histptr += 1
|
|
603
|
+
@histptr = 1 if @histptr > @prompthistory.length
|
|
604
|
+
end
|
|
605
|
+
@promptbuf = @prompthistory[-@histptr].dup
|
|
606
|
+
@promptpos = @promptbuf.length
|
|
607
|
+
when :data
|
|
608
|
+
@dataptr -= 16
|
|
609
|
+
when :code
|
|
610
|
+
@codeptr ||= @rs.regs_cache['eip']
|
|
611
|
+
@codeptr -= (1..10).find { |off|
|
|
612
|
+
di = @rs.mnemonic_di(@codeptr-off)
|
|
613
|
+
di.bin_length == off if di
|
|
614
|
+
} || 10
|
|
615
|
+
end
|
|
616
|
+
when :down
|
|
617
|
+
case @focus
|
|
618
|
+
when :prompt
|
|
619
|
+
if not @histptr
|
|
620
|
+
@prompthistory << @promptbuf
|
|
621
|
+
@histptr = @prompthistory.length
|
|
622
|
+
else
|
|
623
|
+
@histptr -= 1
|
|
624
|
+
@histptr = @prompthistory.length if @histptr < 1
|
|
625
|
+
end
|
|
626
|
+
@promptbuf = @prompthistory[-@histptr].dup
|
|
627
|
+
@promptpos = @promptbuf.length
|
|
628
|
+
when :data
|
|
629
|
+
@dataptr += 16
|
|
630
|
+
when :code
|
|
631
|
+
@codeptr ||= @rs.regs_cache['eip']
|
|
632
|
+
di = @rs.mnemonic_di(@codeptr)
|
|
633
|
+
@codeptr += (di ? (di.bin_length || 1) : 1)
|
|
634
|
+
end
|
|
635
|
+
when :left; @promptpos -= 1 if @promptpos > 0
|
|
636
|
+
when :right; @promptpos += 1 if @promptpos < @promptbuf.length
|
|
637
|
+
when :home; @promptpos = 0
|
|
638
|
+
when :end; @promptpos = @promptbuf.length
|
|
639
|
+
when :backspace, 0x7f; @promptbuf[@promptpos-=1, 1] = '' if @promptpos > 0
|
|
640
|
+
when :suppr; @promptbuf[@promptpos, 1] = '' if @promptpos < @promptbuf.length
|
|
641
|
+
when :pgup
|
|
642
|
+
case @focus
|
|
643
|
+
when :prompt; @log_off += @win_prpt_height-3
|
|
644
|
+
when :data; @dataptr -= 16*(@win_data_height-1)
|
|
645
|
+
when :code
|
|
646
|
+
@codeptr ||= @rs.regs_cache['eip']
|
|
647
|
+
(@win_code_height-1).times {
|
|
648
|
+
@codeptr -= (1..10).find { |off|
|
|
649
|
+
di = @rs.mnemonic_di(@codeptr-off)
|
|
650
|
+
di.bin_length == off if di
|
|
651
|
+
} || 10
|
|
652
|
+
}
|
|
653
|
+
end
|
|
654
|
+
when :pgdown
|
|
655
|
+
case @focus
|
|
656
|
+
when :prompt; @log_off -= @win_prpt_height-3
|
|
657
|
+
when :data; @dataptr += 16*(@win_data_height-1)
|
|
658
|
+
when :code
|
|
659
|
+
@codeptr ||= @rs.regs_cache['eip']
|
|
660
|
+
(@win_code_height-1).times { @codeptr += ((o = @rs.mnemonic_di(@codeptr)) ? [o.bin_length, 1].max : 1) }
|
|
661
|
+
end
|
|
662
|
+
when ?\t
|
|
663
|
+
if not @promptbuf[0, @promptpos].include? ' '
|
|
664
|
+
poss = @command.keys.find_all { |c| c[0, @promptpos] == @promptbuf[0, @promptpos] }
|
|
665
|
+
if poss.length > 1
|
|
666
|
+
log poss.sort.join(' ')
|
|
667
|
+
elsif poss.length == 1
|
|
668
|
+
@promptbuf[0, @promptpos] = poss.first + ' '
|
|
669
|
+
@promptpos = poss.first.length+1
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
when ?\n
|
|
673
|
+
@histptr = nil
|
|
674
|
+
begin
|
|
675
|
+
exec_prompt
|
|
676
|
+
rescue Exception
|
|
677
|
+
log "error: #$!", *$!.backtrace
|
|
678
|
+
end
|
|
679
|
+
when 0x20..0x7e
|
|
680
|
+
@promptbuf[@promptpos, 0] = k.chr
|
|
681
|
+
@promptpos += 1
|
|
682
|
+
else log "unknown key pressed #{k.inspect}"
|
|
683
|
+
end
|
|
684
|
+
nil
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def load_commands
|
|
688
|
+
ntok = nil
|
|
689
|
+
@command['kill'] = lambda { |lex, int|
|
|
690
|
+
@rs.kill
|
|
691
|
+
@running = false
|
|
692
|
+
log 'killed'
|
|
693
|
+
}
|
|
694
|
+
@command['quit'] = @command['detach'] = @command['exit'] = lambda { |lex, int|
|
|
695
|
+
@rs.detach
|
|
696
|
+
@running = false
|
|
697
|
+
}
|
|
698
|
+
@command['closeui'] = lambda { |lex, int|
|
|
699
|
+
@rs.logger = nil
|
|
700
|
+
@running = false
|
|
701
|
+
}
|
|
702
|
+
@command['bpx'] = lambda { |lex, int|
|
|
703
|
+
addr = int[]
|
|
704
|
+
@rs.bpx addr
|
|
705
|
+
}
|
|
706
|
+
@command['bphw'] = lambda { |lex, int|
|
|
707
|
+
type = lex.readtok.raw
|
|
708
|
+
addr = int[]
|
|
709
|
+
@rs.set_hwbp type, addr
|
|
710
|
+
}
|
|
711
|
+
@command['bl'] = lambda { |lex, int|
|
|
712
|
+
log "bpx at #{@rs.findsymbol(@rs.wantbp)}" if @rs.wantbp.kind_of? ::Integer
|
|
713
|
+
@rs.breakpoints.sort.each { |addr, oct|
|
|
714
|
+
log "bpx at #{@rs.findsymbol(addr)}"
|
|
715
|
+
}
|
|
716
|
+
(0..3).each { |dr|
|
|
717
|
+
if @rs.regs_cache['dr7'] & (1 << (2*dr)) != 0
|
|
718
|
+
log "bphw #{{0=>'x', 1=>'w', 2=>'?', 3=>'r'}[(@rs.regs_cache['dr7'] >> (16+4*dr)) & 3]} at #{@rs.findsymbol(@rs.regs_cache["dr#{dr}"])}"
|
|
719
|
+
end
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
@command['bc'] = lambda { |lex, int|
|
|
723
|
+
@rs.clearbreaks
|
|
724
|
+
}
|
|
725
|
+
@command['bt'] = lambda { |lex, int| @rs.backtrace { |t| puts t } }
|
|
726
|
+
@command['d'] = lambda { |lex, int| @dataptr = int[] || return }
|
|
727
|
+
@command['db'] = lambda { |lex, int| @datafmt = 'db' ; @dataptr = int[] || return }
|
|
728
|
+
@command['dw'] = lambda { |lex, int| @datafmt = 'dw' ; @dataptr = int[] || return }
|
|
729
|
+
@command['dd'] = lambda { |lex, int| @datafmt = 'dd' ; @dataptr = int[] || return }
|
|
730
|
+
@command['r'] = lambda { |lex, int|
|
|
731
|
+
r = lex.readtok.raw
|
|
732
|
+
nil while ntok = lex.readtok and ntok.type == :space
|
|
733
|
+
if r == 'fl'
|
|
734
|
+
flag = ntok.raw
|
|
735
|
+
if i = Rubstop::EFLAGS.index(flag)
|
|
736
|
+
@rs.eflags ^= 1 << i
|
|
737
|
+
@rs.readregs
|
|
738
|
+
else
|
|
739
|
+
log "bad flag #{flag}"
|
|
740
|
+
end
|
|
741
|
+
elsif not @rs.regs_cache[r]
|
|
742
|
+
log "bad reg #{r}"
|
|
743
|
+
elsif ntok
|
|
744
|
+
lex.unreadtok ntok
|
|
745
|
+
newval = int[]
|
|
746
|
+
if newval and newval.kind_of? ::Integer
|
|
747
|
+
@rs.send r+'=', newval
|
|
748
|
+
@rs.readregs
|
|
749
|
+
end
|
|
750
|
+
else
|
|
751
|
+
log "#{r} = #{@rs.regs_cache[r]}"
|
|
752
|
+
end
|
|
753
|
+
}
|
|
754
|
+
@command['run'] = @command['cont'] = lambda { |lex, int|
|
|
755
|
+
if tok = lex.readtok
|
|
756
|
+
lex.unreadtok tok
|
|
757
|
+
cont int[]
|
|
758
|
+
else cont
|
|
759
|
+
end
|
|
760
|
+
}
|
|
761
|
+
@command['syscall'] = lambda { |lex, int| syscall }
|
|
762
|
+
@command['singlestep'] = lambda { |lex, int| singlestep }
|
|
763
|
+
@command['stepover'] = lambda { |lex, int| stepover }
|
|
764
|
+
@command['stepout'] = lambda { |lex, int| stepout }
|
|
765
|
+
@command['g'] = lambda { |lex, int|
|
|
766
|
+
target = int[]
|
|
767
|
+
@rs.singlestep if @rs.regs_cache['eip'] == target
|
|
768
|
+
@rs.bpx target, true
|
|
769
|
+
cont
|
|
770
|
+
}
|
|
771
|
+
@command['u'] = lambda { |lex, int| @codeptr = int[] || break }
|
|
772
|
+
@command['has_pax'] = lambda { |lex, int|
|
|
773
|
+
if tok = lex.readtok
|
|
774
|
+
lex.unreadtok tok
|
|
775
|
+
if (int[] == 0)
|
|
776
|
+
@rs.set_pax false
|
|
777
|
+
else
|
|
778
|
+
@rs.set_pax true
|
|
779
|
+
end
|
|
780
|
+
else @rs.set_pax !@rs.has_pax
|
|
781
|
+
end
|
|
782
|
+
log "has_pax now #{@rs.has_pax}"
|
|
783
|
+
}
|
|
784
|
+
@command['loadsyms'] = lambda { |lex, int|
|
|
785
|
+
mapfile = ''
|
|
786
|
+
mapfile << ntok.raw while ntok = lex.readtok
|
|
787
|
+
if mapfile != ''
|
|
788
|
+
@rs.loadmap mapfile
|
|
789
|
+
else
|
|
790
|
+
@rs.loadallsyms
|
|
791
|
+
end
|
|
792
|
+
}
|
|
793
|
+
@command['scansyms'] = lambda { |lex, int| @rs.scansyms }
|
|
794
|
+
@command['sym'] = lambda { |lex, int|
|
|
795
|
+
sym = ''
|
|
796
|
+
sym << ntok.raw while ntok = lex.readtok
|
|
797
|
+
s = []
|
|
798
|
+
@rs.symbols.each { |k, v|
|
|
799
|
+
s << k if v =~ /#{sym}/
|
|
800
|
+
}
|
|
801
|
+
if s.empty?
|
|
802
|
+
log "unknown symbol #{sym}"
|
|
803
|
+
else
|
|
804
|
+
s.sort.each { |s_| log "#{'%08x' % s_} #{@rs.symbols_len[s_].to_s.ljust 6} #{@rs.findsymbol(s_)}" }
|
|
805
|
+
end
|
|
806
|
+
}
|
|
807
|
+
@command['delsym'] = lambda { |lex, int|
|
|
808
|
+
addr = int[]
|
|
809
|
+
log "deleted #{@rs.symbols.delete addr}"
|
|
810
|
+
@rs.symbols_len.delete addr
|
|
811
|
+
}
|
|
812
|
+
@command['addsym'] = lambda { |lex, int|
|
|
813
|
+
name = lex.readtok.raw
|
|
814
|
+
addr = int[]
|
|
815
|
+
if t = lex.readtok
|
|
816
|
+
lex.unreadtok t
|
|
817
|
+
@rs.symbols_len[addr] = int[]
|
|
818
|
+
else
|
|
819
|
+
@rs.symbols_len[addr] = 1
|
|
820
|
+
end
|
|
821
|
+
@rs.symbols[addr] = name
|
|
822
|
+
}
|
|
823
|
+
@command['help'] = lambda { |lex, int|
|
|
824
|
+
log 'commands: (addr/values are things like dword ptr [ebp+(4*byte [eax])] ), type <tab> to see all commands'
|
|
825
|
+
log ' bpx <addr>'
|
|
826
|
+
log ' bphw [r|w|x] <addr>: debug register breakpoint'
|
|
827
|
+
log ' bl: list breakpoints'
|
|
828
|
+
log ' bc: clear breakpoints'
|
|
829
|
+
log ' cont [<signr>]: continue the target sending a signal'
|
|
830
|
+
log ' d/db/dw/dd [<addr>]: change data type/address'
|
|
831
|
+
log ' g <addr>: set a bp at <addr> and run'
|
|
832
|
+
log ' has_pax [0|1]: set has_pax flag'
|
|
833
|
+
log ' loadsyms: load symbol information from mapped files (from /proc and disk)'
|
|
834
|
+
log ' ma <addr> <ascii>: write memory'
|
|
835
|
+
log ' mx <addr> <hex>: write memory'
|
|
836
|
+
log ' maps: list maps'
|
|
837
|
+
log ' r <reg> [<value>]: show/change register'
|
|
838
|
+
log ' r fl <flag>: toggle eflags bit'
|
|
839
|
+
log ' scansyms: scan memory for ELF headers'
|
|
840
|
+
log ' sym <symbol regex>: show symbol information'
|
|
841
|
+
log ' addsym <name> <addr> [<size>]'
|
|
842
|
+
log ' delsym <addr>'
|
|
843
|
+
log ' u <addr>: disassemble addr'
|
|
844
|
+
log ' reload: reload lindebug source'
|
|
845
|
+
log ' ruby <ruby code>: instance_evals ruby code in current instance'
|
|
846
|
+
log ' closeui: detach from the underlying RubStop'
|
|
847
|
+
log 'keys:'
|
|
848
|
+
log ' F5: continue'
|
|
849
|
+
log ' F6: syscall'
|
|
850
|
+
log ' F10: step over'
|
|
851
|
+
log ' F11: single step'
|
|
852
|
+
log ' F12: step out (til next ret)'
|
|
853
|
+
log ' pgup/pgdown: move command history'
|
|
854
|
+
}
|
|
855
|
+
@command['reload'] = lambda { |lex, int| load $0 ; load_commands }
|
|
856
|
+
@command['ruby'] = lambda { |lex, int|
|
|
857
|
+
str = ''
|
|
858
|
+
str << ntok.raw while ntok = lex.readtok
|
|
859
|
+
instance_eval str
|
|
860
|
+
}
|
|
861
|
+
@command['maps'] = lambda { |lex, int|
|
|
862
|
+
@rs.filemap.sort_by { |f, (b, e)| b }.each { |f, (b, e)|
|
|
863
|
+
log "#{f.ljust 20} #{'%08x' % b} - #{'%08x' % e}"
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
@command['ma'] = lambda { |lex, int|
|
|
867
|
+
addr = int[]
|
|
868
|
+
str = ''
|
|
869
|
+
str << ntok.raw while ntok = lex.readtok
|
|
870
|
+
@rs[addr, str.length] = str
|
|
871
|
+
}
|
|
872
|
+
@command['mx'] = lambda { |lex, int|
|
|
873
|
+
addr = int[]
|
|
874
|
+
data = [lex.readtok.raw].pack('H*')
|
|
875
|
+
@rs[addr, data.length] = data
|
|
876
|
+
}
|
|
877
|
+
@command['resize'] = lambda { |lex, int| resize }
|
|
878
|
+
@command['watch'] = lambda { |lex, int| @watch = ExprParser.parse(lex) ; updatedataptr }
|
|
879
|
+
@command['wd'] = lambda { |lex, int|
|
|
880
|
+
@focus = :data
|
|
881
|
+
if tok = lex.readtok
|
|
882
|
+
lex.unreadtok tok
|
|
883
|
+
@win_data_height = int[] || return
|
|
884
|
+
resize
|
|
885
|
+
end
|
|
886
|
+
}
|
|
887
|
+
@command['wc'] = lambda { |lex, int|
|
|
888
|
+
@focus = :code
|
|
889
|
+
if tok = lex.readtok
|
|
890
|
+
lex.unreadtok tok
|
|
891
|
+
@win_code_height = int[] || return
|
|
892
|
+
resize
|
|
893
|
+
end
|
|
894
|
+
}
|
|
895
|
+
@command['wp'] = lambda { |lex, int| @focus = :prompt }
|
|
896
|
+
@command['?'] = lambda { |lex, int|
|
|
897
|
+
val = int[]
|
|
898
|
+
log "#{val} 0x#{val.to_s(16)} #{[val].pack('L').inspect}"
|
|
899
|
+
}
|
|
900
|
+
@command['.'] = lambda { |lex, int| @codeptr = nil }
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
if $0 == __FILE__
|
|
906
|
+
require 'optparse'
|
|
907
|
+
filemap = nil
|
|
908
|
+
OptionParser.new { |opt|
|
|
909
|
+
opt.on('-m map', '--map filemap') { |f| filemap = f }
|
|
910
|
+
}.parse!(ARGV)
|
|
911
|
+
|
|
912
|
+
if not defined? Rubstop
|
|
913
|
+
if ARGV.first =~ /:/
|
|
914
|
+
stub = 'gdbclient'
|
|
915
|
+
else
|
|
916
|
+
stub = 'rubstop'
|
|
917
|
+
end
|
|
918
|
+
require File.join(File.dirname(__FILE__), stub)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
rs = Rubstop.new(ARGV.join(' '))
|
|
922
|
+
rs.loadmap(filemap) if filemap
|
|
923
|
+
LinDebug.new(rs).main_loop
|
|
924
|
+
end
|