Skip to content

Commit f639f56

Browse files
committed
docs: add two more 'misc features' to emulator docs
1 parent fb7c7bb commit f639f56

File tree

3 files changed

+85
-13
lines changed

3 files changed

+85
-13
lines changed

docs/emulator.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ reusable scripts, but `emu["EAX"]` is faster to type when working interactively.
112112

113113
### Hooks
114114

115-
Often we are very interested in the details of the execution, and we want to
116-
process every executed instruction. For this we can use the `callback` parameter:
115+
Often we are interested in the details of the execution, and we want to
116+
process every instruction in some way. We can easily do this using the `callback` parameter:
117117

118118
```python
119119
>>> emu = Emulator()
@@ -349,7 +349,7 @@ Especially if you're emulatingh random pieces of code, setting maxsteps
349349
to something reasonable (like 2000 instructions) may save you from accidentaly
350350
executing an infinite loops.
351351

352-
**breakpoints**
352+
**Breakpoints**
353353

354354
You can set and remove breakpoints using `add_breakpoint` and `clear_breakpoint`
355355
methods
@@ -365,6 +365,52 @@ The downside is that it doesn't support callbacks or instruction counting -
365365
you can only emulate until a specific address. As an upside, function hooks
366366
are supprted.
367367

368+
**Emulation shorthands**
369+
370+
To save precious keystrokes you may combine creating an emulator, running it,
371+
and inspecting the result into one step with:
372+
373+
```python
374+
>>> Emulator.new("main", maxsteps=100)["EAX"]
375+
128
376+
```
377+
378+
This convenience wrapper is equivalent to the following code:
379+
380+
```python
381+
>>> emu = Emulator()
382+
>>> emu.emulate("main", maxsteps=100)
383+
>>> emu["EAX"]
384+
128
385+
```
386+
387+
Some other objects also provide helpers to do the obvious thing with emulator.
388+
For example, you can emulate a function call with:
389+
390+
391+
```python
392+
>>> emu = Function("test").emulate(10)
393+
>>> emu["EAX"]
394+
113
395+
>>> # Or an even shorter version
396+
>>> Function("test").emulate_simple(10)
397+
113
398+
```
399+
400+
**Unicorn compatibility**
401+
402+
There is a very, very thin compatibility layer with Unicorn. There are aliases
403+
provided for the following Unicorn methods: `reg_write`, `reg_read`, `mem_write`,
404+
`mem_read`, `mem_map`, `emu_start`. Why? The idea is that many people already
405+
know Unicorn. It may make it a tiny bit easier for them if they can use familiar
406+
method names instead of learning a completely new set.
407+
408+
The goal is not to provide actual compatibility layer - Unicorn is a very different
409+
library and `ghidralib` won't replace it. The only goal is really so Unicorn users
410+
can use familiar names if they forget ghidralib equivalents. If you are not
411+
an Unicorn user, don't use them.
412+
413+
368414
### Learn more
369415

370416
Check out relevant examples in the `examples` directory, especially:

ghidralib.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,18 +2308,16 @@ def infer_context(self): # type: () -> Emulator
23082308
"""Emulate the code before this function call, and return the state.
23092309
23102310
The goal of this function is to recover the state of the CPU
2311-
before the function call, as well as possible. This will work well in case
2312-
the assembly looks like, for example:
2311+
before the function call, as well as possible. This will work well when
2312+
parameters are constants written just before the call, for example:
23132313
23142314
mov eax, 30
23152315
mov ebx, DAT_encrypted_string
23162316
call decrypt_string
23172317
2318-
Then recovering eax them is as simple as call.infer_context()["eax"]."""
2318+
Then recovering eax is as simple as call.infer_context()["eax"]."""
23192319
basicblock = BasicBlock(self._address)
2320-
emu = Emulator()
2321-
emu.emulate(basicblock.start_address, self._address)
2322-
return emu
2320+
return Emulator.new(basicblock.start_address, self._address)
23232321

23242322
@property
23252323
def high_pcodeop(self): # type: () -> PcodeOp|None
@@ -3593,7 +3591,7 @@ def single_step(self): # type: () -> bool
35933591
return False
35943592

35953593
@staticmethod
3596-
def emulate_new(
3594+
def new(
35973595
start,
35983596
ends=[],
35993597
callback=lambda emu: None,
@@ -3602,8 +3600,20 @@ def emulate_new(
36023600
): # type: (Addr, Addr|list[Addr], Callable[[Emulator], str|None], Callable[[Emulator], bool], int) -> Emulator
36033601
"""Emulate from start to end address, with callback for each executed address.
36043602
3605-
This function creates a new emulator, runs emulate on it, and returns it.
3606-
See emulate documentation for information about the parameters."""
3603+
>>> Emulator.new("main", maxsteps=100)["EAX"]
3604+
128
3605+
3606+
This function is a convenience wrapper around emulate and can be always
3607+
replaced by three lines of code. The above is equivalent to:
3608+
3609+
>>> emu = Emulator()
3610+
>>> emu.emulate("main", maxsteps=100)
3611+
>>> emu["EAX"]
3612+
128
3613+
3614+
This function may be used for quickly doing one-off emulations.
3615+
3616+
See `emulate` documentation for info about this method parameters."""
36073617
emu = Emulator()
36083618
emu.emulate(start, ends, callback, stop_when, maxsteps)
36093619
return emu
@@ -3621,7 +3631,7 @@ def emulate(
36213631
>>> emu = Emulator()
36223632
>>> def callback(emu):
36233633
>>> print("executing {:x}'.format(emu.pc))
3624-
>>> emu.trace(Function("main").entrypoint, callback=callback, maxsteps=3)
3634+
>>> emu.emulate(Function("main").entrypoint, callback=callback, maxsteps=3)
36253635
SUB ESP,0x2d4
36263636
PUSH EBX
36273637
PUSH EBP
@@ -3678,6 +3688,17 @@ def is_at_breakpoint(self): # type: () -> bool
36783688
"""Check if the emulator is at a breakpoint"""
36793689
return self.raw.getEmulator().isAtBreakpoint()
36803690

3691+
# Basic unicorn compatibility, because why not
3692+
# You may prefer these aliases if you already know Unicorn API.
3693+
reg_write = write_register
3694+
reg_read = read_register
3695+
mem_write = write_bytes
3696+
mem_read = read_bytes
3697+
mem_map = (
3698+
lambda _1, _2, _3: None
3699+
) # This is a noop - all memory is already available.
3700+
emu_start = lambda self, begin, until: self.emulate(begin, until)
3701+
36813702

36823703
class MemoryBlock(GhidraWrapper, BodyTrait):
36833704
"""A Ghidra wrapper for a Ghidra MemoryBlock"""

tests/ghidralib_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ def test_emulator():
556556
emu = Emulator()
557557
emu.add_breakpoint(0x403ED0)
558558
emu.emulate(0x403EC1)
559+
assert emu["esi"] == 0xFFFF
559560
assert emu.pc == 0x403ED0
560561

561562
emu = Emulator()
@@ -564,6 +565,10 @@ def test_emulator():
564565
assert emu["esi"] == 0xFFFF
565566
assert emu.pc == 0x403ED0
566567

568+
emu = Emulator.new(0x403ECB, 0x403ED0)
569+
assert emu["esi"] == 0xFFFF
570+
assert emu.pc == 0x403ED0
571+
567572
emu.single_step()
568573
assert emu.pc == 0x405F96
569574

0 commit comments

Comments
 (0)