Skip to content

integration ¤

Provides decompiler integration by leveraging decomp2dbg (mahaloz/decomp2dbg).

Communicates with the decomp2dbg decompiler plugins by following the API laid out in https://github.com/mahaloz/decomp2dbg/blob/main/decompilers/server_template.py.

Code used as reference: + https://github.com/mahaloz/decomp2dbg/blob/main/decomp2dbg/clients/client.py + https://github.com/mahaloz/decomp2dbg/blob/main/decomp2dbg/clients/gdb/gdb_client.py

Classes:

Attributes:

manual_binary_address module-attribute ¤

manual_binary_address: int = -1

manager module-attribute ¤

GlobalVariable dataclass ¤

GlobalVariable(name: str, addr: int)

Attributes:

name instance-attribute ¤

name: str

addr instance-attribute ¤

addr: int

GlobalVariables dataclass ¤

GlobalVariables(vars: list[GlobalVariable])

Attributes:

vars instance-attribute ¤

vars: list[GlobalVariable]

FunctionHeader dataclass ¤

FunctionHeader(name: str, addr: int, size: int)

Attributes:

name instance-attribute ¤

name: str

addr instance-attribute ¤

addr: int

size instance-attribute ¤

size: int

FunctionHeaders dataclass ¤

FunctionHeaders(funcs: list[FunctionHeader])

Attributes:

funcs instance-attribute ¤

funcs: list[FunctionHeader]

RegisterVariable dataclass ¤

RegisterVariable(name: str, type: str, reg_name: str)

Attributes:

name instance-attribute ¤

name: str

type instance-attribute ¤

type: str

reg_name instance-attribute ¤

reg_name: str

StackVariable dataclass ¤

StackVariable(
    name: str, type: str, from_sp: int | None, from_frame: int | None
)

Attributes:

name instance-attribute ¤

name: str

type instance-attribute ¤

type: str

from_sp instance-attribute ¤

from_sp: int | None

from_frame instance-attribute ¤

from_frame: int | None

RebasedStackVariable dataclass ¤

RebasedStackVariable(name: str, type: str, addr: int)

Attributes:

name instance-attribute ¤

name: str

type instance-attribute ¤

type: str

addr instance-attribute ¤

addr: int

FuncVariables dataclass ¤

FuncVariables(
    stack_vars: list[StackVariable], reg_vars: list[RegisterVariable]
)

Attributes:

stack_vars instance-attribute ¤

stack_vars: list[StackVariable]

reg_vars instance-attribute ¤

reg_vars: list[RegisterVariable]

RebasedFuncVariables dataclass ¤

RebasedFuncVariables(
    stack_vars: list[RebasedStackVariable], reg_vars: list[RegisterVariable]
)

Attributes:

stack_vars instance-attribute ¤

stack_vars: list[RebasedStackVariable]

reg_vars instance-attribute ¤

reg_vars: list[RegisterVariable]

FuncDecompilationResult dataclass ¤

FuncDecompilationResult(
    decompilation: list[str], curr_line: int, func_name: str
)

Attributes:

decompilation instance-attribute ¤

decompilation: list[str]

curr_line instance-attribute ¤

curr_line: int

func_name instance-attribute ¤

func_name: str

DecompilerID ¤

Bases: Enum

Attributes:

IDA class-attribute instance-attribute ¤

IDA = 'IDA'

BINARYNINJA class-attribute instance-attribute ¤

BINARYNINJA = 'Binary Ninja'

GHIDRA class-attribute instance-attribute ¤

GHIDRA = 'Ghidra'

ANGR class-attribute instance-attribute ¤

ANGR = 'angr'

DecompilerConnection ¤

DecompilerConnection(server: ServerProxy)

Allows communication with the decompiler.

The lifecycle of this object is tied to the connection to the compiler. It is only constructed after a successful connection, and must not be used after the connection dies.

You should expect every function here to be able to throw ConnectionRefusedError.

