This class implements an objects that behaves like a regular string, but whose real data is dynamically fetched or generated on demand its size is immutable implements a page cache substrings are Strings (small substring) or another VirtualString (a kind of 'window' on the original VString, when the substring length is > 4096)
the real address of our first byte
our length
array of [addr, raw data], sorted by first == last accessed
maximum length of self.pagecache (number of cached pages)
# File metasm/os/main.rb, line 198 def initialize(addr_start, length) @addr_start = addr_start @length = length @pagecache = [] @pagecache_len = 4 @pagelength ||= 4096 # must be (1 << x) end
'=~' does not go through #method_missing
# File metasm/os/main.rb, line 184 def =~(o) realstring =~ o end
formats parameters for reading
# File metasm/os/main.rb, line 79 def [](from, len=nil) if not len and from.kind_of? Range b = from.begin e = from.end b = b + length if b < 0 e = e + length if e < 0 len = e - b len += 1 if not from.exclude_end? from = b end from = from + length if from < 0 return nil if from > length or (from == length and not len) len = length - from if len and from + len > length return '' if len == 0 read_range(from, len) end
formats parameters for overwriting portion of the string
# File metasm/os/main.rb, line 99 def []=(from, len, val=nil) raise TypeError, 'cannot modify frozen virtualstring' if frozen? if not val val = len len = nil end if not len and from.kind_of? Range b = from.begin e = from.end b = b + length if b < 0 e = e + length if e < 0 len = e - b len += 1 if not from.exclude_end? from = b elsif not len len = 1 val = val.chr end from = from + length if from < 0 raise IndexError, 'Index out of string' if from > length raise IndexError, 'Cannot modify virtualstring length' if val.length != len or from + len > length write_range(from, val) end
searches the cache for a page containing addr, updates if not found
# File metasm/os/main.rb, line 224 def cache_get_page(addr) addr &= ~(@pagelength-1) i = 0 @pagecache.each { |c| if addr == c[0] # most recently used first @pagecache.unshift @pagecache.delete_at(i) if i != 0 return c end i += 1 } @pagecache.pop if @pagecache.length >= @pagecache_len c = [addr] p = get_page(addr) c << p.to_s.ljust(@pagelength, "\00"") c << true if not p @pagecache.unshift c c end
avoid triggering realstring from #method_missing if possible
# File metasm/os/main.rb, line 157 def empty? length == 0 end
avoid triggering realstring from #method_missing if possible heavily used in to find 0-terminated strings in ExeFormats
# File metasm/os/main.rb, line 163 def index(chr, base=0) return if base >= length or base <= -length if i = self[base, 64].index(chr) or i = self[base, @pagelength].index(chr) base + i else realstring.index(chr, base) end end
invalidates the page cache
# File metasm/os/main.rb, line 212 def invalidate @pagecache.clear end
forwards unhandled messages to a frozen realstring
# File metasm/os/main.rb, line 147 def method_missing(m, *args, &b) if ''.respond_to? m puts "Using VirtualString.realstring for #{m} from:", caller if $DEBUG realstring.freeze.send(m, *args, &b) else super(m, *args, &b) end end
returns wether a page is valid or not
# File metasm/os/main.rb, line 207 def page_invalid?(addr) cache_get_page(@addr_start+addr)[2] end
reads a range from the page cache returns a new VirtualString (using dup) if the request is bigger than @pagelength bytes
# File metasm/os/main.rb, line 246 def read_range(from, len) from += @addr_start 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) s << page[0, from+len-base] end s else # big request: return a new virtual page dup(from, len) end end
returns the full raw data
# File metasm/os/main.rb, line 127 def realstring ret = '' addr = 0 len = length while len > @pagelength ret << self[addr, @pagelength] addr += @pagelength len -= @pagelength end ret << self[addr, len] end
# File metasm/os/main.rb, line 172 def rindex(chr, max=length) return if max > length if max > 64 and i = self[max-64, 64].rindex(chr) max - 64 + i elsif max > @pagelength and i = self[max-@pagelength, @pagelength].rindex(chr) max - @pagelength + i else realstring.rindex(chr, max) end end
alias to realstring for bad people checking respond_to? :to_str (like String#<<) XXX alias does not work (not virtual (a la C++))
# File metasm/os/main.rb, line 142 def to_str realstring end
rewrites a segment of data the length written is the length of the content (a VirtualString cannot grow/shrink)
# File metasm/os/main.rb, line 267 def write_range(from, content) invalidate rewrite_at(from + @addr_start, content) end