Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,21 @@ sample_decompiler/

### Supported backends

Currently, IDA (using the ida-domain/idapro Python libraries) and radare2 are supported. Other disassemblers may be added in the future.
Three backends are supported, selected with `--backend` (default `auto`, which prefers them in this order):

1. **IDA** – uses the ida-domain/idapro Python libraries (best decompilation quality).
2. **radare2** – uses r2pipe with the r2ghidra decompiler.
3. **angr** – a pure-Python fallback with no external tooling, so ToCode still runs when neither IDA nor radare2 is available. It is an optional extra (it is large: ~450 MB of native dependencies), so it is not installed by default. Get it in any of these ways:

```bash
bash ./install.sh --all # recommended: installs every backend
pip install tocode-cli[angr] # or, with pip directly
uv tool install --force --editable '.[angr]' # or, from a local checkout
```

The angr export is structurally identical to the others (same files and metadata); its pseudo-C is lower quality than Hex-Rays or r2ghidra.

Other disassemblers may be added in the future.

### Using

Expand All @@ -71,6 +85,12 @@ On Linux or macOS:
bash ./install.sh
```

Add `--all` or `--full` (PowerShell: `-Full`) to also install the angr fallback backend, so ToCode works out-of-the-box even without IDA or radare2:

```bash
bash ./install.sh --all
```

Manual setup (requires [uv](https://docs.astral.sh/uv/)):

```bash
Expand Down
70 changes: 53 additions & 17 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ param(
[string]$InstallDir,
[string]$Repo = "https://github.com/buzzer-re/ToCode.git",
[string]$Branch = "main",
[switch]$Dev
[switch]$Dev,
[switch]$Full
)