Methods:

  • addr_to_mapped

    Takes an address relative to the image/file base and

  • addr_to_relative

    Takes an address from the live process' address space and returns

  • disconnect

    Disconnects from the XML RPC server.

  • decompile

    See IntegrationManager.decompile() for the function description.

  • function_data

    See IntegrationManager.function_data() for the function description.

  • function_headers

    See IntegrationManager.function_headers() for the function description.

  • global_vars

    See IntegrationManager.global_vars() for the function description.

  • structs
  • breakpoints
  • focus_address

    See IntegrationManager.focus_address() for the function description.

Attributes:

  • server (ServerProxy) –

    The (host filesystem) path of the binary loaded in the decompiler.

  • binary_path (str) –

    Version information about the decompiler we are connected to. See

  • versions (dict[str, str]) –

    The address of the start of the binary in the live process address space.

  • binary_base_addr (int) –

server instance-attribute ¤

server: ServerProxy = server

The (host filesystem) path of the binary loaded in the decompiler. It can be both an executable and a shared library.

binary_path instance-attribute ¤

binary_path: str = str(binary_path())

Version information about the decompiler we are connected to. See plugin server_template.py for the format.

versions instance-attribute ¤

versions: dict[str, str] = cast(dict[str, str], versions())

The address of the start of the binary in the live process address space. Has value -1 if the process is not live or if the binary is not loaded yet.

binary_base_addr property ¤

binary_base_addr: int

addr_to_mapped ¤

addr_to_mapped(rel_addr: int) -> int

Takes an address relative to the image/file base and returns the actual address in the process' address space.

self.binary_base_addr must be valid before calling this.

addr_to_relative ¤

addr_to_relative(mapped_addr: int) -> int

Takes an address from the live process' address space and returns the relative offset from the the image/file base.

self.binary_base_addr must be valid before calling this.

Assumes that this address is actually in the self.binary_path image rather than somewhere else. If you don't want to check this beforehand (because of performance), you at the very least need to check that the value returned here doesn't exceed XML-RPC int limits.

disconnect ¤

disconnect() -> None

Disconnects from the XML RPC server.

Delete this object after running this function.

decompile ¤

decompile(mapped_addr: int) -> FuncDecompilationResult | None

See IntegrationManager.decompile() for the function description.

function_data ¤

function_data(mapped_addr: int) -> FuncVariables | None

See IntegrationManager.function_data() for the function description.

function_headers ¤

function_headers() -> FunctionHeaders | None

See IntegrationManager.function_headers() for the function description.

global_vars ¤

global_vars() -> GlobalVariables | None

See IntegrationManager.global_vars() for the function description.

structs ¤

structs()

breakpoints ¤

breakpoints()

focus_address ¤

focus_address(mapped_addr: int) -> bool | None

See IntegrationManager.focus_address() for the function description.

IntegrationManager ¤

IntegrationManager()

A singleton class that manages all integration-related stuff.

We can connect to only one decompiler at a time, and acknowledge only one file that decompiler is decompiling. (Could be relaxed in the future! Especially the latter.)

All functions except connect() and disconnect() are no-op if we aren't connected.

Methods:

__func_curr_line instance-attribute ¤

__func_curr_line: dict[str, int] = {}

invalidate_caches ¤

invalidate_caches() -> None

connect ¤

connect(host: str, port: int) -> bool

Connects to the remote decompiler.

Always invalidates the previous connection. This manager saves the new connection internally only if it succeeds.

Returns True if the connection succeeded, otherwise False.

remove_symbols ¤

remove_symbols(inf: Process | None = None) -> bool

Remove the decompiler symbols that we added latest.

Returns whether we suceeded. Resets self._latest_symbol_file_path regardless of success.

