Table of Contents
- Overview
- FAQ
- Quickstart
- A Tour of QEMU
- Record/Replay Details
- Plugins
- Pandalog
- LLVM
- Wish List
- Appendix A: Callback List
PANDA (Platform for Architecture-Neutral Dynamic Analysis) is a whole-system dynamic analysis engine currently based on QEMU 2.9.1. Its strengths lie in rapid reverse engineering of software. PANDA includes a system for recording and replaying execution, a framework for running LLVM analysis on executing code, and an easily extensible plugin architecture. Together, these basic tools let you rapidly understand how individual programs work and how they interact at the system level.
To build PANDA and it's dependencies, use the install script for your OS,
panda/scripts/install_*.sh
. See
our main README for more detail.
Don't worry; it won't actually install PANDA to a system
directory, despite the name. If you already have the dependencies you can just
run qemu/build.sh
. Once it's built, you will find the QEMU binaries in
i386-softmmu/panda-system-i386
, x86_64-softmmu/panda-system-x86_64
, and
arm-softmmu/panda-system-arm
. You'll need to create a qcow (disk image) for use
with PANDA; the internet has documentation on how to do this.
We've found that the most effective workflow in PANDA is to collect a recording of a piece of execution of interest and then analyze that recording over and over again. You can read more about record/replay in our docs. For now, what you need to know is that record/replay allows you to repeat an execution trace with all data exactly the same over and over again. You can then analyze the execution and slowly build understanding about where things are stored, what processes are running, when the key execution events happen, etc. Pictorially:
You can record execution by using the begin_record
and end_record
commands
in the QEMU monitor. To use the monitor, run QEMU with -monitor stdio
(there
are more complicated setups
too). Type begin_record "replay_name"
to start the recording process, and use
end_record
to end it.
Recording will create two files: replay_name-rr-snp
, the VM snapshot at
beginning of recording, and replay_name-rr-nondet.log
, the log of all
nondeterministic inputs. You need both of those to reproduce the segment of
execution.
You can replay a recording (those two files) using panda-system-$arch -replay replay_name
. Make sure you pass the same memory size to the VM as you did for
the recording. Otherwise QEMU will fail with an incomprehensible error.
Once you've captured a replay, you should be able to play it over and over
again. We typically begin by using standard analyses to try and get a basic
picture of what's going on, followed by custom plugins to get more specific
analysis. Plugins reside in the panda_plugins
directory. Although the process depends on the example, some of the plugins we
often use to begin analysis are asidstory
,
stringsearch
, and
file_taint
.
You can also attach a GDB client to replay, allowing you to debug the guest system using PANDA's time-travel debugging.
In order to use PANDA, you will need to understand at least some things about the underlying emulator, QEMU. In truth, the more you know about QEMU the better, but that it is a complicated beast.
This is how you can access and control the emulator, to do all manner of things including connecting an ISO to the CD drive and recording execution. For full details on what you do with the monitor, consult the QEMU manual.
The most common way of interacting with the monitor is just via stdio
in the
terminal from which you originally entered the commandline that started up
PANDA. To get this to work, just add the following to the end of your
commandline: -monitor stdio
. There are also ways to connect to the monitor
over a telnet port etc - refer to the QEMU manual for details.
Here are few monitor functions we commonly need with PANDA.
- Connect an ISO to the cd drive:
change ide1-cd0 foo.iso
. - Begin/end recording:
begin_record foo
andend_record
.
QEMU emulates a large number of instruction set architectures, but only a few of
them are heavily used by PANDA reverse engineers. In particular, PANDA support
is reasonably strong only for x86
, arm
, and ppc
.
It is necessary to have a mental model of how QEMU emulates guest code in order to write plugins. Consider a basic block of guest code that QEMU wants to emulate. It disassembles that code into guest instructions, one by one, simultaneously assembling a parallel basic block of instructions in an intermediate language (IL). This intermediate language is described in a README if you are interested. From this IL, QEMU generates a corresponding basic block of binary code that is directly executable on the host. Note that it is from this QEMU IL that PANDA generates LLVM instructions, as the two are fairly close already (our LLVM translation is actually borrowed from the S2E project). This basic block of code is actually executed, on the host, in order to emulate guest behavior. QEMU toggles between translating guest code and executing the translated binary versions. As a critical optimization, QEMU maintains a cache of already translated basic blocks.
Here is how some of the plugins fit into that emulation sequence.
-
PANDA_CB_BEFORE_BLOCK_TRANSLATE
is before the initial translation of guest code. We don't know length of the block at this point. -
PANDA_CB_BEFORE_TCG_CODEGEN
is before host code is generated from TCG. In this callback, we know the length of the block. -
PANDA_CB_AFTER_BLOCK_TRANSLATE
is after the translation of guest code. In this case we know how long the block is. -
PANDA_CB_BEFORE_BLOCK_EXEC
is after the block of guest code has been translated into code that can run on the host and immediately before QEMU runs it. -
PANDA_CB_AFTER_BLOCK_EXEC
is immediately after the block of translated guest code has actually been run on the host. -
PANDA_CB_BEFORE_BLOCK_EXEC_INVALIDATE_OPT
is right after the guest code has been translated into code that can run on the host, but before it runs. In some situations, plugin code determines that it is necessary to re-translate and can trigger that here, in particular in order to support LLVM lifting and taint. -
PANDA_CB_INSN_TRANSLATE
is just before an instruction is translated, and allows inspection of the instruction to control how translation inserts other plugin callbacks such as theINSN_EXEC
one. -
PANDA_CB_INSN_EXEC
is just before host code emulating a guest instruction executes, but only exists ifINSN_TRANSLATE
callback returned true.
NOTE. Although it is a little out of date, the explanation of emulation in Fabrice Bellard's original USENIX paper on QEMU is quite a good read. "QEMU, a Fast and Portable Dynamic Translator", USENIX 2005 Annual Technical Conference.
NOTE: QEMU has an additional cute optimization called chaining
that links up
cached translated blocks of code in such a way that they emulation can
transition from one to another without the emulator being involved. This is
enabled for record but currently turned off for replay in order to more easily
support callbacks before and after a basic block executes.
PANDA plugins need access to cpu registers and state. The QEMU abstract data
type for this CPUState
and is accessed through a global pointer env
. Note
that the actual type for an emulated CPU is made more specific in the
qemu/target-xxx/cpu.h
directory where xxx
is the architecture in question.
For instance, in qemu/target-i386/cpu.h
, we find it redefined as CPUX86State
,
where we also find convenient definitions such as EAX
, EBX
, and EIP
.
Other information of interest such as hidden flags, segment registers, idt
,
and gdt
are all available via env
.
These functions don't really form an API to QEMU or PANDA, but they are useful for controlling PANDA or interacting with QEMU.
void panda_do_flush_tb(void);
This function requests that the translation block cache be flushed as soon as possible. If running with translation block chaining turned off (e.g. when in LLVM mode or replay mode), this will happen when the current translation block is done executing.
Flushing the translation block cache is additionally necessary if the plugin
makes changes to the way code is translated. For example, by using
panda_enable_precise_pc
.
WARNING: failing to flush the TB before turning on something that alters
code translation may cause QEMU to crash! This is because QEMU's interrupt
handling mechanism relies on translation being deterministic (see the
search_pc
stuff in translate-all.c
for details).
void panda_disable_tb_chaining(void);
void panda_enable_tb_chaining(void);
These functions allow plugins to selectively turn translation block chaining on and off, regardless of whether the backend is TCG or LLVM, and independent of record and replay.
By default, QEMU does not update the program counter after every instruction.
void panda_enable_precise_pc(void);
void panda_disable_precise_pc(void);
These functions enable or disable precise tracking of the program counter.
After enabling precise PC tracking, the program counter will be available in
env->panda_guest_pc
and can be assumed to accurately reflect the guest state. In most cases the helper function panda_current_pc
should be used. It will provide the precise PC if enabled.
Some plugins (taint2
, callstack_instr
, etc) add instrumentation that runs
inside a basic block of emulated code. If such a plugin is enabled mid-replay
then it is important to flush the cache so that all subsequent guest code will
be properly instrumented.
PANDA has callbacks for virtual and physical memory read and write, but these are off by default due to overhead.
void panda_enable_memcb(void);
void panda_disable_memcb(void);
Use these two functions to enable and disable the memory callbacks.
int panda_physical_memory_rw(target_phys_addr_t addr, uint8_t *buf, int len, int is_write);
This function allows a plugin to read or write len
bytes of guest physical
memory at addr
into or from the supplied buffer buf
. This function differs
from QEMU's cpu_physical_memory_rw
in that it will never access I/O, only
RAM. This function returns zero on success, and non-zero values on failure (page not mapped).
int panda_virtual_memory_rw(CPUState *env, target_ulong addr, uint8_t *buf, int len, int is_write);
This function is analogous to the previous one except that it uses the current
virtual to physical mapping (page tables) to permit read and write of guest
memory. It has the same contract but the addr
is a guest virtual address for
the current process.
void panda_enable_llvm(void);
void panda_disable_llvm(void);
These functions enable and disable the use of the LLVM JIT in replacement of the TCG (QEMU intermediate language and compiler) backend. Here, an additional translation step is added from the TCG IR to the LLVM IR, and that is executed on the LLVM JIT. Currently, this only works when QEMU is starting up, but we are hoping to support dynamic configuration of code generation soon.
int panda_vm_quit(void);
int panda_record_begin(const char *name, const char *snapshot);
int panda_record_end(void);
int panda_replay_begin(const char *name);
int panda_replay_end(void);
These functions can be used to programatically start/stop recording on PANDA.
Starting/stopping does not happen imediatelly at the time the functions are
called. Instead, a request to start/stop recording is registered to be applied
at the end of the currently executing basic block. Only one request can be
queued at any time.
The name
argument is mandatory and is used to derive the snapshot/log
filenames to be created/used.
The snapshot
argument is optional (i.e. can be NULL
). If supplied,
the state of the VM will be reverted to the QEMU snapshot with that name.
Possible return values are:
RRCTRL_OK
: Request registered successfully.RRCTRL_EPENDING
: Request ignored because another record/replay state change request is pending.RRCTRL_ERROR
: Request is invalid. E.g. because you are trying to end a recording during a replay.
-
Saves a physical memory snapshot into the open file pointer
void panda_memsavep(FILE *out);
out
. This function is guaranteed not to perturb guest state. -
Returns the current asid for any of the PANDA supported architectures (e.g.
target_ulong panda_current_asid(CPUState *cpu);
cr3
for x86). -
Returns
bool panda_in_kernel(CPUState *cpu);
true
if the processor is in the privilege level corresponding to executing kernel code for any of the PANDA supported architectures. -
Returns the guest stack pointer for any of the PANDA supported architectures.
target_ulong panda_current_sp(CPUState *cpu);
-
Retrieves the return value of a returned call for any of the PANDA supported architectures. The function has to be called in the proper context in order to return a meaningful value. If the context is not right (i.e. the return value has already been overwritten), it will return garbage.
target_ulong panda_get_retval(CPUState *cpu);
-
Writes a textual representation of disassembly of the guest code at virtual address
void panda_disas(FILE *out, void *code, unsigned long size);
code
ofsize
bytes. -
Returns current program counter. If
target_ulong panda_current_pc(CPUState *cpu);
panda_precise_pc
has been enabled it returns the precise value.
PANDA supports whole system deterministic record and replay in whole
system mode on the i386
, x86_64
, and arm
targets. We also support
running in live mode on mips
, but not yet record and replay. We hope
to add more soon; for example, partial SPARC support exists but is not
yet reliable.
Deterministic record and replay is a technique for capturing the non-deterministic inputs to a system -- that is, the things that would cause a system to behave differently if it were re-started from the same point with the same inputs. This includes things like network packets, hard drive reads, mouse and keyboard input, etc.
Our implementation of record and replay focuses on reproducing code
execution. That is, the non-deterministic inputs we record are changes
made to the CPU state and memory -- DMA, interrupts, in
instructions,
and so on. Unlike many record and replay implementations, we do not
record the inputs to devices; this means that one cannot "go live"
during a recording, but it greatly simplifies the implementation. To get
an idea of what is recorded, imagine drawing a line around the CPU and
RAM; things going from the outside world to the CPU and RAM, crossing
this line, must be recorded.
Record and replay is extremely useful because it enables many sophisticated analysis that are too slow to run in real-time; for example, trying to do taint flow analysis makes the guest system so slow that it cannot make network connections (because remote systems time out before the guest can process the packets and respond). By creating a recording, which has fairly modest overhead, and performing analyses on the replayed execution, one can do analyses that simply aren't possible to do live.
Recording is controlled with two QEMU monitor commands, begin_record and end_record:
-
begin_record <name>
Starts a recording session, saved as
<name>
. Note that there is currently no safeguard to prevent overwriting previous recordings, so be careful to choose a unique name.The recording log consists of two parts: the snapshot, which is named
<name>-rr-snp
, and the recording log, which is named<name>-rr-nondet.log
. -
end_record
Ends an active recording session. The guest will be paused, but can be resumed and another recording can be made once the guest is resumed.
A commandline flag -record-from <snapshot>:<record-name>
to restores a
qcow2 snapshot and immediately start recording is also provided for convenience.
Start replays from the command line using the -replay <name>
option.
Of course, just running a replay isn't very useful by itself, so you will probably want to run the replay with some plugins enabled that perform some analysis on the replayed execution. See Plugins for more details.
You can also debug the guest under replay using PANDA's time-travel debugging.
To create a recording with the new rr2
format, simply add .rr2
as an
extention to your begin_record
call, e.g. begin_record new_format.rr2
. The
new format produces a .tar.gz
file with extention ending in .rr2
.
The compressed file contains a magic RRv2
, the snapshot
, the nondetlog
, a
capture.cmd
containing the command line command used to initiate PANDA for
recording, and a sha1
file containing the sha1 hashes of the other files.
The replay command supports both the new rr2
format, as well as the old
format. When you start a replay, the format will automatically be detected.
To produce a rr2
file with an old recording use the rr2pack.py
script, which can be found in the scripts
directory. To pack up a recording,
just use:
scripts/rr2pack.py <name> --output <output_name>
This will bundle up <name>-rr-snp
and <name>-rr-nondet.log
and put
them into PANDA's packed record/replay format in a file named
<output_name>.rr2
. This file can be unpacked using:
tar -xvf <output_name>.rr2
For older recordings use the scripts/rrpack.py
. To pack an old recording into
the old packed record/replay format use:
scripts/rrpack.py <name>
This will bundle up <name>-rr-snp
and <name>-rr-nondet.log
and put
them into PANDA's old packed record/replay format, in a file named
<name>.rr
. This file can be unpacked and verified using:
scripts/rrunpack.py <name>.rr
A central repository for sharing record/replay logs is available at the PANDA Share website.
A great deal of the power of PANDA comes from its abiltiy to be extended with plugins. Plugins are an easy way to extend the features of PANDA, and allow a wide range of dynamic analyses to be implemented without modifying QEMU.
You will probably begin experimenting with PANDA by just using the plugins
others have written. To load a PANDA plugin, you must specify it via -panda
on the QEMU command line followed by the plugin name. You can specify multiple
plugins as a semicolon-separated list, and you can give the plugins arguments as
a comma-separated list after the plugin's name and a colon. For example:
-panda 'stringsearch;callstack_instr;llvm_trace:base=/tmp,foo=6'
This loads the stringsearch
, callstack_instr
, and llvm_trace
plugins and
passes llvm_trace
the arguments base=dir
and foo=bar
. Note that the ;
character must be escaped in most shells; you can either surround the arguments
with quotes (as in this example) or just escape the semicolon itself,
e.g. base=dir\;foo=bar
.
Plugins are automatically unloaded when a replay ends.
Plugins allow you to register callback functions that will be executed at various points as QEMU executes. While it is possible to register and run callbacks during record, it is more usual for plugins to be used during replay. The PANDA RE workflow is (see Overview section for diagram) record, then replay multiple times under a variety of plugins, possibly some of which have been written for the RE task at hand.
Some of these callbacks and where they occur in QEMU's execution are shown below:
If you are using multiple plugins that work together to perform some analysis, you may care about what order plugins' callbacks execute in, since some operations may not make sense if they're done out of order.
The bad news is that PANDA does not guarantee any fixed ordering for its callbacks across different plugins. In the current implementation, each callback of a given type will be executed in the order it was registered. Although this creates a deterministic order for callbacks of a specific type within one plugin, a fixed callback execution order among multiple distinct plugins can not be ensured. (Usually, this order corresponds to the order in which the plugins were loaded; however, because callbacks can be registered at any time throughout a plugin's lifetime, this is not guaranteed to hold true). This could change in the future, though, and in general it's not a good idea to rely on it.
The good news is that there's a better way to enforce an ordering. As described
in the next section, plugins support explicit mechanisms for interacting with
each other. In particular, you can create plugin callbacks, which allow plugins
to notify each other when certain events inside the plugin occur. For example,
if you wanted to ensure that something in Plugin B always happens after Plugin A
does some action foo
, Plugin A would create an on_foo
callback that Plugin B
could then register with. This is much safer and more robust than trying to
guess the order in which the plugin callbacks will be called.
See the Plugin-Plugin Interaction section for details on this mechanism.
To create a PANDA plugin, create a new directory inside plugins
,
and copy Makefile.example
into
plugins/${YOUR_PLUGIN}/Makefile
. Then edit the Makefile to suit the
needs of your plugin (at minimum, you should change the plugin name). By
default, the source file for your plugin must be named ${YOUR_PLUGIN}.(c|cpp)
,
but this can be changed by editing the Makefile.
To have your plugin compiled as part of the main QEMU build process, you should
add it to plugins/config.panda
. Note that plugins in that list can
be disable (excluded from compilation) by commenting them out with a #
.
Plugins can currently be written in either C or C++.
When you run make
, the QEMU build system will build your plugin for each
target architecture that was specified in ./configure --target-list=
. This
means that architecture-specific parts of your plugin should be guarded using
code like:
#if defined(TARGET_I386)
// Do x86-specific stuff
#elif defined(TARGET_ARM)
// Do ARM-specific stuff
#endif
It also means that your code can use the various target-specific macros, such as
target_ulong
, in order to get code that works with all of QEMU's
architectures.
All plugins are required to contain, at minimum, two functions with the following signatures:
bool init_plugin(void *self);
void uninit_plugin(void *self);
The single void * parameter is a handle to the plugin; because this comes from
dlopen
, it can be safely used with dlsym
and friends. This handle is also
what should be passed to panda_register_callback
in order to register a plugin
function as a callback.
In general, init_plugin
should perform any setup the plugin needs, and call
panda_register_callback
to tell PANDA what plugin functions to call for
various events. For example, to register a callback that will be executed after
the execution of each basic block, you would use the following code:
pcb.after_block_exec = after_block_callback;
panda_register_callback(self, PANDA_CB_AFTER_BLOCK_EXEC, pcb);
The uninit_plugin
function will be called when the plugin is unloaded. You
should free any resources used by the plugin here, as plugins can be unloaded in
mid replay or from the monitor – so you can't rely on QEMU doing all your
cleanup for you.
Typically in the init_plugin
function, you will register a number of
callbacks.
void panda_register_callback(void *plugin, panda_cb_type type, panda_cb cb);
Registers a callback with PANDA. The type
parameter specifies what type of
callback, and cb
is used for the callback itself (panda_cb
is a union of all
possible callback signatures).
PANDA_CB_BEFORE_BLOCK_TRANSLATE,// Before translating each basic block
PANDA_CB_BEFORE_TCG_CODEGEN, // Before host codegen of each basic block.
PANDA_CB_AFTER_BLOCK_TRANSLATE, // After translating each basic block
PANDA_CB_BEFORE_BLOCK_EXEC_INVALIDATE_OPT, // Before executing each basic block (with option to invalidate, may trigger retranslation)
PANDA_CB_BEFORE_BLOCK_EXEC, // Before executing each basic block
PANDA_CB_AFTER_BLOCK_EXEC, // After executing each basic block
PANDA_CB_INSN_TRANSLATE, // Before an instruction is translated
PANDA_CB_INSN_EXEC, // Before an instruction is executed
PANDA_CB_AFTER_INSN_TRANSLATE, // After an insn is translated
PANDA_CB_AFTER_INSN_EXEC, // After an insn is executed
PANDA_CB_VIRT_MEM_BEFORE_READ, // Before read of virtual memory
PANDA_CB_VIRT_MEM_BEFORE_WRITE, // Before write to virtual memory
PANDA_CB_PHYS_MEM_BEFORE_READ, // Before read of physical memory
PANDA_CB_PHYS_MEM_BEFORE_WRITE, // Before write to physical memory
PANDA_CB_VIRT_MEM_AFTER_READ, // After read of virtual memory
PANDA_CB_VIRT_MEM_AFTER_WRITE, // After write to virtual memory
PANDA_CB_PHYS_MEM_AFTER_READ, // After read of physical memory
PANDA_CB_PHYS_MEM_AFTER_WRITE, // After write to physical memory
PANDA_CB_MMIO_AFTER_READ, // After each MMIO read
PANDA_CB_MMIO_BEFORE_WRITE, // Before each MMIO write
PANDA_CB_HD_READ, // Each HDD read
PANDA_CB_HD_WRITE, // Each HDD write
PANDA_CB_GUEST_HYPERCALL, // Hypercall from the guest (e.g. CPUID)
PANDA_CB_MONITOR, // Monitor callback
PANDA_CB_CPU_RESTORE_STATE, // In cpu_restore_state() (fault/exception)
PANDA_CB_BEFORE_LOADVM, // At start of replay, before loadvm
PANDA_CB_ASID_CHANGED, // After an ASID (address space identifier - aka PGD) write
PANDA_CB_REPLAY_HD_TRANSFER, // In replay, hd transfer
PANDA_CB_REPLAY_NET_TRANSFER, // In replay, transfers within network card (currently only E1000)
PANDA_CB_REPLAY_SERIAL_RECEIVE, // In replay, right after data is pushed into the serial RX FIFO
PANDA_CB_REPLAY_SERIAL_READ, // In replay, right after a value is read from the serial RX FIFO.
PANDA_CB_REPLAY_SERIAL_SEND, // In replay, right after data is popped from the serial TX FIFO
PANDA_CB_REPLAY_SERIAL_WRITE, // In replay, right after data is pushed into the serial TX FIFO.
PANDA_CB_REPLAY_BEFORE_DMA, // In replay, just before RAM case of cpu_physical_mem_rw
PANDA_CB_REPLAY_AFTER_DMA, // In replay, just after RAM case of cpu_physical_mem_rw
PANDA_CB_REPLAY_HANDLE_PACKET, // In replay, packet in / out
PANDA_CB_AFTER_CPU_EXEC_ENTER, // Just after cpu_exec_enter is called
PANDA_CB_BEFORE_CPU_EXEC_EXIT, // Just before cpu_exec_exit is called
PANDA_CB_AFTER_MACHINE_INIT, // Right after the machine is initialized, before any code runs
PANDA_CB_AFTER_VMLOAD, // Right after machine state is restored from a snapshot, before any code runs
PANDA_CB_START_BLOCK_EXEC, // TCG stream start of block
PANDA_CB_END_BLOCK_EXEC, // TCG stream end of block
PANDA_CB_TOP_LOOP, // At top of loop that manages emulation. good place to take a snapshot
For more information on each callback, see the "Callbacks" section.
void * panda_get_plugin_by_name(const char *name);
Retrieves a handle to a plugin, given its name (the name is just the base name
of the plugin's filename; that is, if the path to the plugin is
qemu/panda/panda_test.so
, the plugin name will be panda_test.so
).
This can be used to allow one plugin to call functions another, since the handle
returned is usable with dlsym
.
bool panda_load_plugin(const char *filename);
Load a PANDA plugin. The filename
parameter is currently interpreted as a
simple filename; no searching is done (this may change in the future). This can
be used to allow one plugin to load another.
void panda_unload_plugin(void *plugin);
Unload a PANDA plugin. This can be used to allow one plugin to unload another one.
void panda_disable_plugin(void *plugin);
Disables callbacks registered by a PANDA plugin. This can be used to allow one plugin to temporarily disable another one.
void panda_enable_plugin(void *plugin);
Enables callbacks registered by a PANDA plugin. This can be used to re-enable callbacks of a plugin that was disabled.
PANDA allows plugins to receive arguments on the command line. For instance, consider that example from above.
-panda 'stringsearch;callstack_instr;llvm_trace:base=/tmp,foo=6'
The llvm_trace
plugin has two arguments, base
and foo
with values /tmp
and 6
. In init_plugin
for llvm_trace
, include the following code to
retrieve just the arguments for the llvm_trace
plugin and then to parse the individual arguments.
panda_arg_list *args = panda_get_args("llvm_trace");
uint32_t foo = panda_parse_uint32(args, "foo", 0);
char *base = panda_parse_string(args, "base", NULL);
Here is the complete list of PANDA arg parsing functions.
target_ulong panda_parse_ulong(panda_arg_list *args, const char *argname, target_ulong defval);
uint32_t panda_parse_uint32(panda_arg_list *args, const char *argname, uint32_t defval);
uint64_t panda_parse_uint64(panda_arg_list *args, const char *argname, uint64_t defval);
double panda_parse_double(panda_arg_list *args, const char *argname, double defval);
bool panda_parse_bool(panda_arg_list *args, const char *argname);
const char *panda_parse_string(panda_arg_list *args, const char *argname, const char *defval);
Note that calling panda_get_args
allocates memory to store the list, which
should be freed after use with panda_free_args
.
void panda_free_args(panda_arg_list *args);
Frees an argument list created with panda_get_args
.
It's often very handy to be able to allow plugins to interact with one another.
For example, the taint2
tracks taint, and exposes an API for labeling data and
querying what taint labels are on some data. This allows one to create plugins
that are small and do one thing well but can be composed together to accomplish
complex tasks.
There three kinds of plugin-plugin-interaction: exposing an API that other plugins can call, defining callback points that other plugins can register with to be notified when something of interest occurs, and turning another plugin on or off.
Interactions between plugins, however, are tricky because PANDA plugins are
dynamically loaded. Thus, even if Plugin A has a function intended to be called
by another plugin, it is painful to obtain access to that function from Plugin B
(hint: dlsym
is involved). Further, the code necessary to iterate over a
sequence of callbacks is annoying and formulaic.Software engineering to the
rescue! panda/plugin_plugin.h
provides a number of convenient macros that
simplify arranging for these two types of plugin interaction. Here is how to use
them.
To export an API for use in another plugin:
- Create a file named
<plugin>_int_fns.h
in the plugin's directory and list each function's prototype, along with any data types it requires. - Create a file named
<plugin>_int.h
in the plugin's directory looks like:
typedef void YourCustomType;
typedef void YourOtherCustomType;
#include "<plugin>_int_fns.h"
This slightly insane-looking arrangement is necessary because the apigen.py
script (which is invoked from build.sh
) uses pycparser
to parse each
plugin's <plugin>_int.h
header and generate the necessary code to seamlessly
use the API from other plugins. Unfortunately, pycparser
is not great at
understanding custom types (i.e. anything that's not in the standard system
headers), so you have to use typedefs
to make pycparser
happy. This is
indeed ridiculous, and if you have a better way to do it we'd welcome your pull
request with open arms.
At build time, apigen.py
will automatically find all the plugins with those
files, and generate a <plugin>_ext.h
file for each one, in the plugin's
directory. To use the API, one simply needs to #include "../<plugin>/<plugin>_ext.h"
and then call init_<plugin>_api()
and ensure that its return value is true
.
For example, to use the functions exported by the sample
plugin, you can do
something like:
#include "../sample/sample_ext.h"
void some_other_callback() {
// This function is defined in sample_ext.h but can be used here
sample_function();
}
bool init_plugin(void *self) {
if (!init_sample_api()) return false;
return true;
}
Suppose you have Plugin A and Plugin B, and Plugin B wants to be notified when something interesting happens in Plugin A. There are two halves to this: creating the pluggable place in Plugin A, and registering a callback implemented in Plugin B with Plugin A.
In order to create the pluggable place in Plugin A, you have to do the following.
-
Determine at precisely what line in A you want callbacks to run. Also, decide what arguments the callback functions will take. Also also, choose a name for the callback, e.g.,
foo
. -
Create a type for the callback function. Put this in the .h file for Plugin A. If the callback's name is
foo
, then this type has to be calledfoo_t
. This type shold be generated using thePPP_CB_TYPEDEF
macro in the form ofPPP_CB_TYPEDEF(return_type, name, arguments...);
. For examplePPP_CB_TYPEDEF(void, foo, CPUState *env, target_ptr_t pc);
. -
Use the macro
PPP_RUN_CB
at the line chosen in 1. This macro takes all the arguments you want the callback to get, so it will look like a function call. But it will expand into code that runs all callbacks registered to run there, handing each all those args. -
In the same file you edited in 3, use the macro
PPP_CB_BOILERPLATE
somewhere above the line decided in 1, just not inside of a function. This macro takes a single argument, the callback name, and expands into a bunch of necessary code: a global array of function pointers, an integer keeping track of how many functions have been registered, and a pair of functions that can be used from outside Plugin A to register callbacks. -
In the same file you edited in 3, in the
extern "C" {
portion near the top of the file, addPPP_PROT_REG_CB(foo);
. For more information on this, seepanda/plugin_plugin.h
. -
Remember to
#include "panda/plugin_plugin.h"
at the top of the edited source file.
In order to register a callback with Plugin A that is defined in Plugin B, all
you need to do is use the PPP_REG_CB
macro in Plugin B's init_plugin function
and include Plugin A's .h file where the type for the callback is defined (see
#2). This macro takes three arguments. The first is the name of plugin A (as
in, its name in the Makefile). The second is the callback name. The third is the
function in B that is to be registered with A.
A good example of how all this fits together can be seen in the interaction
between the stringsearch
and tstringsearch
plugins. stringsearch
is plugin
A. It has one pluggable site: when a string match occurs. The name of that
callback is on_ssm
for "on stringsearch match". Look in stringsearch.h
to
see the type definition for the callback functions on_ssm_t
. Look in
stringsearch.cpp
for the macro invocations of PPP_RUN_CB
and
PPP_CB_BOILERPLATE
. tstringsearch
is plugin B. It contains a function
tstringsearch_match
which it registers with stringsearch
via the
PPP_REG_CB
macro in order to apply taint labels to any string match. Their
powers combined, these two plugins allow us to perform a complicated task
(content-based taint labeling).
You can also pull plugin code from some other directory, i.e., not from
panda/panda/plugins
. This allows you to maintain a separate repository
of your personal plugins.
- Create a directory in which you will create personal plugins.
/home/you/personal
- Create a subdirectory
personal/panda/plugins
there as well. - Say you have written a plugin you want to call
new_cool
. Create a subdirectorypersonal/panda/plugins/new_cool
and put the code for the new plugin there. - Create a file
panda/plugins/config.panda
with names of enabled plugins as you would normally. - You can use the the same makefile set-up as with regular plugins.
- configure with
--extra-plugins-path=/home/you/personal
- Build as usual and you should compile
new_cool
plugin and its compiled code will be deposited in, e.g.,i386-softmmu/panda_plugins
PANDA offers four functions for enabling and disabling other plugins at runtime:
panda_load_plugin
, panda_unload_plugin
, panda_enable_plugin
, and
panda_disable_plugin
. The former completely load or unload plugins while the
latter allow to temporarily enable or disable callbacks registered by a given
plugin). For their prototypes, have a look at panda_plugin.h
.
We have written a bunch of generic plugins for use in analyzing replays. Each one has a README.md file linked here for further explanation.
taint2
- Modern taint plugin. Required by most other taint plugins.Already ported from panda1
ida_taint2
- IDA taint integration.Already ported from panda1
file_taint
- Syscall and OSI-based automatic tainting of file input by filename.Already ported from panda1
tainted_branch
- Find conditional branches where the choice depends on tainted data.Already ported from panda1
tainted_instr
- Find instructions which process tainted data.Already ported from panda1
taint_compute_numbers
- Analyze taint compute numbers (computation tree depth) for tainted data.tstringsearch
- Automatically taint all occurrences of a certain string.Already ported from panda1
Plugins related to Tappan Zee (North) Bridge
stringsearch
- Mine memory accesses for a particular string.Already ported from panda1
textfinder
textprinter
Already ported from panda1
textprinter_fast
unigrams
Already ported from panda1
bigrams
memdump
keyfind
memsnap
memstrings
correlatetaps
Already ported from panda1
tapindex
Already ported from panda1
callstack_instr
- Instruction-based callstack tracing.Already ported from panda1
fullstack
printstack
callstack_block_pc
- Old block-based callstack tracing.
osi
- Operating system introspection framework.Already ported from panda1
osi_linux
- Generic Linux OSI.Already ported from panda1
osi_test
Already ported from panda1
osi_winxpsp3x86
- OSI for Windows XP SP3 x86.asidstory
- ASCII art view of process execution inside VM.Already ported from panda1
linux_vmi
- Alternate Linux OSI system from DECAF.debianwheezyx86intro
- OSI for Debian 7 x86.testdebintro
win7x86intro
- OSI for Windows 7 x86.Already ported from panda1
win2000x86intro
- OSI for Windows 2000 x86.winxpx86intro
- OSI for Windows XP SP3 x86.
syscalls2
- Modern syscalls tracking.Already ported from panda1
win7proc
- Semantic pandalog interpretation of syscalls for Windows 7 x86.
bir
- Binary Information Retrieval. Used to correspond executables on disk with code executing in memory.tralign
- Align parts of execution traces.bufmon
- Monitor all memory accesses to a particular memory region.coverage
Already ported from panda1
llvm_trace
- Record trace of dynamic information necessary for later analysis.lsmll
memsavep
- Create a dump of physical memory at a given point in a replay. The dump can then be fed to Volatility.Already ported from panda1
memstats
network
Already ported from panda1
pmemaccess
recctrl
- Control recording from within the VM.rehosting
replaymovie
- Write a series of framebuffer screenshots to the current directory. Use movie.sh to turn them into a movie.Already ported from panda1
sample
scissors
- Cut out a smaller piece of a given replay.Already ported from panda1
useafterfree
- Track memory allocations and search for uses after frees.func_stats
- Collect synthetic information of called functions for CFG, DFG analysis.
PANDA analyses run on whole system replays and the clear temptation is to just print out what you learn as you learn it. So panda plugins often begin life peppered with print statements. There is nothing wrong with print statements. But, as a plugin matures, it is usual for the consumers of those print statements to yearn for more compact, more parseable output. Pandalog provides this in the form of protocol buffer messages, streamed to a file through zlib's file access functions.
Pandalog is designed to be
- Fast to read and write
- Small log size
- Easy to add to a plugin
- Easy to write code that reads the log
- Useable from any C or C++ panda plugin
Goals 1 and 2 are (arguably) provided by Google's protocol buffers. Protocol buffers optimize for small message size. Marshalling / unmarshalling is reasonably speedy. Better than JSON. We would have liked to use something like flatbuffers (also from Google), which is optimized more for read/write speed (we want FAST plugins, dammit). But this would have violated goal 5, as there is no way to auto-generate code for C with flatbuffers, as yet. A big design goal here (3) was for the logging spec to be distributed throughout the plugins. That is, if new plugin foo wants to write something to the pandalog, it should only have to specify what new fields it wants to add to the pandalog and add the actual logging statements.
There's both a C and C++ API for pandalogging; as of late 2017, the C API is just a wrapper around the C++ that exists for compatibility with C plugins.
For both C and C++, the first step is creating a protobuf schema. The asidstory
plugin is a good example.
$ cd plugins/asidstory/
$ cat asidstory.proto
optional uint64 asid = 3;
optional string process_name = 4;
optional uint32 process_id = 5;
This file contains a snippet from a protocol buffer schema. It indicates that
this plugin will be adding three new optional fields to the pandalog, one for
the asid
(address space id), one for the process_name
, and another for the
process_id
. Note that these fields are given tag numbers. This is
important in so far as no two protobuf fields can have the same number (we don't
know why). That is a global constraint you need to be aware of across all
plugins. If asidstory
uses slot 3, then plugin foo
better not try to use it
as well. Don't worry; if you screw this up, you'll get an error at build time.
Asidstory is also an example of using the old pandalog C API.
extern "C" {
...
#include "panda/plog.h"
...
}
...
int asidstory_before_block_exec(CPUState *env, TranslationBlock *tb) {
...
if (pandalog) {
if (last_name == 0
|| (p->asid != last_asid)
|| (p->pid != last_pid)
|| (0 != strcmp(p->name, last_name))) {
Panda__LogEntry ple = PANDA__LOG_ENTRY__INIT;
ple.has_asid = 1;
ple.asid = p->asid;
ple.has_process_id = 1;
ple.process_id = p->pid;
ple.process_name = p->name;
pandalog_write_entry(&ple);
last_asid = p->asid;
last_pid = p->pid;
free(last_name);
last_name = strdup(p->name);
}
}
...
The logging message was inserted into the function
asidstory_before_block_exec
, and the logic is complicated by the fact that we
are keeping track of the last asid, process name, and process id. When any of
them change, we write a pandalog message. All of that is incidental.
Note that we have available to us a global pandalog
, which we can use to
determine if panda logging is turned on.
To add the logging message, you have to create the ple
, initializing it as so:
Panda__LogEntry ple = PANDA__LOG_ENTRY__INIT;
The definition of the Panda__LogEntry
struct can be found in plog.pb-c.h
in your PANDA build directory. One such header file exists per target architecture.
All you have to do is to populate ple
with the fields that you want to log.
For optional scalar fields, there is always a has_fieldname
bool you need to
set to indicate their presence. For optional pointer or string fields, a NULL value
indicates that the field is not present.
Here is the part of the code above in which we populate the struct for logging:
ple.has_asid = 1;
ple.asid = p->asid;
ple.has_process_id = 1;
ple.process_id = p->pid;
ple.process_name = p->name;
Now all that is left is to write the entry to the pandalog.
pandalog_write_entry(&ple);
The above C functions are defined in panda/include/panda/plog.h
and panda/src/plog.c
, but you'll notice that they are just wrappers around C++ functions. The wrappers simply pass on the data to the C++ functions that actually do the work of reading/writing to the log. The functions that bridge the two are declared in panda/include/panda/plog-cc-bridge.h
.
Using the C++ API is similar. The main difference is that there is a global object of the PandaLog
class called globalLog
, which all plugins use to read and write from the log. In addition, the C++ protobuf code provides the LogEntry
class under the panda
namespace, and getter and setter functions for the fields.
It's also recommended to use std::unique_ptrs
to initialize each LogEntry
, for automatic memory management. Here's an example of this:
extern PandaLog globalLog;
...
if (pandalog){
std::unique_ptr<panda::LogEntry> ple (new panda::LogEntry());
ple->mutable_llvmentry()->set_type(FunctionCode::FUNC_CODE_INST_CALL);
ple->mutable_llvmentry()->set_address(addr);
globalLog.write_entry(std::move(ple));
}
The panda::LogEntry
class is defined in the autogenerated plog.pb.h
, and the PandaLog
class is in panda/src/plog-cc.cpp
.
Any .proto
files you add will be automatically picked up by the PANDA
Makefile and concatenated into a single plog.proto
file per target architecture.
These files are subsequently processed by the protoc
and protoc-c
compilers to autogenerate the C++ and C protobuf files: plog.pb.[cc/h]
and plog.pb-c.[c/h]
.
All the generated files are located under the target-specific
subdirectories of your PANDA build directory.
E.g. build-panda/arm-softmmu/plog.pb-c.h
.
Feel free to peek at any of those auto-generated files.
In particular, you will probably want to consult the headers,
since they defines the logging structs Panda__LogEntry
and panda::LogEntry
.
PANDA logging is enabled at runtime with a new command-line arg.
-pandalog filename
Any specified plugins that write to the pandalog will log to that file, which is
written via zlib
file access functions for compression.
There is a small program in panda/src/example_plog_reader.cpp
, which also serves as an example of reading/writing with the C++ pandalog API.
Compilation directions are at the head of that source file. This example will only print the PC and instr. You can customize the source code to read additional information from your pandalog.
You can also use the panda/scripts/plog_reader.py
script to view a log. This will read not require edits to the code, however this method may be slower.
You can read a pandalog using either program and also see how easy it is to unmarshall the pandalog. Here's how to use it and some of its output.
$ ./pandalog_reader /tmp/pandlog | head
instr=16356 pc=0xc12c3586 : asid=2 pid=171 process=[jbd2/sda1-8]
instr=78182 pc=0xc12c3586 : asid=2 pid=4 process=[kworker/0:0]
instr=80130 pc=0xc12c3586 : asid=2 pid=171 process=[jbd2/sda1-8]
instr=142967 pc=0xc12c3586 : asid=2 pid=4 process=[kworker/0:0]
instr=209715 pc=0xc12c3586 : asid=7984000 pid=2511 process=[sshd]
instr=253940 pc=0xc12c3586 : asid=2 pid=4 process=[kworker/0:0]
instr=256674 pc=0xc12c3586 : asid=5349000 pid=2512 process=[bash]
instr=258267 pc=0xc12c3586 : asid=7984000 pid=2511 process=[sshd]
instr=262487 pc=0xc12c3586 : asid=2 pid=4 process=[kworker/0:0]
instr=268164 pc=0xc12c3586 : asid=5349000 pid=2512 process=[bash]
Note that there are two required fields always added to every pandalog entry: instruction count and program counter. The rest of these log messages come from the asidstory logging.
You may want to search google for "Protocol Buffers" to learn more about it.
PANDA uses the LLVM architecture from the S2E
project. This means you can translate from
QEMU's intermediate representation, TCG, to LLVM IR, which is easier to
understand and platform-independent. We call this process "lifting". Lifting has
non-trivial overhead, but it enables complex analyses like our taint2
plugin.
We use the LLVM JIT to directly execute the LLVM code. In fact, taint2
relies
on this capability, as it inserts the taint operations directly into the stream
of LLVM instructions. One of the quirks of the QEMU execution mopdel is that
exotic instructions are implemented as C code which changes the CPUState
struct. These are called helper functions. We use Clang to compile each of the
helper functions directly into LLVM IR. We then link the compiled helper
functions into the LLVM module containing the lifted LLVM code. When we JIT the
lifted LLVM blocks, the helper functions can be called directly. Unfortunately,
the LLVM infrastructure is pretty slow; expect roughly a 10x slowdown with
respect to QEMU's normal TCG execution mode.
You can access the LLVM code for a certain TranslationBlock
by using the
llvm_tc_ptr
field in the TranslationBlock
struct. This is a pointer to an
llvm::Function
object. We recommend using an llvm::FunctionPass
to run over
each TranslationBlock
you would like to analyze. Initialize the
FunctionPassManager
like this:
extern "C" TCGLLVMContext *tcg_llvm_ctx;
panda_enable_llvm();
panda_enable_llvm_helpers();
llvm::FunctionPassManager *fpm = tcg_llvm_ctx->getFunctionPassManager();
fpm->add(new MyFunctionPass());
FPM->doInitialization();
The pass will then run after each block is translated. You want to have the pass
insert callbacks into the generated code that accept the dynamic values as
arguments (pointers, for example). Look at taint2
(taint2.cpp) for a (very complicated)
example.
What is missing from PANDA? What do we know how to do but just don't have time for? What do we not know how to do?
- How do I modify values in a replay?
PANDA does not by default allow you to modify values in a replay.
- How do I start/continue the system after a replay?
PANDA does not enable you to continue the system after a replay.
- So, what can I do with PANDA?
You can use custom or prebuilt plugins to analyze a replay at the full OS level. See plugins for examples.
- How do I trim my replay to include the executed parts of interest?
That's what the scissors
plugin is for!
- I'm getting an error like
Length mismatch: pc.ram: 0x100000000 in != 0x8000000: Invalid argument
, what do I do?
You need to provide PANDA with the amount of RAM to use (and it must be the same as you used when making the recording). If you forgot, you can look at the error message to tell; in this case it's saying that it expects RAM to be 0x100000000
bytes, which is 4G, so you need to provide -m 4G
on the command line.
before_block_translate
: called before translation of each basic block
Callback ID: PANDA_CB_BEFORE_BLOCK_TRANSLATE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC we are about to translate
Return value:
unused
Signature:
void (*before_block_translate)(CPUState *env, target_ulong pc);
before_tcg_codegen
: called after lifting a guest basic block to TCG but before optimization and host code generation
Callback ID: PANDA_CB_BEFORE_TCG_CODEGEN
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB that is about to be just-in-time (JIT) compiled
Return value:
unused
Notes:
This is a good place to inspect or modify the TCG block right after a guest
basic block has been lifted to TCG. For an example use, see the coverage
plugin source code.
after_block_translate
: called after the translation of each basic block
Callback ID: PANDA_CB_AFTER_BLOCK_TRANSLATE
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB we just translated
Return value:
unused
Notes:
This is a good place to perform extra passes over the generated code (particularly by manipulating the LLVM code) FIXME: How would this actually work? By this point the out ASM has already been generated. Modify the IR and then regenerate?
Signature:
int (*after_block_translate)(CPUState *env, TranslationBlock *tb);
before_block_exec
: called before execution of every basic block
Callback ID: PANDA_CB_BEFORE_BLOCK_EXEC
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB we are about to execute
Return value:
unused
Signature:
int (*before_block_exec)(CPUState *env, TranslationBlock *tb);
before_block_exec_invalidate_opt
: called before execution of every basic
block, with the option to invalidate the TB
Callback ID: PANDA_CB_BEFORE_BLOCK_EXEC_INVALIDATE_OPT
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB we are about to execute
Return value:
true
if we should invalidate the current translation block and retranslate, false
otherwise
Signature:
bool (*before_block_exec_invalidate_opt)(CPUState *env, TranslationBlock *tb);
after_block_exec
: called after execution of every basic block
Callback ID: PANDA_CB_AFTER_BLOCK_EXEC
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB we just executeduint8_t exitCode
: one of theTB_EXIT_
constants fromtcg.h
Return value:
unused
Notes:
The exitCode
can be used to determine if the block was executed to completion, or if it was interrupted by an exit request and execution must be retried later. When exitCode <= TB_EXIT_IDX1
, then execution completed normally; otherwise, it was stopped and will be retried later.
Signature::
int (*after_block_exec)(CPUState *env, TranslationBlock *tb, uint8_t exitCode);
insn_translate
: called before the translation of each instruction
Callback ID: PANDA_CB_INSN_TRANSLATE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC we are about to translate
Return value:
true
if PANDA should insert instrumentation into the generated code,
false
otherwise
Notes:
This allows a plugin writer to instrument only a small number of
instructions, avoiding the performance hit of instrumenting everything.
If you do want to instrument every single instruction, just return
true. See the documentation for PANDA_CB_INSN_EXEC
for more detail.
Signature:
bool (*insn_translate)(CPUState *env, target_ulong pc);
insn_exec
: called before execution of any instruction identified
by the PANDA_CB_INSN_TRANSLATE
callback. Only enabled for blocks where
the insn_translate
callback returns true.
Callback ID: PANDA_CB_INSN_EXEC
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC we are about to execute
Return value:
unused
Notes:
This instrumentation is implemented by generating a call to a
helper function just before the instruction itself is generated.
This is fairly expensive, which is why it's only enabled via
the PANDA_CB_INSN_TRANSLATE
callback.
Signature:
int (*insn_exec)(CPUState *env, target_ulong pc);
after_insn_exec
: called after execution of any instruction identified
by the PANDA_CB_INSN_TRANSLATE
callback. Only enabled for blocks where
the after_insn_translate
callback returns true.
Callback ID: PANDA_CB_AFTER_INSN_EXEC
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC we are about to execute
Return value:
unused
Notes:
This instrumentation is implemented by generating a call to a
helper function just after the instruction itself is generated.
This is fairly expensive, which is why it's only enabled via
the PANDA_CB_AFTER_INSN_TRANSLATE
callback.
Signature:
int (*after_insn_exec)(CPUState *env, target_ulong pc);
virt_mem_before_read
: called before memory is read
Callback ID: PANDA_CB_VIRT_MEM_BEFORE_READ
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the readtarget_ulong addr
: the (virtual) address being readtarget_ulong size
: the size of the read
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*virt_mem_before_read)(CPUState *env, target_ulong pc,target_ulong addr, target_ulong size);
virt_mem_before_write
: called before memory is write
Callback ID: PANDA_CB_VIRT_MEM_BEFORE_WRITE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the writetarget_ulong addr
: the (virtual) address being writtentarget_ulong size
: the size of the writevoid *buf
: pointer to the data that is to be written
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*virt_mem_before_write)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
phys_mem_before_read
: called before memory is read
Callback ID: PANDA_CB_PHYS_MEM_BEFORE_READ
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the readtarget_ulong addr
: the (physical) address being readtarget_ulong size
: the size of the read
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*phys_mem_before_read)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size);
phys_mem_before_write
: called before memory is written
Callback ID: PANDA_CB_PHYS_MEM_BEFORE_WRITE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the writetarget_ulong addr
: the (physical) address being writtentarget_ulong size
: the size of the writevoid *buf
: pointer to the data that is to be written
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*phys_mem_before_write)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
virt_mem_after_read
: called after memory is read
Callback ID: PANDA_CB_VIRT_MEM_AFTER_READ
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the readtarget_ulong addr
: the (virtual) address being readtarget_ulong size
: the size of the readvoid *buf
: pointer to data just read
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*virt_mem_after_read)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
virt_mem_after_write
: called after memory is written
Callback ID: PANDA_CB_VIRT_MEM_AFTER_WRITE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the writetarget_ulong addr
: the (virtual) address being writtentarget_ulong size
: the size of the writevoid *buf
: pointer to the data that was written
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*virt_mem_after_write)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
phys_mem_after_read
: called after memory is read
Callback ID: PANDA_CB_PHYS_MEM_AFTER_READ
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the writetarget_ulong addr
: the (physical) address being writtentarget_ulong size
: the size of the writevoid *buf
: pointer to the data that was written
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*phys_mem_after_read)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
phys_mem_after_write
: called after memory is written
Callback ID: PANDA_CB_PHYS_MEM_AFTER_WRITE
Arguments:
CPUState *env
: the current CPU statetarget_ulong pc
: the guest PC doing the writetarget_ulong addr
: the (physical) address being writtentarget_ulong size
: the size of the writevoid *buf
: pointer to the data that was written
Return value:
unused
Notes:
You must call panda_enable_memcb()
to turn on memory callbacks
before this callback will take effect.
Signature:
void (*phys_mem_after_write)(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf);
guest_hypercall
: called when a program inside the guest makes a
hypercall to pass information from inside the guest to a plugin
Callback ID: PANDA_CB_GUEST_HYPERCALL
Arguments:
CPUState *env
: the current CPU state
Return value:
true
if the callback has processed the hypercall, false
if the
hypercall has been ignored.
Notes:
This feature is implemented using the CPUID instruction for x86 guests and the MCR instructions for ARM guests. Both of these instructions are unprivileged, which makes development and use of the feature easier. They are also available across the different variants of the architectures.
For x86, PANDA opted out from using AMD's SVM and Intel's VT hypercalls
because they are privileged instructions. This makes them harder to
integrate with the guest environment.
For ARM, PANDA specifies coprocessor 7 (p7) as the target of the MCR
instruction (move to coprocessor from register). p7 is reserved by ARM
and not implemented in QEMU, so it can be handled without causing
conflicts.
For AARCH64, PANDA uses a system register that is not implemented in QEMU.
Specifically, it uses the instruction msr S0_0_c5_c0_0, xzr
.
PANDA also opted out from using an undefined opcode to implement the
hypercall functionality. This is the approach taken by S2E, but it has
the drawback that during development you need to output raw bytes
instead of using inline assembly.
Plugins need to check for a magic value in the registers in order to determine if this is a guest hypercall they need to process. Further parameters can be passed in other registers.
If the plugin has processed the hypercall, it should return true
.
On x86, this prevents the regular CPUID code from running to avoid
clobbering of register. This allows hypercalls to return values to
the guest. It also allows detecting when a hypercall is processed
by more than one plugins (possible conflict of magic values).
More importantly, an analyzed binary can't directly use the CPUID
instrucion to probe whether it runs inside a PANDA VM.
On ARM, the return value of callbacks is not currently used. However,
the MCR instruction with p7 as target should result in a nop. This means
that the state of the processor shouldn't change and any values returned
to the guest through r0, r1 will not be clobbered.
PANDA has modified translate.c to make CPUID/MCR instructions end translation blocks. This is useful e.g. for dynamically turning on LLVM and enabling heavyweight instrumentation at at a specific point in execution.
Signature:
bool (*guest_hypercall)(CPUState *env);
monitor
called when someone uses the plugin_cmd
monitor command
Callback ID: PANDA_CB_MONITOR
Arguments:
Monitor *mon
: a pointer to the Monitorconst char *cmd
: the command string passed to plugin_cmd
Return value:
unused
Notes:
The command is passed as a single string. No parsing is performed
on the string before it is passed to the plugin, so each plugin
must parse the string as it deems appropriate (e.g. by using strtok
and getopt
) to do more complex option processing.
It is recommended that each plugin implementing this callback respond to the "help" message by listing the commands supported by the plugin.
Note that every loaded plugin will have the opportunity to respond to
each plugin_cmd
; thus it is a good idea to ensure that your plugin's
monitor commands are uniquely named, e.g. by using the plugin name
as a prefix (sample_do_foo
rather than do_foo
).
Signature:
int (*monitor)(Monitor *mon, const char *cmd);
cpu_restore_state
: Called inside of cpu_restore_state(), when there is a
CPU fault/exception
Callback ID: PANDA_CB_CPU_RESTORE_STATE
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the current translation block
Return value: unused
Signature:
int (*cpu_restore_state)(CPUState *env, TranslationBlock *tb);
before_loadvm
: called at the start of replay, just before the snapshot state
is loaded
Callback ID: PANDA_CB_BEFORE_REPLAY_LOADVM
Arguments:
None.
Return value:
unused
Notes:
This allows us to hook devices' loadvm handlers (remember to unregister the existing handler for the device first)
An example of how to use this callback can be found in the sample
plugin.
Signature
int (*before_loadvm)(void);
asid_changed
: Called right before the CPU ASID register (e.g. cr3
for x86) is updated. The state of other CPU registers may or may not
have been updated (depends on the QEMU implementation).
The callback is currently only implemented for x86 and ARM.
Callback ID: PANDA_CB_ASID_CHANGED
Arguments:
CPUState* env
: pointer to CPUStatetarget_ulong oldval
: old ASID (address space identifier) valuetarget_ulong newval
: new ASID (address space identifier) value
Return value:
true
if the asid should be prevented from being changed, false
otherwise
Signature:
int (*asid_changed)(CPUState *env, target_ulong oldval, target_ulong newval);
replay_hd_transfer
: Called during a replay of a hard drive transfer action
Callback ID: PANDA_CB_REPLAY_HD_TRANSFER
Arguments:
CPUState* env
: pointer to CPUStateuint32_t type
: type of transfer (Hd_transfer_type)uint64_t src_addr
: address for srcuint64_t dest_addr
: address for destuint32_t num_bytes
: size of transfer in bytes
Return value: unused
Notes:
In replay only, some kind of data transfer involving hard drive. NB: We are neither before nor after, really. In replay the transfer doesn't really happen. We are at the point at which it happened, really. Even though the transfer doesn't happen in replay, useful instrumentations (such as taint analysis) can still be applied accurately.
The allowed values for type are:
HD_TRANSFER_HD_TO_IOB
HD_TRANSFER_IOB_TO_HD
HD_TRANSFER_PORT_TO_IOB
HD_TRANSFER_IOB_TO_PORT
HD_TRANSFER_HD_TO_RAM
HD_TRANSFER_RAM_TO_HD
Signature:
int (*replay_hd_transfer)(CPUState *env, uint32_t type, uint64_t src_addr,
uint64_t dest_addr, uint32_t num_bytes);
replay_net_transfer
: Called during a replay of a network transfer action
Callback ID: PANDA_CB_REPLAY_NET_TRANSFER
Arguments:
CPUState* env:
pointer to CPUStateuint32_t type:
type of transfer (Net_transfer_type)uint64_t src_addr:
address for srcuint64_t dest_addr:
address for destuint32_t num_bytes:
size of transfer in bytes
Return value: unused
Notes:
In replay only, some kind of data transfer within the network card (currently, only the E1000 is supported). NB: We are neither before nor after, really. In replay the transfer doesn't really happen. We are at the point at which it happened, really.
Signature:
int (*replay_net_transfer)(CPUState *env, uint32_t type, uint64_t src_addr,
uint64_t dest_addr, uint32_t num_bytes);
replay_before_cpu_physical_mem_rw_ram
: In replay only, we are about to dma
from some qemu buffer to guest memory
Callback ID: PANDA_CB_REPLAY_BEFORE_CPU_PHYSICAL_MEM_RW_RAM
Arguments:
CPUState* env
: pointer to CPUStateuint32_t is_write
: type of transfer going on (is_write == 1 means IO -> RAM else RAM -> IO)uint64_t src_addr
: src of dmauint64_t dest_addr
: dest of dmauint32_t num_bytes
: size of transfer
Return value: unused
Notes:
In the current version of QEMU, this appears to be a less commonly used method
of performing DMA with the hard drive device. For the hard drive, the most
common DMA mechanism can be seen in the PANDA_CB_REPLAY_HD_TRANSFER_TYPE
under
type HD_TRANSFER_HD_TO_RAM
(and vice versa). Other devices still appear to use
cpu_physical_memory_rw() though.
Signature:
int (*replay_before_cpu_physical_mem_rw_ram)(
CPUState *env, uint32_t is_write, uint64_t src_addr, uint64_t dest_addr,
uint32_t num_bytes);
replay_after_cpu_physical_mem_rw_ram
: In replay only, we have just done a DMA
from some QEMU buffer to guest memory
Callback ID: PANDA_CB_REPLAY_AFTER_CPU_PHYSICAL_MEM_RW_RAM
Arguments:
CPUState* env
: pointer to CPUStateuint32_t is_write
: type of transfer going on (is_write == 1 means IO -> RAM else RAM -> IO)uint64_t src_addr
: src of DMAuint64_t dest_addr
: dest of DMAuint32_t num_bytes
: size of transfer
Return value: unused
Notes:
In the current version of QEMU, this appears to be a less commonly used method of performing DMA with the hard drive device. For the hard drive, the most common DMA mechanism can be seen in the PANDA_CB_REPLAY_HD_TRANSFER_TYPE under type HD_TRANSFER_HD_TO_RAM (and vice versa). Other devices still appear to use cpu_physical_memory_rw() though.
Signature:
int (*replay_after_cpu_physical_mem_rw_ram)(
CPUState *env, uint32_t is_write, uint8_t* src_addr, uint64_t dest_addr,
uint32_t num_bytes);
replay_handle_packet
: used for network packet replay
Callback ID: PANDA_CB_REPLAY_HANDLE_PACKET
Arguments:
CPUState *env
: pointer to CPUStateuint8_t *buf
: buffer containing packet dataint size
: num bytes in bufferuint8_t direction
:PANDA_NET_RX
for receive,PANDA_NET_TX
for transmituint64_t buf_addr_rec
: the address thatbuf
had when the recording was taken
Return value:
unused
Signature:
int (*replay_handle_packet)(CPUState *env, uint8_t *buf, int size,
uint8_t direction, uint64_t buf_addr_rec);
after_cpu_exec_enter
: called right after cpu_exec calls the cpu_exec_enter function
Callback ID: PANDA_CB_AFTER_CPU_EXEC_ENTER
Arguments:
CPUState *env
: pointer to CPUState
Return value:
unused
Signature
int after_cpu_exec_enter(CPUState *env);
before_cpu_exec_exit
: called right before cpu_exec calls the cpu_exec_exit function
Callback ID: PANDA_CB_BEFORE_CPU_EXEC_EXIT
Arguments:
CPUState *env
: pointer to CPUStatebool ranBlock
: true if ran a block since the previous cpu_exec_enter
Return value:
unused
Signature
int before_cpu_exec_exit(CPUState *env, bool ranBlock);
after_machine_init
: called right after the machine has been initialized,
but before any guest code runs
Callback ID: PANDA_CB_AFTER_MACHINE_INIT
Arguments:
CPUState *env
: pointer to CPUState
Return value:
unused
Notes:
This callback allows initialization of components that need access to
the RAM, CPU object, etc.
E.g. if you need to enable taint2
taint tracking for the whole of the
replay, this would be an appropriate place to call taint2_enable_taint()
.
Signature
void after_machine_init(CPUState *env);
after_vmload
: called right after a snapshot has been loaded,
but before any guest code runs
Callback ID: PANDA_CB_AFTER_VMLOAD
Arguments:
CPUState *env
: pointer to CPUState
Return value:
unused
Signature
void after_vmload(CPUState *env);
start_block_exec
: This is like before_block_exec except its part of the TCG stream.
Callback ID: PANDA_CB_START_BLOCK_EXEC
Arguments:
CPUState *env
: the current CPU stateTranslationBlock *tb
: the TB we are executing
Return value: none
Notes
This callback is inserted into the TCG stream near before_tcg_codegen
.
Signature
void (*start_block_exec)(CPUState *cpu, TranslationBlock* tb);
end_block_exec
: This is like after_block_exec except its part of the TCG stream.
Callback ID: PANDA_CB_END_BLOCK_EXEC
Arguments:
- CPUState *env: the current CPU state
- TranslationBlock *tb: the TB we are executing
Return value: none
Signature
void (*end_block_exec)(CPUState *cpu, TranslationBlock* tb);
``