Set-StrictMode -Version Latest
Expand Down Expand Up @@ -185,18 +186,38 @@ else {

$binDir = $null

# Build the list of optional dependency groups to install. "dev" pulls in the
# test tooling; -Full installs the angr pure-Python fallback backend.
#
# uv sync populates the project's .venv (used by `uv run` / development), while
# uv tool install builds a separate isolated environment for the `tocode`
# command. Runtime extras (angr) must be passed to BOTH or the installed
# command will not see them. dev is build/test tooling, so it only belongs in
# the project venv, not the tool environment.
$uvExtras = @()
$pipExtras = @()
$toolExtras = @()
if ($Dev) {
$uvExtras += @("--extra", "dev")
$pipExtras += "dev"
}
if ($Full) {
$uvExtras += @("--extra", "angr")
$pipExtras += "angr"
$toolExtras += "angr"
}

if (Test-Command "uv") {
Write-Step "Syncing local project environment with uv"
if ($Dev) {
uv --directory $InstallDir sync --locked --extra dev
}
else {
uv --directory $InstallDir sync --locked
}
uv --directory $InstallDir sync --locked @uvExtras
Assert-NativeSuccess "uv could not sync the project environment"

Write-Step "Installing the tocode command with uv"
uv tool install --force --editable $InstallDir
$toolTarget = $InstallDir
if ($toolExtras.Count -gt 0) {
$toolTarget = "$InstallDir[$($toolExtras -join ',')]"
}
uv tool install --force --editable $toolTarget
Assert-NativeSuccess "uv could not install the tocode command"

$toolBin = Invoke-Quiet { uv tool dir --bin }
Expand All @@ -213,21 +234,36 @@ else {
)
}

# Install into a dedicated virtual environment rather than the system
# interpreter. Some Python installs (Homebrew, distro packages) mark
# themselves "externally managed" (PEP 668), refusing even
# `pip install --user`; a venv sidesteps that and isolates dependencies.
$venvDir = Join-Path $InstallDir ".venv"
Write-Step "Creating an isolated environment at $venvDir"
& $python.Name @($python.Args) -m venv $venvDir
Assert-NativeSuccess "could not create a virtual environment at $venvDir" @(
"Install uv from https://docs.astral.sh/uv/getting-started/installation/ (recommended)."
)

$venvScripts = Join-Path $venvDir "Scripts"
$venvPython = Join-Path $venvScripts "python.exe"

Write-Step "Installing the tocode command with pip"
$package = $InstallDir
if ($Dev) {
$package = "$InstallDir[dev]"
if ($pipExtras.Count -gt 0) {
$package = "$InstallDir[$($pipExtras -join ',')]"
}
& $python.Name @($python.Args) -m pip install --user --editable $package
& $venvPython -m pip install --editable $package
Assert-NativeSuccess "pip could not install ToCode"

# The per-user scripts directory is versioned on Windows (for example
# %APPDATA%\Python\Python312\Scripts), so ask Python for the real path
# instead of assuming USER_BASE\Scripts.
$scriptsDir = Invoke-Quiet { & $python.Name @($python.Args) -c "import sysconfig; print(sysconfig.get_path('scripts', sysconfig.get_preferred_scheme('user')))" }
if ($LASTEXITCODE -eq 0 -and $scriptsDir) {
$binDir = "$scriptsDir".Trim()
# Expose only the tocode launcher on PATH; adding the whole venv Scripts
# dir would shadow the system python/pip.
$userBin = Join-Path $env:LOCALAPPDATA "ToCode\bin"
if (-not (Test-Path $userBin)) {
New-Item -ItemType Directory -Path $userBin -Force | Out-Null
}
Copy-Item -Force (Join-Path $venvScripts "tocode.exe") (Join-Path $userBin "tocode.exe")
$binDir = $userBin
}

if ($binDir) {
Expand Down
93 changes: 81 additions & 12 deletions install.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -euo pipefail
repo_url="${TOCODE_REPO_URL:-https://github.com/buzzer-re/ToCode.git}"
branch="${TOCODE_BRANCH:-main}"
with_dev=false
with_full=false
tocode_bin_dir=""

script_dir="$(cd "$(dirname "$0")" && pwd)"
Expand All @@ -27,6 +28,7 @@ Options:
--repo URL Git repository URL. Default: https://github.com/buzzer-re/ToCode.git
--branch NAME Branch to install. Default: main
--dev Also install development extras in the local checkout
--full, --all Install all decompiler backends, including the angr fallback
-h, --help Show this help
EOF
}
Expand Down Expand Up @@ -56,6 +58,53 @@ find_python() {
return 1
}

ensure_pip() {
python_bin="$1"
if "$python_bin" -m pip --version >/dev/null 2>&1; then
return 0
fi

info "Python pip was not found; attempting to bootstrap it"
"$python_bin" -m ensurepip --upgrade --user >/dev/null 2>&1 || true
if "$python_bin" -m pip --version >/dev/null 2>&1; then
return 0
fi

if command -v apt-get >/dev/null 2>&1; then
info "Installing python3-pip with apt"
sudo apt-get update
sudo apt-get install -y python3-pip
elif command -v brew >/dev/null 2>&1; then
info "Installing Python packaging tools with Homebrew"
brew install python
fi

"$python_bin" -m pip --version >/dev/null 2>&1 \
|| die "Python pip could not be installed automatically" \
"Install uv from https://docs.astral.sh/uv/getting-started/installation/ (recommended)," \
"or install python3-pip with your package manager, then run this installer again."
}

pip_install_user() {
python_bin="$1"
requirement="$2"
info "Installing the tocode command with pip for the current user"
if "$python_bin" -m pip install --user --editable "$requirement"; then
return 0
fi

info "Retrying pip install with --break-system-packages for this user install"
"$python_bin" -m pip install --user --break-system-packages --editable "$requirement" \
|| die "pip could not install ToCode" \
"Check the pip output above, or install uv and run this installer again."
}

python_user_bin_dir() {
python_bin="$1"
"$python_bin" -c 'import site; print(site.getuserbase() + "/bin")' 2>/dev/null \
|| printf '%s\n' "$HOME/.local/bin"
}

path_contains() {
case ":$PATH:" in
*":$1:"*) return 0 ;;
Expand Down Expand Up @@ -124,6 +173,10 @@ while [ "$#" -gt 0 ]; do
with_dev=true
shift
;;
--full|--all)
with_full=true
shift
;;
-h|--help)
usage
exit 0
Expand Down Expand Up @@ -154,16 +207,33 @@ else
git clone --branch "$branch" "$repo_url" "$install_dir"
fi