FIXME: Only works for GDB :(

disconnect ¤

disconnect() -> None

update_symbols ¤

update_symbols() -> int

Update global variables and functions in the debugger.

This always invalidates the cache for global variables and function headers, and requests them from the plugin.

Returns the amount of synced symbols.

FIXME: Currently they are all 8 bytes in size.

update_function_variables ¤

update_function_variables() -> int

Update debugger convnience varibles based on the function variables in the currently selected frame.

This always fully invalidates the cache for function variables and requests them from the plugin.

Returns:

  • int

    The number of variables we successfully updated in the debugger.

FIXME: Currently this kinda doesn't work if it runs while we are in the function prologue. We should ideally run it only when we enter new functions and are past their prologues.

is_connected ¤

is_connected() -> bool

Are we connected to a decompiler?

Lightweight check, use a ping for an actual check.

decompiler_id ¤

decompiler_id() -> DecompilerID | None

Which decompiler are we connected to?

decompiler_name ¤

decompiler_name() -> str

If we are connected, will return the name of the decompiler we are connected to.

If we are not connected, will return "???".

version_string ¤

version_string() -> str | None

Get a string with version information about the decompiler environment.

get_function_vars_rebased_from_frame ¤

get_function_vars_rebased_from_frame(
    frame: Frame,
) -> RebasedFuncVariables | None

Get function variables for the passed frame. Stack variables will have valid addresses rather than offsets.

frame.pc() will be used to ask the debugger for the valid function variables. Note that it is possible that the same function returns different sets of variables at different PC's.

frame.sp() and frame.start() are used to rebase stack variables based on decompiler-returned offsets. Register variables are unmodified.

The RPC call to the decompiler when asking for variables is cached, but the rebasing is not, ergo this function call is relatively expensive.

Parameters:

  • frame (Frame) –

    The frame to use for fetching variables.

get_stack_var_dict_from_frame ¤

get_stack_var_dict_from_frame(frame: Frame) -> dict[int, str]

Ask the decompiler for stack variable offsets in this frame and resolve each variable to an actual address.

The RPC call to the decompiler when asking for variables is cached, but the rebasing is not, and dict creating is not, ergo this function call is relatively expensive.

Returns:

  • dict[int, str]

    A dictionary that maps (stack variable address) -> (stack variable name)

  • dict[int, str]

    for all variables in the given frame.

get_stack_var_dict_all ¤

get_stack_var_dict_all() -> dict[int, str]

Take all valid stack frames (from the whole backtrace), ask the decompiler to figure out where they are, and map them to their actual addresses.

You must not modify the object you got from this function (because of caching).

Returns:

  • dict[int, str]

    A dictionary that maps (stack variable address) -> (stack variable name)

  • dict[int, str]

    for all currently valid stack frames.

decompile_pretty ¤

decompile_pretty(mapped_addr: int, nlines: int) -> list[str] | None

Get the prettified decompilation of a function.

The following things are done: + syntax highlighting + '►' indicator at the current line + trimmed to only return nlines lines (surrounding the mapped_addr) (returns all lines if nlines == -1)

Returns a list of strings each representing one line of the decompilation.

symbol_at_address ¤

symbol_at_address(mapped_addr: int) -> str | None

Returns name of a symbol (function or global variable) at given address, or None if there is nothing there.

FIXME: Currently, global variables don't acknowledge their actual size. FIXME2: After update_symbols() is updated to acknowledge symbol sizes, this will be obsolete.

decompile_raw ¤

decompile_raw(mapped_addr: int) -> FuncDecompilationResult | None

Returns the decompilation of the function which contains address mapped_addr.

Generally you should use self.decompile_pretty().

function_data ¤

function_data(mapped_addr: int) -> FuncVariables | None

Returns the variables of the function which contains address mapped_addr.

The "offset" field of the stack variables is poorly defined.

The register variables are quite best effort and do not actually take the asked for address into account. In other words, the output for these may be just plain wrong.

Function arguments are included in these variables.

function_headers ¤

function_headers() -> FunctionHeaders | None

Returns the name, address and size off all functions in the binary, sorted by address.

global_vars ¤

global_vars() -> GlobalVariables | None

Returns the name and address of all global variables in the binary, sorted by address.

structs ¤

structs()

breakpoints ¤

breakpoints()

focus_address ¤

focus_address(mapped_addr: int) -> bool

Focus (jump to) this address in the decompiler.