Skip to content

Commit

Permalink
refactor into its own standalone debugger
Browse files Browse the repository at this point in the history
- remove the need of invoking `lldb` directly (instead create own `SBDebugger` instance)
- add F-keys as new hotkeys
- improve IPython shell experience (rc scripts, magic functions, etc)
- use `pre-commit` hook in order to enforce linter errors
  • Loading branch information
netanelc305 committed May 23, 2024
1 parent 24fc03a commit 15f1938
Show file tree
Hide file tree
Showing 16 changed files with 590 additions and 246 deletions.
20 changes: 10 additions & 10 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Lint with flake8
- name: Install dependencies
run: |
python -m pip install flake8
flake8 . --max-complexity=14 --max-line-length=127
- name: Verify sorted imports
python3 -m pip install --upgrade pip
python3 -m pip install pre-commit
- name: Run pre-commit hooks
run: |
python -m pip install isort
isort . -m HANGING_INDENT -l 120 --check-only
pre-commit run --all-files
- name: Test install
run: |
python -m pip install --upgrade pip
python -m pip install -U .
- name: Test show usage
xcrun python3 -m pip install --upgrade pip
xcrun python3 -m pip install -r requirements.txt
xcrun python3 -m pip install -e ".[test]"
- name: Run pytest
run: |
python -m hilda
sudo xcrun python3 -m pytest -k "not set_implementation"
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.9.3
hooks:
- id: isort
args: [ '-m', 'HANGING_INDENT', '-l', '120','--check-only' ]
files: \.py$