# Build the list of optional dependency groups to install. "dev" pulls in the
# test tooling; "full"/angr installs the pure-Python fallback backend.
#
# uv sync populates the project's .venv (used by `uv run` / development), while
# uv tool install builds a separate isolated environment for the `tocode`
# command. Runtime extras (angr) must be passed to BOTH or the installed
# command will not see them. dev is build/test tooling, so it only belongs in
# the project venv, not the tool environment.
uv_extras=""
pip_extras=""
tool_extras=""
if [ "$with_dev" = true ]; then
uv_extras="$uv_extras --extra dev"
pip_extras="dev"
fi
if [ "$with_full" = true ]; then
uv_extras="$uv_extras --extra angr"
pip_extras="${pip_extras:+$pip_extras,}angr"
tool_extras="angr"
fi

if command -v uv >/dev/null 2>&1; then
info "Syncing local project environment with uv"
if [ "$with_dev" = true ]; then
uv --directory "$install_dir" sync --locked --extra dev
else
uv --directory "$install_dir" sync --locked
fi
uv --directory "$install_dir" sync --locked $uv_extras

info "Installing the tocode command with uv"
uv tool install --force --editable "$install_dir"
uv tool install --force --editable "${install_dir}${tool_extras:+[$tool_extras]}"

tocode_bin_dir="$(uv tool dir --bin 2>/dev/null || true)"
if ! command -v tocode >/dev/null 2>&1; then
Expand All @@ -180,15 +250,14 @@ else
"Install uv from https://docs.astral.sh/uv/getting-started/installation/ (recommended)," \
"or install Python 3.10 or newer, then run this installer again."

info "Installing the tocode command with pip"
if [ "$with_dev" = true ]; then
"$python_bin" -m pip install --user --editable "${install_dir}[dev]"
ensure_pip "$python_bin"
if [ -n "$pip_extras" ]; then
pip_install_user "$python_bin" "${install_dir}[$pip_extras]"
else
"$python_bin" -m pip install --user --editable "$install_dir"
pip_install_user "$python_bin" "$install_dir"
fi

tocode_bin_dir="$("$python_bin" -c 'import sysconfig; print(sysconfig.get_path("scripts", sysconfig.get_preferred_scheme("user")))' 2>/dev/null \
|| "$python_bin" -c 'import os, site; print(os.path.join(site.USER_BASE, "bin"))')"
tocode_bin_dir="$(python_user_bin_dir "$python_bin")"
ensure_path "$tocode_bin_dir"
fi

Expand Down
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ tocode = "tocode.cli:main"
dev = [
"pytest==8.4.2",
]
angr = [
"angr>=9.2",
]

# angr is an optional backend dependency, so it may be absent from the type
# checking environment. Ignore its missing stubs/imports rather than failing.
[[tool.mypy.overrides]]
module = ["angr.*"]
ignore_missing_imports = true

[tool.setuptools]
package-dir = { "" = "src" }
Expand Down
10 changes: 10 additions & 0 deletions src/tocode/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,16 @@ def create_analyzer(
session=IdaSession(binary, idadir=idadir, ida_domain_path=ida_domain_path),
progress=progress,
)
if choice.selected == "angr":
# Imported lazily: pulling in angr is slow and noisy (it loads native
# extensions at import time), so only do it when angr is the backend.
from .backends.angr import AngrSession

return BinaryAnalyzer(
binary,
session=AngrSession(binary),
progress=progress,
)
return BinaryAnalyzer(
binary,
session=R2Session(binary, analysis_command=analysis_command),
Expand Down
Loading
Loading