<commit_before>#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 <NAME>
#
#    Licence is LGPL, see LICENCE in the top-level directory


require 'metasm/gui/dasm_hex'
require 'metasm/gui/dasm_listing'
require 'metasm/gui/dasm_opcodes'
require 'metasm/gui/dasm_coverage'
require 'metasm/gui/dasm_graph'
require 'metasm/gui/dasm_decomp'
require 'metasm/gui/dasm_funcgraph'
require 'metasm/gui/cstruct'

module Metasm
module Gui
# the main disassembler widget: this is a container for all the lower-level widgets that actually render the dasm state
class DisasmWidget < ContainerChoiceWidget
	attr_accessor :dasm, :entrypoints, :gui_update_counter_max
	attr_accessor :keyboard_callback, :keyboard_callback_ctrl	# hash key => lambda { |key| true if handled }
	attr_accessor :clones
	attr_accessor :pos_history, :pos_history_redo
	attr_accessor :bg_color_callback	# proc { |address|  "rgb" # "00f" -> blue }
	attr_accessor :focus_changed_callback
	attr_accessor :parent_widget

	def initialize_widget(dasm, ep=[])
		@dasm = dasm
		@dasm.gui = self
		ep = [ep] if not ep.kind_of? Array
		@entrypoints = ep
		@pos_history = []
		@pos_history_redo = []
		@keyboard_callback = {}
		@keyboard_callback_ctrl = {}
		@clones = [self]
		@parent_widget = nil
		@gui_update_counter_max = 100
		@dasm.callback_prebacktrace ||= lambda { Gui.main_iter }
		start_disassemble_bg

		addview :listing,   AsmListingWidget.new(@dasm, self)
		addview :graph,     GraphViewWidget.new(@dasm, self)
		addview :decompile, CdecompListingWidget.new(@dasm, self)
		addview :opcodes,   AsmOpcodeWidget.new(@dasm, self)
		addview :hex,       HexWidget.new(@dasm, self)
		addview :coverage,  CoverageWidget.new(@dasm, self)
		addview :funcgraph, FuncGraphViewWidget.new(@dasm, self)
		addview :cstruct,   CStructWidget.new(@dasm, self)

		view(:listing).grab_focus
	end

	# start an idle callback that will run one round of @dasm.disassemble_mainiter
	def start_disassemble_bg
		gui_update_counter = 0
		run = false
		Gui.idle_add {
			# metasm disassembler loop
			# update gui once in a while
			if run or not @entrypoints.empty? or not @dasm.addrs_todo.empty?
				protect { run = @dasm.disassemble_mainiter(@entrypoints) }
				gui_update_counter += 1
				if gui_update_counter > @gui_update_counter_max
					gui_update_counter = 0
					gui_update
				end
				true
			else
				gui_update
				false
			end
		}
	end

	def terminate
		@clones.delete self
	end

	# returns the address of the item under the cursor in current view
	def curaddr
		curview.current_address
	end

	# returns the object under the cursor in current view (@dasm.decoded[curaddr])
	def curobj
		@dasm.decoded[curaddr]
	end

	# returns the address of the label under the cursor or the address of the line of the cursor
	def pointed_addr
		hl = curview.hl_word
		if hl =~ /^[0-9].*h$/ and a = hl.to_i(16) and @dasm.get_section_at(a)
			return a
		end
		@dasm.prog_binding[hl] || curview.current_address
	end

	# parse an address and change it to a canonical address form
	# supported formats: label names, or string with numerical value, incl hex (0x42 and 42h)
	# if the string is full decimal, a check against mapped space is done to find if it is
	# hexadecimal (eg 08048000)
	def normalize(addr)
		case addr
		when ::String
			if @dasm.prog_binding[addr]
				addr = @dasm.prog_binding[addr]
			elsif (?0..?9).include? addr[0] or (?a..?f).include? addr.downcase[0]
				case addr
				when /^0x/i
				when /h$/i; addr = '0x' + addr[0...-1]
				when /[a-f]/i; addr = '0x' + addr
				when /^[0-9]+$/
					addr = '0x' + addr if not @dasm.get_section_at(addr.to_i) and
								  @dasm.get_section_at(addr.to_i(16))
				end
				begin
					addr = Integer(addr)
				rescue ::ArgumentError
					return
				end
			else
				return
			end
		end
		addr
	end

	# display the specified address
	# the display first searches in the current view (graph, listing, etc),
	# if it cannot display the address all other views are tried in order
	# the current focus address is saved in @pos_history (see focus_addr_back/redo)
	# a messagebox is popped if no view can display the address unless quiet is true
	def focus_addr(addr, viewidx=nil, quiet=false, *a)
		viewidx ||= curview_index || :listing
		return if not addr
		return if viewidx == curview_index and addr == curaddr
		oldpos = [curview_index, (curview.get_cursor_pos if curview)]
		if [viewidx, oldpos[0], *view_indexes].compact.uniq.find { |i|
			o_p = view(i).get_cursor_pos
			if (view(i).focus_addr(addr, *a) rescue nil)
				view(i).gui_update if i != oldpos[0]
				showview(i)
				true
			else
				view(i).set_cursor_pos o_p
				false
			end
		}
			@pos_history << oldpos if oldpos[0]	# ignore start focus_addr
			@pos_history_redo.clear
			true<commit_msg>dasm/gui: focus_addr only tries argument and listing+graph views<commit_after>#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 <NAME>
#
#    Licence is LGPL, see LICENCE in the top-level directory