- repo: https://github.com/pycqa/flake8
rev: "7.0.0"
hooks:
- id: flake8
args: [ '--max-complexity=14', '--max-line-length=127' ]
files: \.py$
107 changes: 80 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
- [Description](#description)
- [Installation](#installation)
- [How to use](#how-to-use)
* [Starting a Hilda shell](#starting-a-hilda-shell)
+ [Bare mode](#bare-mode)
+ [Remote mode](#remote-mode)
* [Usage](#usage)
* [Symbol objects](#symbol-objects)
* [Globalized symbols](#globalized-symbols)
- [Starting a Hilda shell](#starting-a-hilda-shell)
- [Attach mode](#attach-mode)
- [Launch mode](#launch-mode)
- [Bare mode](#bare-mode)
- [Remote mode](#remote-mode)
- [The connected device is connected via network](#the-connected-device-is-connected-via-network)
- [Startup Files](#startup-files)
- [Usage](#usage)
- [Magic functions](#magic-functions)
- [Shortcuts](#shortcuts)
- [Configurables](#configurables)
- [Attributes](#attributes)
- [Example Usage](#example-usage)
- [UI Configuration](#ui-configuration)
- [Symbol objects](#symbol-objects)
- [Globalized symbols](#globalized-symbols)
- [Searching for the right symbol](#searching-for-the-right-symbol)
- [Objective-C Classes](#objective-c-classes)
- [Objective-C Objects](#objective-c-objects)
- [Using snippets](#using-snippets)
- [Contributing](#contributing)

+ [Objective-C Classes](#objective-c-classes)
* [Objective-C Objects](#objective-c-objects)
* [Using snippets](#using-snippets)
* [Contributing](#contributing)
Would you like any further adjustments?

# Description

Expand Down Expand Up @@ -66,7 +77,21 @@ Use the attach sub-command in order to start an LLDB shell attached to given pro
hilda attach [-p pid] [-n process-name]
```

After attaching, simply execute `hilda` command to enter the hilda shell.
### Launch mode

Use the attach sub-command in order to launch given process.

```shell
hilda launch /path/to/executable \
--argv arg1 --argv arg2 \
--envp NAME=Alice --envp AGE=30 \
--stdin /path/to/input.txt \
--stdout /path/to/output.txt \
--stderr /path/to/error.txt \
--wd /path/to/working/directory \
--flags 0x01 \
--stop-at-entry
```

### Bare mode

Expand Down Expand Up @@ -120,6 +145,23 @@ Run the following command:
hilda remote HOSTNAME PORT
```

## Startup Files

Each command can accept startup files to execute on start. As opposed to snippets, the startup files can accept Hilda
syntax.

#### Startup File Example

```python
cfg.objc_verbose_monitor = True
p.bp(ADDRESS)
p.cont()
```

```shell
hilda remote HOSTNAME PORT -f startupfile1 -f startupfile2
```

## Usage

Upon starting Hilda shell, you are greeted with:
Expand Down Expand Up @@ -270,17 +312,6 @@ Here is a gist of methods you can access from `p`:
evaluate_expression(f'[[{currentDevice} systemName] hasPrefix:@"2"]')
- `import_module`
- Import & reload given python module (intended mainly for external snippets)
- `set_evaluation_unwind`
- Set whether LLDB will attempt to unwind the stack whenever an expression evaluation error occurs.
Use unwind() to restore when an error is raised in this case.
- `get_evaluation_unwind`
- Get evaluation unwind state.
When this value is True, LLDB will attempt unwinding the stack on evaluation errors.
Otherwise, the stack frame will remain the same on errors to help you investigate the error.
- `set_evaluation_ignore_breakpoints`
- Set whether to ignore breakpoints while evaluating expressions
- `get_evaluation_ignore_breakpoints`
- Get evaluation "ignore-breakpoints" state.
- `unwind`
- Unwind the stack (useful when get_evaluation_unwind() == False)
Expand All @@ -293,6 +324,32 @@ Sometimes accessing the python API can be tiring, so we added some magic functio
- `%fbp <filename> <addressInHex>`
- Equivalent to: `p.file_symbol(addressInHex, filename).bp()`
## Shortcuts
- **F7**: Step Into
- **F8**: Step Over
- **F9**: Continue
- **F10**: Stop
## Configurables
The global `cfg` used to configure various settings for evaluation and monitoring.
### Attributes
- `evaluation_unwind_on_error`: Whether to unwind on error during evaluation. (Default: `False`)
- `evaluation_ignore_breakpoints`: Whether to ignore breakpoints during evaluation. (Default: `False`)
- `nsobject_exclusion`: Whether to exclude `NSObject` during evaluation, reducing IPython autocomplete results. (
Default: `False`)
- `objc_verbose_monitor`: When set to `True`, using `monitor()` will automatically print Objective-C method arguments. (
Default: `False`)
### Example Usage
```python
cfg.objc_verbose_monitor = True
```
## UI Configuration
Hilda contains minimal UI for examining the target state.
Expand Down Expand Up @@ -650,9 +707,5 @@ This will monitor all XPC related traffic in the given process.
Please run the tests as follows before submitting a PR:
```shell
xcrun python3 -m tests aggregated

# wait for lldb shell prompt

run_tests
xcrun python3 -m pytest
```
4 changes: 4 additions & 0 deletions hilda/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@

def main():
cli()


if __name__ == '__main__':
main()
89 changes: 89 additions & 0 deletions hilda/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import logging
from pathlib import Path
from typing import List, Mapping, Optional

import click
import coloredlogs

from hilda import launch_lldb
from hilda._version import version

DEFAULT_HILDA_PORT = 1234

coloredlogs.install(level=logging.DEBUG)


@click.group()
def cli():
pass


startup_files_option = click.option('-f', '--startup_files', multiple=True, default=None, help='Files to run on start')


def parse_envp(ctx: click.Context, param: click.Parameter, value: List[str]) -> List[str]:
env_list = []
for item in value:
try:
key, val = item.split('=', 1)
env_list.append(f'{key}={val}')
except ValueError:
raise click.BadParameter(f'Invalid format for --envp: {item}. Expected KEY=VALUE.')
return env_list


@cli.command('remote')
@click.argument('hostname', default='localhost')
@click.argument('port', type=click.INT, default=DEFAULT_HILDA_PORT)
@startup_files_option
def remote(hostname: str, port: int, startup_files: Optional[List[str]] = None) -> None:
""" Connect to remote debugserver at given address """
launch_lldb.remote(hostname, port, startup_files)


@cli.command('attach')
@click.option('-n', '--name', help='process name to attach')
@click.option('-p', '--pid', type=click.INT, help='pid to attach')
@startup_files_option
def attach(name: str, pid: int, startup_files: Optional[List[str]] = None) -> None:
""" Attach to given process and start a lldb shell """
launch_lldb.attach(name=name, pid=pid, startup_files=startup_files)


@cli.command('launch')
@click.argument('exec_path')
@click.option('--argv', multiple=True, help='Command line arguments to pass to the process')
@click.option('--envp', multiple=True, callback=parse_envp, help='Environment variables in the form KEY=VALUE')
@click.option('--stdin', type=Path, help='Redirect stdin from this file path')
@click.option('--stdout', type=Path, help='Redirect stdout to this file path')
@click.option('--stderr', type=Path, help='Redirect stderr to this file path')
@click.option('--cwd', type=Path, help='Set the working directory for the process')
@click.option('--flags', type=click.INT, default=0, help='Launch flags (bitmask)')
@click.option('--stop-at-entry', is_flag=True, help='Stop the process at the entry point')
@startup_files_option
def launch(exec_path: str, argv: Optional[List] = None, envp: Optional[Mapping] = None,
stdin: Optional[Path] = None,
stdout: Optional[Path] = None, stderr: Optional[Path] = None, cwd: Optional[Path] = None,
flags: Optional[int] = 0, stop_at_entry: Optional[bool] = False,
startup_files: Optional[List[str]] = None) -> None:
""" Attach to given process and start a lldb shell """
if not argv:
argv = None
if not envp:
envp = None
launch_lldb.launch(exec_path, argv, envp, stdin, stdout, stderr, cwd, flags, stop_at_entry,
startup_files)


@cli.command('bare')
def cli_bare():
""" Just start a lldb shell """
commands = [f'command script import {Path(__file__).resolve().parent / "lldb_entrypoint.py"}']
commands = '\n'.join(commands)
launch_lldb.execute(f'lldb --one-line "{commands}"')


@cli.command('version')
def cli_version():
"""Show the version information."""
click.echo(version)
10 changes: 9 additions & 1 deletion hilda/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
__all__ = ['HildaException', 'SymbolAbsentError', 'EvaluatingExpressionError', 'CreatingObjectiveCSymbolError',
'ConvertingToNsObjectError', 'ConvertingFromNSObjectError', 'DisableJetsamMemoryChecksError',
'GettingObjectiveCClassError', 'AccessingRegisterError', 'AccessingMemoryError',
'BrokenLocalSymbolsJarError', 'AddingLldbSymbolError']
'BrokenLocalSymbolsJarError', 'AddingLldbSymbolError', 'LLDBException']


class HildaException(Exception):
""" A domain exception for hilda errors. """
pass


class LLDBException(Exception):
""" A domain exception for lldb errors. """

def __init__(self, message: str):
super().__init__()
self.message = message


class SymbolAbsentError(HildaException):
""" Raise when trying to get a symbol that doesn't exist. """
pass
Expand Down
Loading

0 comments on commit 15f1938

Please sign in to comment.