bring metasm to tip

git-svn-id: file:///home/svn/framework3/trunk@10600 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Joshua Drake 2010-10-08 16:01:37 +00:00
parent d8b9cf5cac
commit 7f9fe3b527
31 changed files with 2254 additions and 657 deletions

View File

@ -47,8 +47,7 @@ module Metasm
'X86_64' => 'x86_64', 'Sh4' => 'sh4', 'Dalvik' => 'dalvik',
'C' => ['parse_c', 'compile_c'],
'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe',
'ELF' => ['exe_format/elf_encode', 'exe_format/elf_decode'],
'COFF' => ['exe_format/coff_encode', 'exe_format/coff_decode'],
'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff',
'Shellcode' => 'exe_format/shellcode', 'AutoExe' => 'exe_format/autoexe',
'AOut' => 'exe_format/a_out', 'MachO' => 'exe_format/macho',
'DEX' => 'exe_format/dex',
@ -76,7 +75,6 @@ def self.autorequire_const_missing(c)
const_get c
end
def self.require(f)
# temporarily put the current file directory in the ruby include path
if not $:.include? Metasmdir
@ -121,6 +119,10 @@ require 'metasm/os/main'
# remove an 1.9 warning, couldn't find a compatible way...
if {}.respond_to? :key
puts "using ruby1.9 workaround for Hash.index" if $DEBUG
class Hash ; alias index key end
puts "using ruby1.9 workaround for Hash#index warning" if $DEBUG
class Hash
alias index_premetasm index rescue nil
undef index rescue nil
alias index key
end
end

View File

