1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-31 18:40:56 +00:00

build(core): emulator valgrind support

[no changelog]
This commit is contained in:
Martin Milata 2024-10-23 21:44:42 +02:00
parent c44f901a97
commit 9dee211c27
4 changed files with 67 additions and 16 deletions

View File

@ -67,27 +67,31 @@ def watch_emulator(emulator: CoreEmulator) -> int:
return 0
def run_debugger(emulator: CoreEmulator, gdb_script_file: str | Path | None) -> None:
def run_debugger(emulator: CoreEmulator, gdb_script_file: str | Path | None, valgrind: bool = False, run_command: list[str] = []) -> None:
os.chdir(emulator.workdir)
env = emulator.make_env()
if platform.system() == "Darwin":
if valgrind:
dbg_command = ["valgrind", "-v", "--tool=callgrind", "--read-inline-info=yes", str(emulator.executable)] + emulator.make_args()
elif platform.system() == "Darwin":
env["PATH"] = "/usr/bin"
os.execvpe(
"lldb",
["lldb", "-f", str(emulator.executable), "--"] + emulator.make_args(),
env,
)
dbg_command = ["lldb", "-f", str(emulator.executable), "--"] + emulator.make_args()
else:
# Optionally run a gdb script from a file
if gdb_script_file is None:
gdb = ["gdb"]
dbg_command = ["gdb"]
else:
gdb = ["gdb", "-x", str(HERE / gdb_script_file)]
os.execvpe(
"gdb",
gdb + ["--args", str(emulator.executable)] + emulator.make_args(),
env,
)
dbg_command = ["gdb", "-x", str(HERE / gdb_script_file)]
dbg_command += ["--args", str(emulator.executable)]
dbg_command += emulator.make_args()
if not run_command:
os.execvpe(dbg_command[0], dbg_command, env)
else:
dbg_process = subprocess.Popen(dbg_command, env=env)
run_process = subprocess.Popen(run_command, env=env, shell=True)
rc = run_process.wait()
dbg_process.send_signal(signal.SIGINT)
sys.exit(rc)
def _from_env(name: str) -> bool:
@ -118,6 +122,7 @@ def _from_env(name: str) -> bool:
@click.option("-r", "--record-dir", help="Directory where to record screen changes")
@click.option("-s", "--slip0014", is_flag=True, help="Initialize device with SLIP-14 seed (all all all...)")
@click.option("-S", "--script-gdb-file", type=click.Path(exists=True, dir_okay=False), help="Run gdb with an init file")
@click.option("-V", "--valgrind", is_flag=True, help="Use valgrind instead of debugger (-D)")
@click.option("-t", "--temporary-profile", is_flag=True, help="Create an empty temporary profile")
@click.option("-w", "--watch", is_flag=True, help="Restart emulator if sources change")
@click.option("-X", "--extra-arg", "extra_args", multiple=True, help="Extra argument to pass to micropython")
@ -144,6 +149,7 @@ def cli(
record_dir: Optional[str],
slip0014: bool,
script_gdb_file: str | Path | None,
valgrind: bool,
temporary_profile: bool,
watch: bool,
extra_args: list[str],
@ -261,8 +267,8 @@ def cli(
if alloc_profiling:
os.environ["TREZOR_MEMPERF"] = "1"
if debugger:
run_debugger(emulator, script_gdb_file)
if debugger or valgrind:
run_debugger(emulator, script_gdb_file, valgrind, command)
raise RuntimeError("run_debugger should not return")
emulator.start()

View File

@ -6,6 +6,7 @@
- [Embedded](core/build/embedded.md)
- [Emulator](core/build/emulator.md)
- [Emulator](core/emulator/index.md)
- [Valgrind profiling](core/emulator/valgrind.md)
- [Event Loop](core/src/event-loop.md)
- [Apps](core/src/apps.md)
- [Tests](core/tests/index.md)

View File

@ -0,0 +1,43 @@
# Profiling emulator with Valgrind
Sometimes, it can be helpful to know which parts of your code take most of the CPU time.
[Callgrind](https://valgrind.org/docs/manual/cl-manual.html) tool from the [Valgrind](https://valgrind.org/)
instrumentation framework can generate profiling data for a run of Trezor emulator. These can then be visualized
with [KCachegrind](https://kcachegrind.github.io/).
Bear in mind that profiling the emulator is of very limited usefulness due to:
* different CPU architecture,
* different/mocked drivers,
* & other differences from actual hardware.
Still, it might be a way to get *some* insight without a [hardware debugger](../systemview/index.md)
and a development board.
Valgrind also currently doesn't understand MicroPython call stack so it won't help you when your code is spending
a lot of time in pure python functions that don't call out to C. It might be possible to instrument trezor-core
so that Valgrind is aware of MicroPython stack frames.
## Build
```
make build_unix_frozen TREZOR_EMULATOR_DEBUGGABLE=1 ADDRESS_SANITIZER=0
```
With `PYOPT=0`, most of the execution time is spent formatting and writing logs, so it is recommended to use
`PYOPT=1` (and lose DebugLink) or get rid of logging manually.
## Run
If you're using Nix, you can use Valgrind and KCachegrind packages from our `shell.nix`:
```
nix-shell --args devTools true --run "poetry shell"
```
Record profiling data on some device tests:
```
./emu.py -a --debugger --valgrind -c 'sleep 10; pytest ../../tests/device_tests/ -v --other-pytest-args...'
```
Open profiling data in KCachegrind (file suffix is different for each emulator process):
```
kcachegrind src/callgrind.out.$PID
```

View File

@ -164,6 +164,7 @@ stdenvNoCC.mkDerivation ({
] ++ lib.optionals devTools [
shellcheck
openocd-stm
kcachegrind
] ++ lib.optionals (devTools && !stdenv.isDarwin) [
gdb
] ++ lib.optionals (devTools && acceptJlink) [