require 'metasm/gui/dasm_hex'
require 'metasm/gui/dasm_listing'
require 'metasm/gui/dasm_opcodes'
require 'metasm/gui/dasm_coverage'
require 'metasm/gui/dasm_graph'
require 'metasm/gui/dasm_decomp'
require 'metasm/gui/dasm_funcgraph'
require 'metasm/gui/cstruct'

module Metasm
module Gui
# the main disassembler widget: this is a container for all the lower-level widgets that actually render the dasm state
class DisasmWidget < ContainerChoiceWidget
	attr_accessor :dasm, :entrypoints, :gui_update_counter_max
	attr_accessor :keyboard_callback, :keyboard_callback_ctrl	# hash key => lambda { |key| true if handled }
	attr_accessor :clones
	attr_accessor :pos_history, :pos_history_redo
	attr_accessor :bg_color_callback	# proc { |address|  "rgb" # "00f" -> blue }
	attr_accessor :focus_changed_callback
	attr_accessor :parent_widget

	def initialize_widget(dasm, ep=[])
		@dasm = dasm
		@dasm.gui = self
		ep = [ep] if not ep.kind_of? Array
		@entrypoints = ep
		@pos_history = []
		@pos_history_redo = []
		@keyboard_callback = {}
		@keyboard_callback_ctrl = {}
		@clones = [self]
		@parent_widget = nil
		@gui_update_counter_max = 100
		@dasm.callback_prebacktrace ||= lambda { Gui.main_iter }
		start_disassemble_bg

		addview :listing,   AsmListingWidget.new(@dasm, self)
		addview :graph,     GraphViewWidget.new(@dasm, self)
		addview :decompile, CdecompListingWidget.new(@dasm, self)
		addview :opcodes,   AsmOpcodeWidget.new(@dasm, self)
		addview :hex,       HexWidget.new(@dasm, self)
		addview :coverage,  CoverageWidget.new(@dasm, self)
		addview :funcgraph, FuncGraphViewWidget.new(@dasm, self)
		addview :cstruct,   CStructWidget.new(@dasm, self)

		view(:listing).grab_focus
	end

	# start an idle callback that will run one round of @dasm.disassemble_mainiter
	def start_disassemble_bg
		gui_update_counter = 0
		run = false
		Gui.idle_add {
			# metasm disassembler loop
			# update gui once in a while
			if run or not @entrypoints.empty? or not @dasm.addrs_todo.empty?
				protect { run = @dasm.disassemble_mainiter(@entrypoints) }
				gui_update_counter += 1
				if gui_update_counter > @gui_update_counter_max
					gui_update_counter = 0
					gui_update
				end
				true
			else
				gui_update
				false
			end
		}
	end

	def terminate
		@clones.delete self
	end

	# returns the address of the item under the cursor in current view
	def curaddr
		curview.current_address
	end

	# returns the object under the cursor in current view (@dasm.decoded[curaddr])
	def curobj
		@dasm.decoded[curaddr]
	end

	# returns the address of the label under the cursor or the address of the line of the cursor
	def pointed_addr
		hl = curview.hl_word
		if hl =~ /^[0-9].*h$/ and a = hl.to_i(16) and @dasm.get_section_at(a)
			return a
		end
		@dasm.prog_binding[hl] || curview.current_address
	end

	# parse an address and change it to a canonical address form
	# supported formats: label names, or string with numerical value, incl hex (0x42 and 42h)
	# if the string is full decimal, a check against mapped space is done to find if it is
	# hexadecimal (eg 08048000)
	def normalize(addr)
		case addr
		when ::String
			if @dasm.prog_binding[addr]
				addr = @dasm.prog_binding[addr]
			elsif (?0..?9).include? addr[0] or (?a..?f).include? addr.downcase[0]
				case addr
				when /^0x/i
				when /h$/i; addr = '0x' + addr[0...-1]
				when /[a-f]/i; addr = '0x' + addr
				when /^[0-9]+$/
					addr = '0x' + addr if not @dasm.get_section_at(addr.to_i) and
								  @dasm.get_section_at(addr.to_i(16))
				end
				begin
					addr = Integer(addr)
				rescue ::ArgumentError
					return
				end
			else
				return
			end
		end
		addr
	end

	# display the specified address
	# the display first searches in the current view (graph, listing, etc),
	# if it cannot display the address all other views are tried in order
	# the current focus address is saved in @pos_history (see focus_addr_back/redo)
	# a messagebox is popped if no view can display the address unless quiet is true
	def focus_addr(addr, viewidx=nil, quiet=false, *a)
		viewidx ||= curview_index || :listing
		return if not addr
		return if viewidx == curview_index and addr == curaddr
		oldpos = [curview_index, (curview.get_cursor_pos if curview)]
		views = [viewidx, oldpos[0]]
		views += [:listing, :graph] & view_indexes
		if views.compact.uniq.find { |i|
			o_p = view(i).get_cursor_pos
			if (view(i).focus_addr(addr, *a) rescue nil)
				view(i).gui_update if i != oldpos[0]
				showview(i)
				true
			else
				view(i).set_cursor_pos o_p
				false
			end
		}
			@pos_history << oldpos if oldpos[0]	# ignore start focus_addr
			@pos_history_redo.clear
			true
{'fraction_non_alphanumeric': 0.09552362245425411, 'fraction_numerical': 0.010338054378166029, 'mean_word_length': 3.7867392380009894, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4085105', 'n_tokens_mistral': 3859, 'n_tokens_neox': 3405, 'n_words': 999}