Class: OneGadget::Emulators::Processor
- Inherits:
-
Object
- Object
- OneGadget::Emulators::Processor
- Includes:
- Conditional, Constraints, DataProcessing, TrackedMemory
- Defined in:
- lib/one_gadget/emulators/processor.rb
Overview
Base of the per-architecture instruction emulators, used to symbolically execute a candidate and solve its constraints. A subclass implements the arch's supported instructions, calling convention and stack model; what it inherits here is the emulation lifecycle (parse, dispatch, and the calls a candidate may cross) plus four architecture-independent concerns kept in modules of their own: Conditional for the branch/compare machinery, Constraints for what a gadget requires of its caller, TrackedMemory for the memory a candidate reads and writes, and DataProcessing for what an instruction leaves in a register.
To add an architecture, see docs/adding-an-architecture.md.
Constant Summary collapse
- TERMINAL_CALL_RE =
Function names whose call ends a gadget: the real
exec*entry points. Deliberately excludes theposix_spawnsetup helpers (+posix_spawnattr_*+,posix_spawn_file_actions_*), which merely share theposix_spawnprefix. /\A(?:posix_spawnp?|exec(?:ve|l|v)[a-z]*)\z/
Constants included from Constraints
Constraints::ADDRESS_TYPES, Constraints::CLOBBERED, Constraints::NULLABLE_REQUIREMENTS, Constraints::POINTER_REQUIREMENTS
Constants included from Conditional
Conditional::COMPARE_OPS, Conditional::NEGATE, Conditional::RELATION, Conditional::ZERO
Instance Attribute Summary collapse
-
#bp ⇒ String?
readonly
Frame pointer, or nil when this arch tracks none.
-
#pc ⇒ String
readonly
Program counter.
-
#refused_line ⇒ String?
readonly
The line this emulator could not run at all: an instruction outside #instructions.
-
#registers ⇒ RegisterFile
readonly
The current registers' state.
-
#sp ⇒ String
readonly
Stack pointer.
Class Method Summary collapse
-
.bits ⇒ Integer
32 or 64.
-
.instruction_table ⇒ (Array<Instruction>, Hash{String => Instruction})
The architecture's supported instructions, and the same set indexed by mnemonic, built on first use and shared by every emulator of that architecture: the set is fixed, while an emulator is made for each of the thousands of windows a candidate yields.
-
.line_memo(kind) ⇒ Hash
What a line always reads as, remembered per architecture and
kindof reading: a candidate is emulated once for every window it yields, so the same line is read thousands of times, and nothing about how it reads depends on the state the emulator holds.
Instance Method Summary collapse
-
#argument(_idx) ⇒ Lambda, Integer
To be inherited.
-
#initialize(registers, sp) ⇒ Processor
constructor
Instantiate a Processor object.
-
#instructions ⇒ Array<Instruction>
Method need to be implemented in inheritors.
-
#parse(cmd) ⇒ (Instruction, Array<String>)
Parse one command into instruction and arguments.
-
#process(cmd) ⇒ Boolean
Process one command, without raising any exceptions.
-
#process!(_cmd) ⇒ Boolean
Method need to be implemented in inheritors.
-
#reach_terminal_call(addr) ⇒ Symbol
Record a reached terminal
exec*call as the gadget's effect and stop emulating: it is the gadget's goal, and any following instruction would clobber the argument registers that Fetchers::ArgumentResolution#resolve reads to describe it. -
#terminal_call?(addr) ⇒ Boolean
Whether
addrcalls a terminalexec*entry point (see #reach_terminal_call).
Methods included from TrackedMemory
#bp_based_stack, #get_corresponding_stack, #resolve_address, #setup_frame_pointer, #sp_based_stack, #writes_through
Methods included from Constraints
#address_deref0?, #closed_fds, #constraint_key, #constraints, #drop_implied_nonzero, #drop_restated_null, #render_constraint
Methods included from Conditional
#branch_on_bit, #branch_on_compare, #branch_on_zero, #comparisons_on, #handle_compare, #mnemonic, #operand_str, #record_compare, #resolve_pending_branch, #satisfiable?, #value_str
Constructor Details
#initialize(registers, sp) ⇒ Processor
Instantiate a OneGadget::Emulators::Processor object.
43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/one_gadget/emulators/processor.rb', line 43 def initialize(registers, sp) @registers = RegisterFile.build(registers, OneGadget::ABI::NARROW_VIEWS.fetch(arch_name, {})) do |reg| to_lambda(reg) end @sp = sp @constraints = [] @deferred_reads = [] # pointer args of safe calls, resolved once emulation ends @closed_fds = [] # where each descriptor closed before the terminal call comes from @flags = nil # last compare, for a following conditional branch @pending = nil # a conditional branch awaiting one-line-ahead resolution end |
Instance Attribute Details
#bp ⇒ String? (readonly)
Returns Frame pointer, or nil when this arch tracks none.
36 37 38 |
# File 'lib/one_gadget/emulators/processor.rb', line 36 def bp @bp end |
#pc ⇒ String (readonly)
Returns Program counter.
35 36 37 |
# File 'lib/one_gadget/emulators/processor.rb', line 35 def pc @pc end |
#refused_line ⇒ String? (readonly)
The line this emulator could not run at all: an instruction outside #instructions. Only what #parse reads decides that -- the mnemonic and the operands, never the state the emulator holds -- so the same line stops every emulation that reaches it.
143 144 145 |
# File 'lib/one_gadget/emulators/processor.rb', line 143 def refused_line @refused_line end |
#registers ⇒ RegisterFile (readonly)
Returns The current registers' state.
33 34 35 |
# File 'lib/one_gadget/emulators/processor.rb', line 33 def registers @registers end |
#sp ⇒ String (readonly)
Returns Stack pointer.
34 35 36 |
# File 'lib/one_gadget/emulators/processor.rb', line 34 def sp @sp end |
Class Method Details
.bits ⇒ Integer
32 or 64.
298 299 |
# File 'lib/one_gadget/emulators/processor.rb', line 298 def bits; raise NotImplementedError end |
.instruction_table ⇒ (Array<Instruction>, Hash{String => Instruction})
The architecture's supported instructions, and the same set indexed by mnemonic, built on first use and shared by every emulator of that architecture: the set is fixed, while an emulator is made for each of the thousands of windows a candidate yields.
116 117 118 119 120 121 |
# File 'lib/one_gadget/emulators/processor.rb', line 116 def instruction_table @instruction_table ||= begin list = yield [list, list.each_with_object({}) { |i, h| h[i.inst] ||= i }] end end |
.line_memo(kind) ⇒ Hash
What a line always reads as, remembered per architecture and kind of
reading: a candidate is emulated once for every window it yields, so the
same line is read thousands of times, and nothing about how it reads
depends on the state the emulator holds.
106 107 108 |
# File 'lib/one_gadget/emulators/processor.rb', line 106 def line_memo(kind) (@line_memo ||= Hash.new { |memo, k| memo[k] = {} })[kind] end |
Instance Method Details
#argument(_idx) ⇒ Lambda, Integer
To be inherited.
168 169 |
# File 'lib/one_gadget/emulators/processor.rb', line 168 def argument(_idx); raise NotImplementedError end |
#instructions ⇒ Array<Instruction>
Method need to be implemented in inheritors.
158 159 |
# File 'lib/one_gadget/emulators/processor.rb', line 158 def instructions; raise NotImplementedError end |
#parse(cmd) ⇒ (Instruction, Array<String>)
Parse one command into instruction and arguments.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/one_gadget/emulators/processor.rb', line 86 def parse(cmd) self.class.line_memo(:parse)[cmd] ||= begin list, index = self.class.instruction_table { instructions } mnem = cmd[/\A[0-9a-f]+:\s*(\S+)/, 1] || cmd[/\A\s*(\S+)/, 1] inst = index[mnem] # Fall back to the original scan for any mnemonic that isn't a bare word. inst ||= list.find { |i| i.match?(cmd) } raise Error::UnsupportedInstructionError, "Not implemented instruction in #{cmd}" if inst.nil? [inst, inst.fetch_args(cmd)] end end |
#process(cmd) ⇒ Boolean
Process one command, without raising any exceptions.
128 129 130 131 132 133 134 135 136 |
# File 'lib/one_gadget/emulators/processor.rb', line 128 def process(cmd) process!(cmd) # rescue OneGadget::Error::UnsupportedError => e; p e # for debugging rescue OneGadget::Error::UnsupportedInstructionError @refused_line = cmd false rescue OneGadget::Error::Error false end |
#process!(_cmd) ⇒ Boolean
Method need to be implemented in inheritors.
Process one command. Will raise exceptions when encounter unhandled instruction.
153 154 |
# File 'lib/one_gadget/emulators/processor.rb', line 153 def process!(_cmd); raise NotImplementedError end |
#reach_terminal_call(addr) ⇒ Symbol
Record a reached terminal exec* call as the gadget's effect and stop
emulating: it is the gadget's goal, and any following instruction would
clobber the argument registers that Fetchers::ArgumentResolution#resolve reads to describe it.
77 78 79 80 |
# File 'lib/one_gadget/emulators/processor.rb', line 77 def reach_terminal_call(addr) registers[pc] = addr :fail end |
#terminal_call?(addr) ⇒ Boolean
Whether addr calls a terminal exec* entry point (see
#reach_terminal_call). Matches the resolved symbol name exactly so a
setup helper isn't mistaken for the call it precedes.
67 68 69 70 |
# File 'lib/one_gadget/emulators/processor.rb', line 67 def terminal_call?(addr) name = addr[/<([^@>]+)/, 1] !name.nil? && TERMINAL_CALL_RE.match?(name) end |