@ -204,6 +204,9 @@ class Decompiler
def new_global_var(addr, type, scope=nil)
addr = @dasm.normalize(addr)
# (almost) NULL ptr
return if addr.kind_of? Fixnum and addr >= 0 and addr < 32
# check preceding structure we're hitting
# TODO check what we step over when defining a new static struct
0x100.times { |i_|
@ -485,7 +488,7 @@ class Decompiler
when C::Goto
if jumpto[s.target]
r = jumpto[s.target].dup
r.value = C::CExpression[r.value.reduce(@c_parser)] if r.kind_of? C::Return and r.value # deep_dup
r.value = r.value.deep_dup if r.kind_of? C::Return and r.value.kind_of? C::CExpression
r
end
when C::Return

View File

@ -657,6 +657,8 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace
split_block(di.block, di.address) if not di.block_head? # this updates di.block
di.block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default
bf = di.block
elsif di == true
bf = @function[addr]
end
elsif bf = @function[addr]
detect_function_thunk_noreturn(from) if bf.noreturn
@ -1943,20 +1945,23 @@ puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtra
vals = []
edata.ptr = off
dups = dumplen/elemlen
elemsym = "u#{elemlen*8}".to_sym
while edata.ptr < edata.data.length
if vals.length > dups and vals.uniq.length > 1
if vals.length > dups and vals.last != vals.first
# we have a dup(), unread the last element which is different
vals.pop
addr = Expression[addr, :-, elemlen].reduce
edata.ptr -= elemlen
break
end
break if vals.length == dups and vals.uniq.length > 1
vals << edata.decode_imm("u#{elemlen*8}".to_sym, @cpu.endianness)
vals << edata.decode_imm(elemsym, @cpu.endianness)
addr += elemlen
if i = (1-elemlen..0).find { |i_|
t = addr + i_
@xrefs[t] or @decoded[t] or edata.reloc[edata.ptr+i_] or edata.inv_export[edata.ptr+i_]
}
# i < 0
edata.ptr += i
addr += i
break

View File

@ -700,18 +700,11 @@ class Disassembler
found = []
@sections.each { |sec_addr, e|
chunkoff = 0
while chunkoff < e.data.length
chunk = e.data[chunkoff, chunksz+margin].to_str
off = 0
while match_off = (chunk[off..-1] =~ pat)
break if off+match_off >= chunksz
match_addr = sec_addr + chunkoff + off + match_off
e.pattern_scan(pat, chunksz, margin) { |eo|
match_addr = sec_addr + eo
found << match_addr if not block_given? or yield(match_addr)
off += match_off + 1
end
chunkoff += chunksz
end
false
}
}
found
end

View File

@ -52,7 +52,12 @@ extern VALUE *rb_eArgError __attribute__((import));
#define Qtrue ((VALUE)2)
#define Qnil ((VALUE)4)
#if #{RUBY_VERSION >= '1.9' ? 1 : 0}
// allows generating a ruby1.9 dynldr.so from ruby1.8
#ifndef DYNLDR_RUBY_19
#define DYNLDR_RUBY_19 #{RUBY_VERSION >= '1.9' ? 1 : 0}
#endif
#if DYNLDR_RUBY_19
#define T_STRING 0x05
#define T_ARRAY 0x07
#define T_FIXNUM 0x15
@ -78,7 +83,7 @@ extern VALUE *rb_eArgError __attribute__((import));
VALUE rb_uint2inum(VALUE);
VALUE rb_ull2inum(unsigned long long);
VALUE rb_num2ulong(VALUE);
VALUE rb_str_new(const char* ptr, unsigned long len); // alloc + memcpy + 0term
VALUE rb_str_new(const char* ptr, long len); // alloc + memcpy + 0term
VALUE rb_ary_new2(int len);
VALUE rb_float_new(double);
@ -128,7 +133,7 @@ static VALUE dynldr;
static VALUE memory_read(VALUE self, VALUE addr, VALUE len)
{
return rb_str_new((char*)VAL2INT(addr), (unsigned long)VAL2INT(len));
return rb_str_new((char*)VAL2INT(addr), (long)VAL2INT(len));
}
static VALUE memory_read_int(VALUE self, VALUE addr)
@ -162,18 +167,34 @@ static VALUE str_ptr(VALUE self, VALUE str)
return INT2VAL((uintptr_t)STR_PTR(str));
}
// return the VALUE of an object (different of .object_id for Symbols, maybe others)
static VALUE rb_obj_to_value(VALUE self, VALUE obj)
{
return INT2VAL((uintptr_t)obj);
}
// return the ruby object at VALUE
// USE WITH CAUTION, passing invalid values will segfault the interpreter/GC
static VALUE rb_value_to_obj(VALUE self, VALUE val)
{
return VAL2INT(val);
}
// load a symbol from a lib byname, byordinal if integral
static VALUE sym_addr(VALUE self, VALUE lib, VALUE func)
{
uintptr_t h, p;
if (TYPE(lib) != T_STRING)
if (TYPE(lib) == T_STRING)
h = os_load_lib(STR_PTR(lib));
else if (TYPE(lib) == T_FIXNUM)
h = VAL2INT(lib);
else
rb_raise(*rb_eArgError, "Invalid lib");
if (TYPE(func) != T_STRING && TYPE(func) != T_FIXNUM)
rb_raise(*rb_eArgError, "Invalid func");
h = os_load_lib(STR_PTR(lib));
if (TYPE(func) == T_FIXNUM)
p = os_load_sym_ord(h, VAL2INT(func));
else
@ -322,6 +343,8 @@ int Init_dynldr(void) __attribute__((export_as(Init_<insertfilenamehere>))) // t
rb_define_singleton_method(dynldr, "memory_write", memory_write, 2);
rb_define_singleton_method(dynldr, "memory_write_int", memory_write_int, 2);
rb_define_singleton_method(dynldr, "str_ptr", str_ptr, 1);
rb_define_singleton_method(dynldr, "rb_obj_to_value", rb_obj_to_value, 1);
rb_define_singleton_method(dynldr, "rb_value_to_obj", rb_value_to_obj, 1);
rb_define_singleton_method(dynldr, "sym_addr", sym_addr, 2);
rb_define_singleton_method(dynldr, "raw_invoke", invoke, 3);
rb_define_const(dynldr, "CALLBACK_TARGET", INT2VAL((VALUE)&callback_handler));
@ -462,13 +485,9 @@ do_invoke_fastcall:
add eax, 8
mov [ebp+16], eax
mov eax,[ebp+12]
test eax, eax
jz _do_invoke_call
dec eax
test eax, eax
jz _do_invoke_call
dec eax
mov eax, [ebp+12]
sub eax, 2
jb _do_invoke_call
jmp _do_invoke_copy
do_invoke:
@ -583,7 +602,7 @@ EOS
def self.compile_binary_module_hack(bin)
# this is a hack
# we need the module to use ruby symbols
# but we don't know the actual lib filename (depends on ruby version,
# but we don't know the actual ruby lib filename (depends on ruby version,
# platform, ...)
case bin.class.name.gsub(/.*::/, '')
when 'ELF'
@ -626,7 +645,7 @@ EOS
bin.arch_encode_thunk(text, i) # encode a jmp [importtable]
end
# update to the offset table
# update the offset table
asm_table << "#{sym} #{dd} #{str_label} - ruby_import_table"
}
# dont forget the final 0
@ -701,13 +720,12 @@ EOS
# parse a C string into the @cp parser, create it if needed
def self.parse_c(src)
@cp ||= C::Parser.new(host_exe.new(host_cpu))
@cp.parse(src)
cp.parse(src)
end
# compile a C fragment into a Shellcode, honors the host ABI
def self.compile_c(src)
# XXX could we reuse @cp ? (for its macros etc)
# XXX could we reuse self.cp ? (for its macros etc)
cp = C::Parser.new(host_exe.new(host_cpu))
cp.parse(src)
sc = Shellcode.new(host_cpu)
@ -733,9 +751,9 @@ EOS
proto += "\n;" # allow 'int foo()' and '#include <bar>'
parse_c(proto)
@cp.toplevel.symbol.dup.each_value { |v|
cp.toplevel.symbol.dup.each_value { |v|
next if not v.kind_of? C::Variable # enums
@cp.toplevel.symbol.delete v.name
cp.toplevel.symbol.delete v.name
lib = fromlib || lib_from_sym(v.name)
addr = sym_addr(lib, v.name)
if addr == 0 or addr == -1 or addr == 0xffff_ffff or addr == 0xffffffff_ffffffff
@ -756,7 +774,7 @@ EOS
}
# constant definition from macro/enum
@cp.numeric_constants.each { |k, v|
cp.numeric_constants.each { |k, v|
n = k.upcase
n = "C#{n}" if n !~ /^[A-Z]/
const_set(n, v) if v.kind_of? Integer and not constants.map { |c| c.to_s }.include?(n)
@ -773,7 +791,7 @@ EOS
flags = 0
flags |= 1 if proto.has_attribute('stdcall')
flags |= 2 if proto.has_attribute('fastcall')
flags |= 4 if proto.type.type.integral? and @cp.sizeof(nil, proto.type.type) == 8
flags |= 4 if proto.type.type.integral? and cp.sizeof(nil, proto.type.type) == 8
flags |= 8 if proto.type.type.float?
class << self ; self ; end.send(:define_method, name) { |*a|
raise ArgumentError, "bad arg count for #{name}: #{a.length} for #{proto.type.args.length}" if a.length != proto.type.args.length and not proto.type.varargs
@ -792,10 +810,10 @@ EOS
when String; str_ptr(val)
when Proc; cb = callback_alloc_cobj(formal, val) ; (opts[:cb_list] ||= []) << cb ; cb
# TODO when Hash, Array; if formal.type.pointed.kind_of? C::Struct; yadda yadda ; end
else val.to_i
else val.to_i rescue 0 # NaN, Infinity, etc
end
if opts[:expand_i64] and formal and formal.type.integral? and @cp.sizeof(formal) == 8 and host_cpu.size == 32
if opts[:expand_i64] and formal and formal.type.integral? and cp.sizeof(formal) == 8 and host_cpu.size == 32
val = [val & 0xffff_ffff, (val >> 32) & 0xffff_ffff]
val.reverse! if host_cpu.endianness != :little
end
@ -822,7 +840,7 @@ EOS
# C raw cb arg -> ruby object
def self.convert_arg_c2rb(formal, rawargs)
val = rawargs.shift
if formal.type.integral? and @cp.sizeof(formal) == 64 and host_cpu.size == 32
if formal.type.integral? and cp.sizeof(formal) == 64 and host_cpu.size == 32
if host.cpu.endianness == :little
val |= rawargs.shift << 32
else
@ -841,7 +859,7 @@ EOS
ret
end
def self.cp; @cp ||= nil ; end
def self.cp ; @cp ||= C::Parser.new(host_exe.new(host_cpu)) ; end
def self.cp=(c); @cp = c ; end
# allocate a callback for a given C prototype (string)
@ -849,10 +867,10 @@ EOS
def self.callback_alloc_c(proto, &b)
proto += ';' # allow 'int foo()'
parse_c(proto)
v = @cp.toplevel.symbol.values.find_all { |v_| v_.kind_of? C::Variable and v_.type.kind_of? C::Function }.first
if (v and v.initializer) or @cp.toplevel.statements.find { |st| st.kind_of? C::Asm }
@cp.toplevel.statements.delete_if { |st| st.kind_of? C::Asm }
@cp.toplevel.symbol.delete v.name if v
v = cp.toplevel.symbol.values.find_all { |v_| v_.kind_of? C::Variable and v_.type.kind_of? C::Function }.first
if (v and v.initializer) or cp.toplevel.statements.find { |st| st.kind_of? C::Asm }
cp.toplevel.statements.delete_if { |st| st.kind_of? C::Asm }
cp.toplevel.symbol.delete v.name if v
sc = compile_c(proto)
ptr = memory_alloc(sc.encoded.length)
sc.base_addr = ptr
@ -863,7 +881,7 @@ EOS
elsif not v
raise 'empty prototype'
else
@cp.toplevel.symbol.delete v.name
cp.toplevel.symbol.delete v.name
callback_alloc_cobj(v, b)
end
end
@ -878,8 +896,8 @@ EOS
cb[:id] = id
cb[:proc] = b
cb[:proto] = proto
cb[:abi_stackfix] = proto.args.inject(0) { |s, a| s + [@cp.sizeof(a), @cp.typesize[:ptr]].max } if ori and ori.has_attribute('stdcall')
cb[:abi_stackfix] = proto.args[2..-1].to_a.inject(0) { |s, a| s + [@cp.sizeof(a), @cp.typesize[:ptr]].max } if ori and ori.has_attribute('fastcall') # supercedes stdcall
cb[:abi_stackfix] = proto.args.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('stdcall')
cb[:abi_stackfix] = proto.args[2..-1].to_a.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('fastcall') # supercedes stdcall
@@callback_table[id] = cb
id
end
@ -914,19 +932,21 @@ EOS
# compile a bunch of C functions, defines methods in this module to call them
# returns the raw pointer to the code page
# if given a block, run the block and then undefine all the C functions
# if given a block, run the block and then undefine all the C functions & free memory
def self.new_func_c(src)
sc = compile_c(src)
ptr = memory_alloc(sc.encoded.length)
sc.base_addr = ptr
# TODO fixup external calls - this will need OS ABI compat (eg win64)
bd = sc.encoded.binding(ptr)
sc.encoded.reloc_externals.uniq.each { |ext| bd[ext] = sym_addr(lib_from_sym(ext), ext) or raise "unknown symbol #{ext}" }
sc.encoded.fixup(bd)
memory_write ptr, sc.encode_string
memory_perm ptr, sc.encoded.length, 'rwx'
parse_c(src) # XXX the Shellcode parser may have defined stuff / interpreted C another way...
defs = []
@cp.toplevel.symbol.dup.each_value { |v|
cp.toplevel.symbol.dup.each_value { |v|
next if not v.kind_of? C::Variable
@cp.toplevel.symbol.delete v.name
cp.toplevel.symbol.delete v.name
next if not v.type.kind_of? C::Function or not v.initializer
next if not off = sc.encoded.export[v.name]
new_caller_for(v, v.name, ptr+off)
@ -1116,6 +1136,9 @@ EOS
# on PaX-enabled systems, this may need a non-mprotect-restricted ruby interpreter
def self.memory_perm(addr, len, perm)
perm = perm.to_s.downcase
len += (addr & 0xfff) + 0xfff
len &= ~0xfff
addr &= ~0xfff
p = 0
p |= PROT_READ if perm.include? 'r'
p |= PROT_WRITE if perm.include? 'w'

View File

@ -409,3 +409,6 @@ class COFFArchive < ExeFormat
end
end
end
require 'metasm/exe_format/coff_encode'
require 'metasm/exe_format/coff_decode'

View File

@ -76,7 +76,7 @@ class COFF
if coff.sect_at_rva(@func_p)
@exports = []
addrs = []
@num_exports.times { |i| addrs << coff.decode_word }
@num_exports.times { addrs << coff.decode_word }
@num_exports.times { |i|
e = Export.new
e.ordinal = i + @ordinal_base

View File

@ -439,11 +439,11 @@ class COFF
# encodes a thunk to imported function
def arch_encode_thunk(edata, import)
case @cpu
when Ia32
case @cpu.shortname
when 'ia32', 'x64'
shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded }
if @cpu.generate_PIC
if @cpu.size == 64
if @cpu.shortname == 'x64'
edata << shellcode["#{import.thunk}: jmp [rip-$_+#{import.target}]"]
return
end

View File

@ -736,6 +736,9 @@ class FatELF < ExeFormat
end
end
require 'metasm/exe_format/elf_encode'
require 'metasm/exe_format/elf_decode'
# TODO symbol version info
__END__
/*
@ -906,5 +909,3 @@ typedef struct {
#define SYMINFO_CURRENT 1
#define SYMINFO_NUM 2
P

View File

@ -860,8 +860,13 @@ class ELF
def init_disassembler
d = super()
d.backtrace_maxblocks_data = 4
case @cpu
when Ia32
if d.get_section_at(0)
# fixes call [constructor] => 0
d.decoded[0] = true
d.function[0] = @cpu.disassembler_default_func
end
case @cpu.shortname
when 'ia32', 'x64'
old_cp = d.c_parser
d.c_parser = nil
d.parse_c <<EOC
@ -884,7 +889,7 @@ EOC
dls.btbind_callback = lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth|
sz = @cpu.size/8
raise 'dlsym call error' if not dasm.decoded[calladdr]
if @cpu.kind_of? X86_64
if @cpu.shortname == 'x64'
arg2 = :rsi
else
arg2 = Indirection.new(Expression[:esp, :+, 2*sz], sz, calladdr)
@ -898,7 +903,7 @@ EOC
df = d.function[:default] = @cpu.disassembler_default_func
df.backtrace_binding[@cpu.register_symbols[4]] = Expression[@cpu.register_symbols[4], :+, @cpu.size/8]
df.btbind_callback = nil
when MIPS
when 'mips'
(d.address_binding[@header.entry] ||= {})[:$t9] ||= Expression[@header.entry]
@symbols.each { |s|
next if s.shndx == 'UNDEF' or s.type != 'FUNC'

View File

@ -9,6 +9,7 @@ require 'metasm/parse'
require 'metasm/encode'
require 'metasm/decode'
require 'metasm/exe_format/serialstruct'
require 'metasm/os/main' # VirtualFile
module Metasm
class ExeFormat

View File

@ -6,8 +6,7 @@
require 'metasm/exe_format/main'
require 'metasm/exe_format/mz'
require 'metasm/exe_format/coff_encode'
require 'metasm/exe_format/coff_decode'
require 'metasm/exe_format/coff'
module Metasm
class PE < COFF
@ -216,7 +215,7 @@ EOS
# TODO seh prototype (args => context)
# TODO hook on (non)resolution of :w xref
def get_xrefs_x(dasm, di)
if @cpu.kind_of? Ia32 and a = di.instruction.args.first and a.kind_of? Ia32::ModRM and a.seg and a.seg.val == 4 and
if @cpu.shortname =~ /ia32|x64/ and a = di.instruction.args.first and a.kind_of? Ia32::ModRM and a.seg and a.seg.val == 4 and
w = get_xrefs_rw(dasm, di).find { |type, ptr, len| type == :w and ptr.externals.include? 'segment_base_fs' } and
dasm.backtrace(Expression[w[1], :-, 'segment_base_fs'], di.address) == [Expression[0]]
sehptr = w[1]
@ -239,8 +238,8 @@ EOS
def init_disassembler
d = super()
d.backtrace_maxblocks_data = 4
case @cpu
when Ia32
case @cpu.shortname
when 'ia32', 'x64'
old_cp = d.c_parser
d.c_parser = nil
d.parse_c '__stdcall void *GetProcAddress(int, char *);'

View File

@ -85,9 +85,20 @@ class Graph
@madetree = false
end
# gives a text representation of the current graph state
def dump_layout(groups=@groups)
groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" }
end
def auto_arrange_step
# TODO fix
# 0->[1, 2] 1->[3] 2->[3, 4] 3->[] 4->[1]
# push 0 jz l3 push 1 jz l4 push 2 l3: push 3 l4: hlt
# and more generally all non-looping graphs where this algo creates backward links
groups = @groups
return if groups.length <= 1
maketree = lambda { |roots|
next if @madetree
@madetree = true
@ -116,7 +127,7 @@ class Graph
g.to.each { |gg| walk[gg] }
}
roots.each { |g| trim[g, g.from] }
roots.each { |g| trim[g, g.from] unless g.from.empty? }
roots.each { |g| walk[g] }
# handle loops now (unmarked nodes)
@ -319,7 +330,7 @@ class Graph
# unknown pattern, group as we can..
group_other = lambda {
puts 'graph arrange: unknown configuration', groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.inspect}" }
puts 'graph arrange: unknown configuration', dump_layout
g1 = groups.find_all { |g| g.from.empty? }
g1 << groups[rand(groups.length)] if g1.empty?
g2 = g1.map { |g| g.to }.flatten.uniq - g1
@ -408,8 +419,28 @@ puts 'graph arrange: unknown configuration', groups.map { |g| "#{groups.index(g)
end
}
boxxy = @box.sort_by { |bb| bb.y }
# fill gaps that we created
@box.each { |b|
bottom = b.y+b.h
next if not follower = boxxy.find { |bb| bb.y+bb.h > bottom }
# preserve line[] constructs margins
gap = follower.y-16*follower.from.length - (bottom+16*b.to.length)
next if gap <= 0
@box.each { |bb|
if bb.y+bb.h <= bottom
bb.y += gap/2
else
bb.y -= gap/2
end
}
boxxy = @box.sort_by { |bb| bb.y }
}
@box[0,0].each { |b|
# TODO elastic positionning (ignore up arrows ?) & collision detection (box vs box and box vs arrow)
# TODO elastic positionning (ignore up arrows ?) & collision detection (box/box + box/arrow)
f = b.from[0]
t = b.to[0]
if b.to.length == 1 and b.from.length == 1 and b.y+b.h<t.y and b.y>f.y+f.h
@ -672,21 +703,32 @@ class GraphViewWidget < DrawableWidget
def paint_arrow(b1, b2)
x1, y1 = b1.x+b1.w/2-@curcontext.view_x, b1.y+b1.h-@curcontext.view_y
x2, y2 = b2.x+b2.w/2-@curcontext.view_x, b2.y-1-@curcontext.view_y
x1o, x2o = x1, x2
margin = @margin
x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2
x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2
return if (y1+margin < 0 and y2 < 0) or (y1 > height/@zoom and y2-margin > height/@zoom) # just clip on y
margin, x1, y1, x2, y2, b1w, b2w = [margin, x1, y1, x2, y2, b1.w, b2.w].map { |v| v*@zoom }
margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom }
# gtk wraps coords around 0x8000
# XXX gtk wraps coords around 0x8000
if x1.abs > 0x7000 ; y1 /= x1.abs/0x7000 ; x1 /= x1.abs/0x7000 ; end
if y1.abs > 0x7000 ; x1 /= y1.abs/0x7000 ; y1 /= y1.abs/0x7000 ; end
if x2.abs > 0x7000 ; y2 /= x2.abs/0x7000 ; x2 /= x2.abs/0x7000 ; end
if y2.abs > 0x7000 ; x2 /= y2.abs/0x7000 ; y2 /= y2.abs/0x7000 ; end
# straighten vertical arrows if possible
if y2 > y1 and (x1-x2).abs <= margin
if b1.to.length == 1
x1 = x2
elsif b2.from.length == 1
x2 = x1
end
end
set_color_arrow(b1, b2)
if margin > 1
# draw arrow tip
draw_line(x1, y1, x1, y1+margin)
draw_line(x2, y2-margin+1, x2, y2)
draw_line(x2-margin/2, y2-margin/2, x2, y2)
@ -695,23 +737,26 @@ class GraphViewWidget < DrawableWidget
y2 -= margin-1
end
if y2+margin >= y1-margin-1
# straight vertical down arrow
draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2
elsif x1-b1w/2-margin >= x2+b2w/2+margin # z
draw_line(x1, y1, x1-b1w/2-margin, y1)
draw_line(x1-b1w/2-margin, y1, x2+b2w/2+margin, y2)
draw_line(x2+b2w/2+margin, y2, x2, y2)
draw_line(x1, y1+1, x1-b1w/2-margin, y1+1) # double
draw_line(x1-b1w/2-margin+1, y1, x2+b2w/2+margin+1, y2)
draw_line(x2+b2w/2+margin, y2+1, x2, y2+1)
# else arrow up, need to sneak around boxes
elsif x1o-b1w/2-margin >= x2o+b2w/2+margin # z
draw_line(x1, y1, x1o-b1w/2-margin, y1)
draw_line(x1o-b1w/2-margin, y1, x2o+b2w/2+margin, y2)
draw_line(x2o+b2w/2+margin, y2, x2, y2)
draw_line(x1, y1+1, x1o-b1w/2-margin, y1+1) # double
draw_line(x1o-b1w/2-margin+1, y1, x2o+b2w/2+margin+1, y2)
draw_line(x2o+b2w/2+margin, y2+1, x2, y2+1)
elsif x1+b1w/2+margin <= x2-b2w/2-margin # invert z
draw_line(x1, y1, x1+b1w/2+margin, y1)
draw_line(x1+b1w/2+margin, y1, x2-b2w/2-margin, y2)
draw_line(x2-b2w/2-margin, y2, x2, y2)
draw_line(x1, y1, x1o+b1w/2+margin, y1)
draw_line(x1o+b1w/2+margin, y1, x2o-b2w/2-margin, y2)
draw_line(x2o-b2w/2-margin, y2, x2, y2)
draw_line(x1, y1+1, x1+b1w/2+margin, y1+1) # double
draw_line(x1+b1w/2+margin+1, y1, x2-b2w/2-margin+1, y2)
draw_line(x2-b2w/2-margin, y2+1, x2, y2+1)
draw_line(x1o+b1w/2+margin+1, y1, x2o-b2w/2-margin+1, y2)
draw_line(x2o-b2w/2-margin, y2+1, x2, y2+1)
else # turn around
x = (x1 <= x2 ? [x1-b1w/2-margin, x2-b2w/2-margin].min : [x1+b1w/2+margin, x2+b2w/2+margin].max)
x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max)
draw_line(x1, y1, x, y1)
draw_line(x, y1, x, y2)
draw_line(x, y2, x2, y2)
@ -936,8 +981,8 @@ class GraphViewWidget < DrawableWidget
def keypress_ctrl(key)
case key
when ?f
@parent_widget.inputbox('text to search (regex)') { |pat|
when ?F
@parent_widget.inputbox('text to search in curfunc (regex)') { |pat|
re = /#{pat}/i
list = [['addr', 'instr']]
@curcontext.box.each { |b|
@ -1089,41 +1134,57 @@ class GraphViewWidget < DrawableWidget
puts 'autoarrange done'
when ?u
gui_update
when ?R
load __FILE__
when ?S
when ?S # reset
@curcontext.auto_arrange_init(@selected_boxes.empty? ? @curcontext.box : @selected_boxes)
puts 'reset', @curcontext.dump_layout, ''
zoom_all
redraw
when ?T
when ?T # step auto_arrange
@curcontext.auto_arrange_step
puts @curcontext.dump_layout, ''
zoom_all
redraw
when ?L
when ?L # post auto_arrange
@curcontext.auto_arrange_post
zoom_all
redraw
when ?V
when ?V # shrink
@selected_boxes.each { |b_|
dx = (b_.from+b_.to).map { |bb| bb.x+bb.w/2 - b_.x-b_.w/2 }
dx = (b_.from + b_.to).map { |bb| bb.x+bb.w/2 - b_.x-b_.w/2 }
dx = dx.inject(0) { |s, xx| s+xx }/dx.length
if dx > 0
xmax = b_.from.map { |bb| bb.x if b_.from.find { |bbb|
bbb.x+bbb.w/2 < bb.x+bb.w/2 and bbb.y+bbb.h < bb.y
} }.compact.min
bx = b_.x+dx
bx = [bx, xmax-b_.w/2-@margin].min if xmax
b_.x = bx if bx > b_.x
else
xmin = b_.from.map { |bb| bb.x+bb.w if b_.from.find { |bbb|
bbb.x+bbb.w/2 < bb.x+bb.w/2 and bbb.y+bbb.h < bb.y
} }.compact.max
bx = b_.x+dx
bx = [bx, xmin+b_.w/2+@margin].max if xmin
b_.x = bx if bx < b_.x
end
b_.x += dx
}
redraw
when ?I # create arbitrary boxes/links
if @selected_boxes.empty?
@fakebox ||= 0
b = @curcontext.new_box "id_#@fakebox",
:addresses => [], :line_address => [],
:line_text_col => [[[" blublu #@fakebox", :text]]]
b.w = @font_width * 15
b.h = @font_height * 2
b.x = rand(200) - 100
b.y = rand(200) - 100
@fakebox += 1
else
b1, *bl = @selected_boxes
bl = [b1] if bl.empty? # loop
bl.each { |b2|
if b1.to.include? b2
b1.to.delete b2
b2.from.delete b1
else
b1.to << b2
b2.from << b1
end
}
end
redraw
when ?1 # (numeric) zoom to 1:1
if @zoom == 1.0
zoom_all

View File

@ -572,6 +572,9 @@ class DisasmWidget < ContainerChoiceWidget
return if not popup = DasmWindow.new
popup.display(@dasm, @entrypoints)
w = popup.dasm_widget
w.bg_color_callback = @bg_color_callback if bg_color_callback
w.keyboard_callback = @keyboard_callback
w.keyboard_callback_ctrl = @keyboard_callback_ctrl
w.clones = @clones.concat w.clones
w.focus_addr(*focus)
popup

View File

@ -161,6 +161,7 @@ class DrawableWidget < Gtk::DrawingArea
key = {
:page_up => :pgup, :page_down => :pgdown, :next => :pgdown,
:escape => :esc, :return => :enter, :l1 => :f11, :l2 => :f12,
:prior => :pgup,
:space => ?\ ,
:asciitilde => ?~, :quoteleft => ?`,

View File

@ -907,7 +907,7 @@ class CCompiler < C::Compiler
ptr = make_volatile(ptr, expr.lexpr.type) if ptr.kind_of? Address
instr 'call', ptr
f = expr.lexpr
f = f.rexpr while f.kind_of? C::CExpression and not f.op and f.type == f.rexpr.type
f = f.rexpr while f.kind_of? C::CExpression and not f.op and f.rexpr.kind_of? C::Typed and f.type == f.rexpr.type
if not f.type.attributes.to_a.include? 'stdcall' and (not f.kind_of?(C::Variable) or not f.attributes.to_a.include? 'stdcall')
al = typesize[:ptr]
argsz = expr.rexpr.inject(0) { |sum, a| sum + (sizeof(a) + al - 1) / al * al }

View File

@ -181,6 +181,7 @@ class Ia32
base = op.bin.dup
oi = op.args.zip(i.args)
set_field = lambda { |f, v|
v ||= 0 # ST => ST(0)
fld = op.fields[f]
base[fld[0]] |= v << fld[1]
}

View File

@ -176,6 +176,7 @@ class Ia32
addop('mov', [0x8C], 0, {:d => [0, 1], :seg3 => [1, 3]}, :seg3) { |op| op.args.reverse! }
addop 'out', [0xE6], nil, {:w => [0, 0]}, :reg_eax, :u8
addop 'out', [0xE6], nil, {:w => [0, 0]}, :u8
addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_dx, :reg_eax
addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax, :reg_dx
addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax # implicit arguments
addop 'out', [0xEE], nil, {:w => [0, 0]}

View File

@ -168,6 +168,8 @@ end
# parses an arbitrary ia32 instruction argument
def parse_argument(lexer)
lexer = AsmPreprocessor.new(lexer) if lexer.kind_of? String
# reserved names (registers/segments etc)
@args_token ||= [Reg, SimdReg, SegReg, DbgReg, CtrlReg, FpReg].map { |a| a.s_to_i.keys }.flatten.inject({}) { |h, e| h.update e => true }
@ -238,7 +240,7 @@ end
cond = true
if s = o.props[:argsz] and (arg.kind_of? Reg or arg.kind_of? ModRM)
cond = (!arg.sz or arg.sz == s)
cond = (!arg.sz or arg.sz == s or spec == :reg_dx)
end
cond and

View File

@ -341,18 +341,30 @@ class Expression < ExpressionType
# in operands order, and allows nesting using sub-arrays
# ex: Expression[[:-, 42], :*, [1, :+, [4, :*, 7]]]
# with a single argument, return it if already an Expression, else construct a new one (using unary +/-)
def self.[](l, op = nil, r = nil)
raise ArgumentError, 'invalid Expression[nil]' if not l and not r and not op
return l if l.kind_of? Expression and not op
l, op, r = nil, :-, -l if not op and l.kind_of? ::Numeric and l < 0
l, op, r = nil, :+, l if not op
l, op, r = nil, l, op if not r
def self.[](l, op=nil, r=nil)
if not r # need to shift args
if not op
raise ArgumentError, 'invalid Expression[nil]' if not l
return l if l.kind_of? Expression
if l.kind_of? ::Numeric and l < 0
r = -l
op = :'-'
else
r = l
op = :'+'
end
else
r = op
op = l
end
l = nil
else
l = self[*l] if l.kind_of? ::Array
end
r = self[*r] if r.kind_of? ::Array
new(op, r, l)
end
# checks if a given Expression/Integer is in the type range
# returns true if it is, false if it overflows, and nil if cannot be determined (eg unresolved variable)
def self.in_range?(val, type)
@ -391,7 +403,7 @@ class Expression < ExpressionType
# will not match 1+2 and 2+1
def ==(o)
# shortcircuit recursion
o.object_id == object_id or (o.class == self.class and @op == o.op and @lexpr == o.lexpr and @rexpr == o.rexpr)
o.object_id == object_id or (o.kind_of?(Expression) and @op == o.op and @lexpr == o.lexpr and @rexpr == o.rexpr)
end
# make it useable as Hash key (see +==+)
@ -517,18 +529,8 @@ class Expression < ExpressionType
0
elsif l == 1
Expression[r, :'!=', 0].reduce_rec
elsif r == 0 # (no sideeffects) && 0 => 0
sideeffect = lambda { |e|
if e.kind_of? Expression
not [:+, :-, :*, :/, :&, :|, :^, :>, :<, :>>, :<<, :'==', :'!=', :<=, :>=, :'&&', :'||'].include?(e.op) or
sideeffect[e.lexpr] or sideeffect[e.rexpr]
elsif e.kind_of? ExpressionType
true # fail safe
else
false
end
}
0 if not sideeffect[l]
elsif r == 0
0 # XXX l could be a special ExprType with sideeffects ?
end
elsif @op == :'||'
if l.kind_of? ::Numeric and l != 0 # shortcircuit eval
@ -599,26 +601,7 @@ class Expression < ExpressionType
Expression[[l.lexpr, :&, r], l.op, [l.rexpr, :&, r]].reduce_rec
# rol/ror composition
elsif r.kind_of? ::Integer and l.kind_of? Expression and l.op == :|
m = Expression[[['var', :sh_op, 'amt'], :|, ['var', :inv_sh_op, 'inv_amt']], :&, 'mask']
if vars = Expression[l, :&, r].match(m, 'var', :sh_op, 'amt', :inv_sh_op, 'inv_amt', 'mask') and vars[:sh_op] == {:>> => :<<, :<< => :>>}[ vars[:inv_sh_op]] and
((vars['amt'].kind_of?(::Integer) and vars['inv_amt'].kind_of?(::Integer) and ampl = vars['amt'] + vars['inv_amt']) or
(vars['amt'].kind_of? Expression and vars['amt'].op == :% and vars['amt'].rexpr.kind_of? ::Integer and
vars['inv_amt'].kind_of? Expression and vars['inv_amt'].op == :% and vars['amt'].rexpr == vars['inv_amt'].rexpr and ampl = vars['amt'].rexpr)) and
vars['mask'].kind_of?(::Integer) and vars['mask'] == (1<<ampl)-1 and vars['var'].kind_of? Expression and # it's a rotation
ivars = vars['var'].match(m, 'var', :sh_op, 'amt', :inv_sh_op, 'inv_amt', 'mask') and ivars[:sh_op] == {:>> => :<<, :<< => :>>}[ivars[:inv_sh_op]] and
((ivars['amt'].kind_of?(::Integer) and ivars['inv_amt'].kind_of?(::Integer) and ampl = ivars['amt'] + ivars['inv_amt']) or
(ivars['amt'].kind_of? Expression and ivars['amt'].op == :% and ivars['amt'].rexpr.kind_of? ::Integer and
ivars['inv_amt'].kind_of? Expression and ivars['inv_amt'].op == :% and ivars['amt'].rexpr == ivars['inv_amt'].rexpr and ampl = ivars['amt'].rexpr)) and
ivars['mask'].kind_of?(::Integer) and ivars['mask'] == (1<<ampl)-1 and ivars['mask'] == vars['mask'] # it's a composed rotation
if ivars[:sh_op] != vars[:sh_op]
# ensure the rotations are the same orientation
ivars[:sh_op], ivars[:inv_sh_op] = ivars[:inv_sh_op], ivars[:sh_op]
ivars['amt'], ivars['inv_amt'] = ivars['inv_amt'], ivars['amt']
end
amt = Expression[[vars['amt'], :+, ivars['amt']], :%, ampl]
invamt = Expression[[vars['inv_amt'], :+, ivars['inv_amt']], :%, ampl]
Expression[[[ivars['var'], vars[:sh_op], amt], :|, [ivars['var'], vars[:inv_sh_op], invamt]], :&, vars['mask']].reduce_rec
end
reduce_rec_composerol r, l
end
elsif @op == :|
if l == 0; r
@ -675,28 +658,7 @@ class Expression < ExpressionType
elsif l.kind_of? Expression and r.kind_of? Expression and l.op == :% and r.op == :% and l.rexpr.kind_of?(::Integer) and l.rexpr == r.rexpr
Expression[[l.lexpr, :+, r.lexpr], :%, l.rexpr].reduce_rec
else
# a+(b+(c+(-a))) => b+c+0
# a+((-a)+(b+c)) => 0+b+c
neg_l = l.rexpr if l.kind_of? Expression and l.op == :-
# recursive search & replace -lexpr by 0
simplifier = lambda { |cur|
if (neg_l and neg_l == cur) or (cur.kind_of? Expression and cur.op == :- and not cur.lexpr and cur.rexpr == l)
# -l found
0
else
# recurse
if cur.kind_of? Expression and cur.op == :+
if newl = simplifier[cur.lexpr]
Expression[newl, cur.op, cur.rexpr].reduce_rec
elsif newr = simplifier[cur.rexpr]
Expression[cur.lexpr, cur.op, newr].reduce_rec
end
end
end
}
simplifier[r]
reduce_rec_add(l, r)
end
end
@ -719,6 +681,60 @@ class Expression < ExpressionType
ret
end
# a+(b+(c+(-a))) => b+c+0
# a+((-a)+(b+c)) => 0+b+c
def reduce_rec_add(l, r)
if l.kind_of? Expression and l.op == :- and not l.lexpr
neg_l = l.rexpr
else
neg_l = Expression[:-, l]
end
# recursive search & replace -lexpr by 0
simplifier = lambda { |cur|
if neg_l == cur
# -l found
0
elsif cur.kind_of? Expression and cur.op == :+
# recurse
if newl = simplifier[cur.lexpr]
Expression[newl, cur.op, cur.rexpr].reduce_rec
elsif newr = simplifier[cur.rexpr]
Expression[cur.lexpr, cur.op, newr].reduce_rec
end
end
}
simplifier[r]
end
# a check to see if an Expr is the composition of two rotations (rol eax, 4 ; rol eax, 6 => rol eax, 10)
# this is a bit too ugly to stay in the main reduce_rec body.
def reduce_rec_composerol
m = Expression[[['var', :sh_op, 'amt'], :|, ['var', :inv_sh_op, 'inv_amt']], :&, 'mask']
if vars = Expression[l, :&, r].match(m, 'var', :sh_op, 'amt', :inv_sh_op, 'inv_amt', 'mask') and vars[:sh_op] == {:>> => :<<, :<< => :>>}[ vars[:inv_sh_op]] and
((vars['amt'].kind_of?(::Integer) and vars['inv_amt'].kind_of?(::Integer) and ampl = vars['amt'] + vars['inv_amt']) or
(vars['amt'].kind_of? Expression and vars['amt'].op == :% and vars['amt'].rexpr.kind_of? ::Integer and
vars['inv_amt'].kind_of? Expression and vars['inv_amt'].op == :% and vars['amt'].rexpr == vars['inv_amt'].rexpr and ampl = vars['amt'].rexpr)) and
vars['mask'].kind_of?(::Integer) and vars['mask'] == (1<<ampl)-1 and vars['var'].kind_of? Expression and # it's a rotation
ivars = vars['var'].match(m, 'var', :sh_op, 'amt', :inv_sh_op, 'inv_amt', 'mask') and ivars[:sh_op] == {:>> => :<<, :<< => :>>}[ivars[:inv_sh_op]] and
((ivars['amt'].kind_of?(::Integer) and ivars['inv_amt'].kind_of?(::Integer) and ampl = ivars['amt'] + ivars['inv_amt']) or
(ivars['amt'].kind_of? Expression and ivars['amt'].op == :% and ivars['amt'].rexpr.kind_of? ::Integer and
ivars['inv_amt'].kind_of? Expression and ivars['inv_amt'].op == :% and ivars['amt'].rexpr == ivars['inv_amt'].rexpr and ampl = ivars['amt'].rexpr)) and
ivars['mask'].kind_of?(::Integer) and ivars['mask'] == (1<<ampl)-1 and ivars['mask'] == vars['mask'] # it's a composed rotation
if ivars[:sh_op] != vars[:sh_op]
# ensure the rotations are the same orientation
ivars[:sh_op], ivars[:inv_sh_op] = ivars[:inv_sh_op], ivars[:sh_op]
ivars['amt'], ivars['inv_amt'] = ivars['inv_amt'], ivars['amt']
end
amt = Expression[[vars['amt'], :+, ivars['amt']], :%, ampl]
invamt = Expression[[vars['inv_amt'], :+, ivars['inv_amt']], :%, ampl]
Expression[[[ivars['var'], vars[:sh_op], amt], :|, [ivars['var'], vars[:inv_sh_op], invamt]], :&, vars['mask']].reduce_rec
end
end
# a pattern-matching method
# Expression[42, :+, 28].match(Expression['any', :+, 28], 'any') => {'any' => 42}
# Expression[42, :+, 28].match(Expression['any', :+, 'any'], 'any') => false
@ -900,11 +916,11 @@ class EncodedData
# base defaults to the first export name + its offset
def binding(base = nil)
if not base
key = @export.keys.sort_by { |k| @export[k] }.first
key = @export.index(@export.values.min)
return {} if not key
base = (@export[key] == 0 ? key : Expression[key, :-, @export[key]])
end
@export.inject({}) { |binding, (n, o)| binding.update n => Expression[base, :+, o] }
@export.inject({}) { |binding, (n, o)| binding.update n => Expression.new(:+, o, base) }
end
# returns an array of variables that needs to be defined for a complete #fixup
@ -940,36 +956,35 @@ class EncodedData
# concatenation of another +EncodedData+ (or nil/Fixnum/anything supporting String#<<)
def << other
case other
when nil
when ::Fixnum
fill
@data = @data.realstring if defined? VirtualString and @data.kind_of? VirtualString
@data = @data.to_str if not @data.kind_of? String
@data << other
@virtsize += 1
when EncodedData
fill if not other.data.empty?
other.reloc.each { |k, v| @reloc[k + @virtsize] = v }
cf = (other.export.keys & @export.keys).find_all { |k| other.export[k] != @export[k] - @virtsize }
raise "edata merge: label conflict #{cf.inspect}" if not cf.empty?
other.export.each { |k, v| @export[k] = v + @virtsize }
other.reloc.each { |k, v| @reloc[k + @virtsize] = v } if not other.reloc.empty?
if not other.export.empty?
other.export.each { |k, v|
if @export[k] and @export[k] != v + @virtsize
cf = (other.export.keys & @export.keys).find_all { |k_| other.export[k_] != @export[k_] - @virtsize }
raise "edata merge: label conflict #{cf.inspect}"
end
@export[k] = v + @virtsize
}
other.inv_export.each { |k, v| @inv_export[@virtsize + k] = v }
if @data.empty?; @data = other.data.dup
elsif defined? VirtualString and @data.kind_of? VirtualString; @data = @data.realstring << other.data
else
if(other.data.respond_to?('force_encoding'))
other.data.force_encoding("binary")
end
@data << other.data
if @data.empty?; @data = other.data.dup
elsif not @data.kind_of?(String); @data = @data.to_str << other.data
else @data << other.data
end
@virtsize += other.virtsize
else
fill
if @data.empty?; @data = other.dup
elsif defined? VirtualString and @data.kind_of? VirtualString; @data = @data.realstring << other
elsif not @data.kind_of?(String); @data = @data.to_str << other
else @data << other
end
@virtsize += other.length
@ -1095,5 +1110,31 @@ class EncodedData
raise EncodeError, 'cannot patch data: new content too long' if to - from < content.length
self[from, content.length] = content
end
# returns a list of offsets where /pat/ can be found inside @data
# scan is done per chunk of chunksz bytes, with a margin for chunk-overlapping patterns
# yields each offset found, and only include it in the result if the block returns !false
def pattern_scan(pat, chunksz=nil, margin=nil)
chunksz ||= 4*1024*1024 # scan 4MB at a time
margin ||= 65536 # add this much bytes at each chunk to find /pat/ over chunk boundaries
pat = Regexp.new(Regexp.escape(pat)) if pat.kind_of? ::String
found = []
chunkoff = 0
while chunkoff < @data.length
chunk = @data[chunkoff, chunksz+margin].to_str
off = 0
while match_off = (chunk[off..-1] =~ pat)
break if off+match_off >= chunksz # match fully in margin
match_addr = chunkoff + off + match_off
found << match_addr if not block_given? or yield(match_addr)
off += match_off + 1
# XXX +1 or +lastmatch.length ?
# 'aaaabc'.pattern_scan(/a*bc/) will match 5 times here
end
chunkoff += chunksz
end
found
end
end
end

View File

@ -227,10 +227,11 @@ class VirtualString
# returns a new VirtualString (using dup) if the request is bigger than @pagelength bytes
def read_range(from, len)
from += @addr_start
base, page = cache_get_page(from)
if not len
base, page = cache_get_page(from)
page[from - base]
elsif len <= @pagelength
base, page = cache_get_page(from)
s = page[from - base, len]
if from+len-base > @pagelength # request crosses a page boundary
base, page = cache_get_page(from+len)
@ -846,5 +847,18 @@ class Debugger
instance_eval File.read(plugin_filename)
end
# see EData#pattern_scan
# scans only mapped areas of @memory, using os_process.mappings
def pattern_scan(pat)
ret = []
os_process.mappings.each { |a, l, *o|
EncodedData.new(@memory[a, l]).pattern_scan(pat) { |o|
o += a
ret << o if not block_given? or yield(o)
}
}
ret
end
end
end

View File

@ -22,7 +22,7 @@ typedef unsigned int UINT;
typedef long LONG;
typedef unsigned long ULONG, DWORD, *LPDWORD;
typedef int BOOL;
typedef unsigned long long DWORD64;
typedef unsigned long long DWORD64, ULONGLONG;
typedef intptr_t INT_PTR, LONG_PTR;
typedef uintptr_t UINT_PTR, ULONG_PTR, DWORD_PTR, SIZE_T;
@ -665,6 +665,38 @@ Thread32Next(
LPTHREADENTRY32 lpte
);
typedef struct _MEMORY_BASIC_INFORMATION32 {
DWORD BaseAddress;
DWORD AllocationBase;
DWORD AllocationProtect; // initial (alloc time) prot
DWORD RegionSize;
DWORD State; // MEM_FREE/COMMIT/RESERVE
DWORD Protect; // PAGE_EXECUTE_READWRITE etc
DWORD Type; // MEM_IMAGE/MAPPED/PRIVATE
} MEMORY_BASIC_INFORMATION32, *PMEMORY_BASIC_INFORMATION32;
typedef struct _MEMORY_BASIC_INFORMATION64 {
ULONGLONG BaseAddress;
ULONGLONG AllocationBase;
DWORD AllocationProtect;
DWORD __alignment1;
ULONGLONG RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
DWORD __alignment2;
} MEMORY_BASIC_INFORMATION64, *PMEMORY_BASIC_INFORMATION64;
SIZE_T
WINAPI
VirtualQueryEx(
HANDLE hProcess,
LPVOID lpAddress,
PMEMORY_BASIC_INFORMATION32 lpBuffer,
SIZE_T dwLength // sizeof lpBuffer
);
EOS
new_api_c <<EOS, 'advapi32'
@ -860,6 +892,35 @@ class WinOS < OS
WinAPI.closehandle(h)
list
end
# return a list of [addr_start, length, perms]
def mappings
addr = 0
list = []
info = WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{addrsz}")
while WinAPI.virtualqueryex(handle, addr, info, info.length)
addr += info[:regionsize]
next unless info[:state] & WinAPI::MEM_COMMIT > 0
prot = {
WinAPI::PAGE_NOACCESS => '---',
WinAPI::PAGE_READONLY => 'r--',
WinAPI::PAGE_READWRITE => 'rw-',
WinAPI::PAGE_WRITECOPY => 'rw-',
WinAPI::PAGE_EXECUTE => '--x',
WinAPI::PAGE_EXECUTE_READ => 'r-x',
WinAPI::PAGE_EXECUTE_READWRITE => 'rwx',
WinAPI::PAGE_EXECUTE_WRITECOPY => 'rwx'
}[info[:protect] & 0xff]
prot << 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0
prot << 'p' if info[:type] & WinAPI::MEM_PRIVATE > 0
list << [info[:baseaddress], info[:regionsize], prot]
end
list
end
end
class << self
@ -1005,12 +1066,6 @@ class WindowsRemoteString < VirtualString
return if WinAPI.readprocessmemory(@handle, addr, page, len, 0) == 0
page
end
def realstring
s = [0].pack('C') * @length
WinAPI.readprocessmemory(@handle, @addr_start, s, @length, 0)
s
end
end
class WinDbgAPI

View File

@ -40,7 +40,7 @@ class CPU
raise tok, 'invalid opcode' if not opcode_list_byname[tok.raw]
i.opname = tok.raw
i.backtrace = tok.backtrace.dup
i.backtrace = tok.backtrace
lexer.skip_space
# find arguments list
@ -317,7 +317,7 @@ class ExeFormat
raise tok, "label redefinition" if new_label(lname) != lname
end
l = Label.new(lname)
l.backtrace = tok.backtrace.dup
l.backtrace = tok.backtrace
@cursource << l
lasteol = false
else
@ -331,7 +331,7 @@ class ExeFormat
end
if lname = @locallabels_fwd.delete('endinstr')
l = Label.new(lname)
l.backtrace = tok.backtrace.dup
l.backtrace = tok.backtrace
@cursource << l
end
end
@ -377,7 +377,7 @@ class ExeFormat
@lexer.unreadtok ntok
end
raise tok, 'syntax error' if ntok = @lexer.nexttok and ntok.type != :eol
@cursource << Align.new(e, fillwith, tok.backtrace.dup)
@cursource << Align.new(e, fillwith, tok.backtrace)
when '.pad'
@lexer.skip_space
@ -394,12 +394,12 @@ class ExeFormat
@lexer.unreadtok ntok
end
raise tok, 'syntax error' if ntok = @lexer.nexttok and ntok.type != :eol
@cursource << Padding.new(fillwith, tok.backtrace.dup)
@cursource << Padding.new(fillwith, tok.backtrace)
when '.offset'
e = Expression.parse(@lexer)
raise tok, 'syntax error' if ntok = @lexer.nexttok and ntok.type != :eol
@cursource << Offset.new(e, tok.backtrace.dup)
@cursource << Offset.new(e, tok.backtrace)
when '.padto'
e = Expression.parse(@lexer)
@ -418,7 +418,7 @@ class ExeFormat
@lexer.unreadtok ntok
end
raise tok, 'syntax error' if ntok = @lexer.nexttok and ntok.type != :eol
@cursource << Padding.new(fillwith, tok.backtrace.dup) << Offset.new(e, tok.backtrace.dup)
@cursource << Padding.new(fillwith, tok.backtrace) << Offset.new(e, tok.backtrace)
else
@cpu.parse_parser_instruction(self, tok)
@ -441,15 +441,15 @@ class ExeFormat
break
end
end
Data.new(type, arr, 1, tok.backtrace.dup)
Data.new(type, arr, 1, tok.backtrace)
end
def parse_data_data(type)
raise ParseError, 'need data content' if not tok = @lexer.readtok
if tok.type == :punct and tok.raw == '?'
Data.new type, :uninitialized, 1, tok.backtrace.dup
Data.new type, :uninitialized, 1, tok.backtrace
elsif tok.type == :quoted
Data.new type, tok.value, 1, tok.backtrace.dup
Data.new type, tok.value, 1, tok.backtrace
else
@lexer.unreadtok tok
raise tok, 'invalid data' if not i = Expression.parse(@lexer)
@ -470,10 +470,10 @@ class ExeFormat
end
end
raise ntok, 'syntax error, ) expected' if not ntok = @lexer.readtok or ntok.type != :punct or ntok.raw != ')'
Data.new type, content, count, tok.backtrace.dup
Data.new type, content, count, tok.backtrace
else
@lexer.unreadtok ntok
Data.new type, i, 1, tok.backtrace.dup
Data.new type, i, 1, tok.backtrace
end
end
end
@ -664,7 +664,7 @@ class Expression
l = lexer.program.cursource.last
if not l.kind_of? Label
l = Label.new(lexer.program.new_label('instr_start'))
l.backtrace = tok.backtrace.dup
l.backtrace = tok.backtrace
lexer.program.cursource << l
end
tok.value = l.name
@ -672,7 +672,7 @@ class Expression
l = lexer.program.cursource.first
if not l.kind_of? Label
l = Label.new(lexer.program.new_label('section_start'))
l.backtrace = tok.backtrace.dup
l.backtrace = tok.backtrace
lexer.program.cursource.unshift l
end
tok.value = l.name

View File

@ -276,6 +276,7 @@ module C
case tok.raw
when ';'; break
when ','
when '}'; parser.unreadtok(tok); break
else raise tok, '"," or ";" expected'
end
end
@ -647,7 +648,7 @@ module C
raise tok || parser, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
raise tok, 'expr expected' if not expr = CExpression.parse(parser, scope) or not expr.type.arithmetic?
raise tok || parser, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
raise tok || parser, '";" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ';'
parser.checkstatementend(tok)
new expr, body
end
@ -821,7 +822,7 @@ module C
end
raise tok || parser, '")" expected' if not tok or tok.type != :punct or tok.raw != ')'
ret.parse_attributes(parser)
raise tok || parser, '";" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ';'
parser.checkstatementend(tok)
ret
end
end
@ -1076,8 +1077,13 @@ module C
@lexer.define_weak('__STDC__')
@lexer.define_weak('__const', 'const')
@lexer.define_weak('__signed', 'signed')
@lexer.define_weak('__signed__', 'signed')
@lexer.define_weak('__volatile', 'volatile')
@lexer.nodefine_strong('__REDIRECT_NTH') # booh gnu
if not @lexer.definition['__builtin_constant_p']
# magic macro to check if its arg is an immediate value
@lexer.define_weak('__builtin_constant_p', '0')
@lexer.definition['__builtin_constant_p'].args = [Preprocessor::Token.new([])]
end
@lexer.nodefine_strong('alloca') # TODO __builtin_alloca
@lexer.hooked_include['stddef.h'] = <<EOH
/* simplified, define all at first invocation. may break things... */
@ -1297,6 +1303,13 @@ EOH
t
end
# checks that we are at the end of a statement, ie an ';' character (consumed), or a '}' (not consumed)
# otherwise, raise either the given token or self.
def checkstatementend(tok=nil)
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or (tok.raw != ';' and tok.raw != '}')
unreadtok tok if tok.raw == '}'
end
# returns the size of a type in bytes
def sizeof(var, type=nil)
var, type = nil, var if var.kind_of? Type and not type
@ -1457,6 +1470,7 @@ EOH
case tok.raw
when ','; nofunc = true
when ';'; break
when '}'; unreadtok(tok); break
else raise tok, '";" or "," expected'
end
end
@ -1497,7 +1511,7 @@ EOH
elsif tok.type != :string
unreadtok tok
raise tok, 'expr expected' if not expr = CExpression.parse(self, scope)
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
if $VERBOSE and not nest.include?(:expression) and (expr.op or not expr.type.untypedef.kind_of? BaseType or expr.type.untypedef.name != :void) and CExpression.constant?(expr)
puts tok.exception("statement with no effect : #{expr}").message
@ -1519,7 +1533,7 @@ EOH
when 'goto'
raise tok || self, 'label expected' if not tok = skipspaces or tok.type != :string
name = tok.raw
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
Goto.new name
when 'return'
expr = CExpression.parse(self, scope) # nil allowed
@ -1528,7 +1542,7 @@ EOH
if (not p and not i) or (i and not r.kind_of? ::Integer) or (p and r != 0)
check_compatible_type(tok, (expr ? expr.type : BaseType.new(:void)), nest[0])
end
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
Return.new expr
when 'case'
raise tok, 'case out of switch' if not nest.include? :switch
@ -1538,11 +1552,11 @@ EOH
raise tok, 'case out of switch' if not nest.include? :switch
Case.new 'default', nil, parse_statement(scope, nest)
when 'continue'
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
raise tok, 'continue out of loop' if not nest.include? :loop
Continue.new
when 'break'
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
raise tok, 'break out of loop' if not nest.include? :loop and not nest.include? :switch
Break.new
when 'asm', '__asm', '__asm__'
@ -1559,7 +1573,7 @@ EOH
unreadtok ntok
unreadtok tok
raise tok, 'expr expected' if not expr = CExpression.parse(self, scope)
raise tok || self, '";" expected' if not tok = skipspaces or tok.type != :punct or tok.raw != ';'
checkstatementend(tok)
if $VERBOSE and not nest.include?(:expression) and (expr.op or not expr.type.untypedef.kind_of? BaseType or expr.type.untypedef.name != :void) and CExpression.constant?(expr)
puts tok.exception("statement with no effect : #{expr}").message
@ -2095,7 +2109,7 @@ EOH
# overflow
case t.name
when :char, :short, :int, :long, :longlong, :__int8, :__int16, :__int32, :__int64
when :char, :short, :int, :long, :ptr, :longlong, :__int8, :__int16, :__int32, :__int64
max = 1 << (8*parser.typesize[t.name])
ret = ret.to_i & (max-1)
if t.specifier == :signed and (ret & (max >> 1)) > 0 # char == unsigned char
@ -2707,11 +2721,11 @@ EOH
r.join("\n")
end
# returns a string containing the C definition of the toplevel function funcname, with its dependencies
def dump_definition(funcname)
# returns a string containing the C definition(s) of toplevel functions, with their dependencies
def dump_definition(*funcnames)
oldst = @toplevel.statements
@toplevel.statements = []
dump_definitions([@toplevel.symbol[funcname]])
dump_definitions(funcnames.map { |f| @toplevel.symbol[f] })
ensure
@toplevel.statements = oldst
end
@ -3373,7 +3387,7 @@ EOH
r, dep = @rexpr.dump(scope, r, dep)
when Block
r.last << '('
r, dep = Statement.dump(scope, r, dep)
r, dep = Statement.dump(@rexpr, scope, r, dep)
r.last << ' )'
when Label
r.last << '&&' << @rexpr.name
@ -3423,7 +3437,7 @@ EOH
else
r, dep = CExpression.dump(@lexpr, scope, r, dep, (@lexpr.kind_of? CExpression and @lexpr.lexpr and @lexpr.op != @op))
r.last << ' ' << @op.to_s << ' '
r, dep = CExpression.dump(@rexpr, scope, r, dep, (@rexpr.kind_of? CExpression and @rexpr.lexpr and @rexpr.op != @op))
r, dep = CExpression.dump(@rexpr, scope, r, dep, (@rexpr.kind_of? CExpression and @rexpr.lexpr and @rexpr.op != @op and @rexpr.op != :funcall))
end
end
r.last << ')' if brace and @op != :'->' and @op != :'.' and @op != :'[]' and (@op or @rexpr.kind_of? CExpression)

View File

@ -551,6 +551,7 @@ class Preprocessor
def ungetchar
@pos = @ungetcharpos
@lineno = @ungetcharlineno
nil
end
# returns true if no more data is available
@ -562,6 +563,7 @@ class Preprocessor
# lifo
def unreadtok(tok)
@queue << tok if tok
nil
end
# calls readtok_nopp and handles preprocessor directives
@ -622,17 +624,88 @@ class Preprocessor
def readtok_nopp
return @queue.pop unless @queue.empty?
tok = Token.new((@backtrace.map { |bt| bt[0, 2] } + [@filename, @lineno]).flatten)
nbt = []
@backtrace.each { |bt| nbt << bt[0] << bt[1] }
tok = Token.new(nbt << @filename << @lineno)
case c = getchar
when nil
return nil
when ?', ?"
# read quoted string value
tok.type = :quoted
delimiter = c
readtok_nopp_str(tok, c)
when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_
tok.type = :string
raw = tok.raw << c
loop do
case c = getchar
when nil; ungetchar; break # avoids 'no method "coerce" for nil' warning
when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_
raw << c
else ungetchar; break
end
end
when ?\ , ?\t, ?\r, ?\n, ?\f
tok.type = ((c == ?\ || c == ?\t) ? :space : :eol)
raw = tok.raw << c
loop do
case c = getchar
when nil; break
when ?\ , ?\t
when ?\n, ?\f, ?\r; tok.type = :eol
else break
end
raw << c
end
ungetchar
when ?/
raw = tok.raw << c
# comment
case c = getchar
when ?/
# till eol
tok.type = :eol
raw << c
while c = getchar
raw << c
break if c == ?\n
end
when ?*
tok.type = :space
raw << c
seenstar = false
loop do
raise tok, 'unterminated c++ comment' if not c = getchar
raw << c
case c
when ?*; seenstar = true
when ?/; break if seenstar # no need to reset seenstar, already false
else seenstar = false
end
end
else
# just a slash
ungetchar
tok.type = :punct
end
else
tok.type = :punct
tok.raw << c
end
tok
end
# we just read a ' or a ", read until the end of the string
# tok.value will contain the raw string (with escapes interpreted etc)
def readtok_nopp_str(tok, delimiter)
tok.type = :quoted
tok.raw << delimiter
tok.value = ''
c = nil
loop do
raise tok, 'unterminated string' if not c = getchar
tok.raw << c
@ -685,72 +758,10 @@ class Preprocessor
end
end
when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_
tok.type = :string
tok.raw << c
loop do
case c = getchar
when nil; ungetchar; break # avoids 'no method "coerce" for nil' warning
when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_
tok.raw << c
else ungetchar; break
end
end
when ?\ , ?\t, ?\r, ?\n, ?\f
tok.type = :space
tok.raw << c
loop do
case c = getchar
when nil; break
when ?\ , ?\t
when ?\n, ?\f, ?\r; tok.type = :eol
else break
end
tok.raw << c
end
ungetchar
tok.type = :eol if tok.raw.index(?\n) or tok.raw.index(?\f)
when ?/
tok.raw << c
# comment
case c = getchar
when ?/
# till eol
tok.type = :eol
tok.raw << c
while c = getchar
tok.raw << c
break if c == ?\n
end
when ?*
tok.type = :space
tok.raw << c
seenstar = false
loop do
raise tok, 'unterminated c++ comment' if not c = getchar
tok.raw << c
case c
when ?*; seenstar = true
when ?/; break if seenstar # no need to reset seenstar, already false
else seenstar = false
end
end
else
# just a slash
ungetchar
tok.type = :punct
end
else
tok.type = :punct
tok.raw << c
end
tok
end
# defines a simple preprocessor macro (expands to 0 or 1 token)
# does not check overwriting
def define(name, value=nil, from=caller.first)

View File

@ -65,9 +65,24 @@ end
class Expression
include Renderable
attr_accessor :render_info
def render
l, r = [@lexpr, @rexpr].map { |e|
if e.kind_of? Integer
# this is an accessor to @@render_int, the lambda used to render integers > 10
# usage: Expression.render_int = lambda { |e| '0x%x' % e }
# or Expression.render_int { |e| '0x%x' % e }
# XXX the returned string should be suitable for inclusion in a label name etc
def self.render_int(&b)
if b
@@render_int = b
else
@@render_int
end
end
def self.render_int=(p)
@@render_int = p
end
@@render_int = nil
def render_integer(e)
if render_info and @render_info[:char]
ee = e
v = []
@ -78,7 +93,7 @@ class Expression
v.reverse! if @render_info[:char] == :big
if not v.empty? and v.all? { |c| c < 0x7f }
# XXX endianness
next "'" + v.pack('C*').inspect.gsub("'") { '\\\'' }[1...-1] + "'"
return "'" + v.pack('C*').inspect.gsub("'") { '\\\'' }[1...-1] + "'"
end
end
if e < 0
@ -86,18 +101,23 @@ class Expression
e = -e
end
if e < 10; e = e.to_s
elsif @@render_int
e = @@render_int[e]
else
e = '%xh' % e
e = '0' << e unless (?0..?9).include? e[0]
end
e = '-' << e if neg
end
e
}
nosq = {:* => [:*], :+ => [:+, :-, :*], :- => [:+, :-, :*]}
l = ['(', l, ')'] if @lexpr.kind_of? Expression and not nosq[@op].to_a.include?(@lexpr.op)
nosq[:-] = [:*]
r = ['(', r, ')'] if @rexpr.kind_of? Expression and not nosq[@op].to_a.include?(@rexpr.op)
end
NOSQ1 = NOSQ2 = {:* => [:*], :+ => [:+, :-, :*], :- => [:+, :-, :*]}
NOSQ2[:-] = [:*]
def render
l = @lexpr.kind_of?(Integer) ? render_integer(@lexpr) : @lexpr
r = @rexpr.kind_of?(Integer) ? render_integer(@rexpr) : @rexpr
l = ['(', l, ')'] if @lexpr.kind_of? Expression and (not oa = NOSQ1[@op] or not oa.include?(@lexpr.op))
r = ['(', r, ')'] if @rexpr.kind_of? Expression and (not oa = NOSQ2[@op] or not oa.include?(@rexpr.op))
op = @op if l or @op != :+
if op == :+
r0 = [r].flatten.first

View File

@ -42,6 +42,7 @@ class X86_64
@opcode_list.delete_if { |o|
o.args.include? :modrmmmx or # mmx is dead!
o.args.include? :regmmx or # movd
o.args.include? :regfp or # no fpu beyond this line
o.name == 'loadall' or
o.name == 'arpl'
}

View File

@ -4,34 +4,58 @@
# Licence is LGPL, see LICENCE in the top-level directory
# A script to help finding performance bottlenecks:
# ruby-prof myscript.rb
#
# $ ruby-prof myscript.rb
# => String#+ gets called 50k times and takes 30s
# LOGCALLER='String#+' ruby -r log_caller myscript.rb
#
# $ LOGCALLER='String#+' ruby -r bottleneck myscript.rb
# => String#+ called 40k times from:
# stuff.rb:42 in Myclass#uglymethod from
# stuff.rb:32 in Myclass#initialize
#
# now you know what to rewrite
def log_caller(cls, meth, histlen=-1)
malias = meth.to_s.gsub(/[^a-z0-9_]/i, '') + '_log_caller'
mcntr = '$' + meth.to_s.gsub(/[^a-z0-9_]/i, '') + '_counter'
def log_caller(cls, meth, singleton=false, histlen=nil)
histlen ||= ENV.fetch('LOGCALLER_MAXHIST', 16).to_i
dec_meth = 'm_' + meth.to_s.gsub(/[^\w]/) { |c| c.unpack('H*')[0] }
malias = dec_meth + '_log_caller'
mcntr = '$' + dec_meth + '_counter'
eval <<EOS
class #{cls}
#{cls.kind_of?(Class) ? 'class' : 'module'} #{cls}
#{'class << self' if singleton}
alias #{malias} #{meth}
def #{meth}(*a, &b)
#{mcntr}[caller[0..#{histlen}]] += 1
#{mcntr}[caller[0, #{histlen}]] += 1
#{malias}(*a, &b)
end
#{'end' if singleton}
end
#{mcntr} = Hash.new(0)
at_exit { puts " callers of #{cls} #{meth}:", #{mcntr}.sort_by { |k, v| -v }[0, 4].map { |k, v| ["\#{v} times from", k, ''] } }
at_exit {
total = #{mcntr}.inject(0) { |a, (k, v)| a+v }
puts "\#{total} callers of #{cls} #{meth}:"
#{mcntr}.sort_by { |k, v|
-v
}[0, 4].each { |k, v|
puts " \#{'%.2f%%' % (100.0*v/total)} - \#{v} times from", k, ''
}
}
EOS
end
if ENV['LOGCALLER'] =~ /^(.*)#(.*)$/
cls, meth = $1, $2.to_sym
cls = cls.split('::').inject(Object) { |o, cst| o.const_get(cst) }
log_caller(cls, meth)
end
ENV['LOGCALLER'].to_s.split(';').map { |lc|
next if not lc =~ /^(.*)([.#])(.*)$/
cls, sg, meth = $1, $2, $3.to_sym
sg = { '.' => true, '#' => false }[sg]
cls = cls.split('::').inject(::Object) { |o, cst| o.const_get(cst) }
log_caller(cls, meth, sg)
}

File diff suppressed because it is too large Load Diff

View File

@ -258,7 +258,7 @@ class LinDebug
text << ' '
x += r.length + 11
}
text << (' '*(@console_width-x)) << "\n" << ' '
text << (' '*([@console_width-x, 0].max)) << "\n" << ' '
x = 1
%w[esi edi ebp esp].each { |r|
text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r]
@ -281,7 +281,7 @@ class LinDebug
text << ' '
x += 2
}
text << (' '*(@console_width-x)) << "\n"
text << (' '*([@console_width-x, 0].max)) << "\n"
end
def updatecode
@ -342,7 +342,7 @@ class LinDebug
if di
text <<
if addr == @rs.regs_cache['eip']
"*#{di.instruction}".ljust(@console_width-37)
"*#{di.instruction}".ljust([@console_width-37, 0].max)
else
" #{di.instruction}" << Ansi::ClearLineAfter
end
@ -368,7 +368,7 @@ class LinDebug
text << Color[:border]
title = @rs.findsymbol(addr)
pre = [@console_width-100, 6].max
post = @console_width - (pre + title.length + 2)
post = [@console_width - (pre + title.length + 2), 0].max
text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n"
cnt = @win_data_height

View File

@ -14,5 +14,16 @@ class TestDynldr < Test::Unit::TestCase
d.new_api_c('int memcpy(char*, char*, int)')
d.memcpy(str, "9999", 2)
assert_equal('9934', str)
c_src = <<EOS
int sprintf(char*, char*, ...);
void fufu(int i, char* ptr)
{
sprintf(ptr, "lolzor %i\\n", i);
}
EOS
buf = 'aaaaaaaaaaaaaaaaaa'
d.new_func_c(c_src) { d.fufu(42, buf) }
assert_equal("lolzor 42\n\000aaaaaaa", buf)
end
end