Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz 2024-10-17 10:53:45 -04:00
commit 64e9b2bff5
33 changed files with 992 additions and 211 deletions

View File

@ -2,6 +2,11 @@
##MODULE IP: JSch License ##MODULE IP: JSch License
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/debugger-launchers/local-gdb.bat||GHIDRA||||END| data/debugger-launchers/local-gdb.bat||GHIDRA||||END|
data/debugger-launchers/qemu-gdb.bat||GHIDRA||||END|
data/debugger-launchers/raw-gdb.bat||GHIDRA||||END|
data/debugger-launchers/remote-gdb.bat||GHIDRA||||END|
data/debugger-launchers/ssh-gdb.bat||GHIDRA||||END|
data/debugger-launchers/ssh-gdbserver.bat||GHIDRA||||END|
data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END| data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END|
data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END| data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END|
data/scripts/getpid-linux-i386.gdb||GHIDRA||||END| data/scripts/getpid-linux-i386.gdb||GHIDRA||||END|

View File

@ -10,12 +10,10 @@
::@icon icon.debugger ::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb ::@help TraceRmiLauncherServicePlugin#gdb
::@enum StartCmd:str run start starti ::@enum StartCmd:str run start starti
::@arg :file "Image" "The target binary executable image" ::@env OPT_TARGET_IMG:file="" "Image" "The target binary executable image"
::@args "Arguments" "Command-line arguments to pass to the target" ::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH." ::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
::@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target." ::@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
::@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
::@tty TTY_TARGET if env:OPT_EXTRA_TTY
@echo off @echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
@ -30,24 +28,18 @@ IF EXIST %GHIDRA_HOME%\ghidra\.git (
) )
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH% set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
set target_image=%1
shift
set target_args=%*
"%OPT_GDB_PATH%" ^ "%OPT_GDB_PATH%" ^
-q ^ -q ^
-ex "set pagination off" ^ -ex "set pagination off" ^
-ex "set confirm off" ^ -ex "set confirm off" ^
-ex "show version" ^ -ex "show version" ^
-ex "python import ghidragdb" ^ -ex "python import ghidragdb" ^
-ex "target exec %target_image%" ^ -ex "target exec %OPT_TARGET_IMG%" ^
-ex "set args %target_args%" ^ -ex "set args %OPT_TARGET_ARGS%" ^
-ex "set inferior-tty %TTY_TARGET%" ^ -ex "set inferior-tty %TTY_TARGET%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^ -ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^ -ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^ -ex "ghidra trace sync-enable" ^
-ex "%OPT_START_CMD%" ^ -ex "%OPT_START_CMD%" ^
-ex "set confirm on" ^ -ex "set confirm on" ^
-ex "set pagination on" ^ -ex "set pagination on"

View File

@ -0,0 +1,56 @@
::@title qemu + gdb
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>qemu</tt> and connect with <tt>gdb</tt></h3>
::@desc <p>
::@desc This will launch the target on the local machine using <tt>qemu</tt>.
::@desc Then in a second terminal, it will connect <tt>gdb</tt> to QEMU's GDBstub.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group cross
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb_qemu
::@env OPT_TARGET_IMG:file="" "Image" "The target binary executable image"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture."
::@env QEMU_GDB:int=1234 "QEMU Port" "Port for gdb connection to qemu"
::@env OPT_EXTRA_QEMU_ARGS:str="" "Extra qemu arguments" "Extra arguments to pass to qemu. Use with care."
::@env OPT_GDB_PATH:file="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
::@env OPT_EXTRA_TTY:bool=false "QEMU TTY" "Provide a separate terminal emulator for the target."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
IF "%OPT_EXTRA_TTY%"=="true" (
start "qemu" "%GHIDRA_LANG_EXTTOOL_qemu%" %OPT_EXTRA_QEMU_ARGS% -gdb tcp::%QEMU_GDB% -S "%OPT_TARGET_IMG%" %OPT_TARGET_ARGS%
) ELSE (
start /B "qemu" "%GHIDRA_LANG_EXTTOOL_qemu%" %OPT_EXTRA_QEMU_ARGS% -gdb tcp::%QEMU_GDB% -S "%OPT_TARGET_IMG%" %OPT_TARGET_ARGS%
)
:: Give QEMU a moment to open the socket
powershell -nop -c "& {sleep -m 100}"
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target exec '%OPT_TARGET_IMG%'" ^
-ex "set args %OPT_TARGET_ARGS%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^
-ex "target remote localhost:%QEMU_GDB%" ^
-ex "set confirm on" ^
-ex "set pagination on"

View File

@ -1,18 +1,18 @@
#!/usr/bin/bash #!/usr/bin/bash
## ### ## ###
# IP: GHIDRA # IP: GHIDRA
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
#@title qemu + gdb #@title qemu + gdb
#@desc <html><body width="300px"> #@desc <html><body width="300px">
@ -29,7 +29,7 @@
#@arg :file "Image" "The target binary executable image" #@arg :file "Image" "The target binary executable image"
#@args "Arguments" "Command-line arguments to pass to the target" #@args "Arguments" "Command-line arguments to pass to the target"
#@env GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture." #@env GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture."
#@env QEMU_GDB:int=12345 "QEMU Port" "Port for gdb connection to qemu" #@env QEMU_GDB:int=1234 "QEMU Port" "Port for gdb connection to qemu"
#@env OPT_EXTRA_QEMU_ARGS:str="" "Extra qemu arguments" "Extra arguments to pass to qemu. Use with care." #@env OPT_EXTRA_QEMU_ARGS:str="" "Extra qemu arguments" "Extra arguments to pass to qemu. Use with care."
#@env OPT_GDB_PATH:file="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH." #@env OPT_GDB_PATH:file="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
#@env OPT_EXTRA_TTY:bool=false "QEMU TTY" "Provide a separate terminal emulator for the target." #@env OPT_EXTRA_TTY:bool=false "QEMU TTY" "Provide a separate terminal emulator for the target."
@ -52,9 +52,9 @@ target_image="$1"
if [ -z "$TTY_TARGET" ] if [ -z "$TTY_TARGET" ]
then then
"$GHIDRA_LANG_EXTTOOL_qemu" $OPT_EXTRA_QEMU_ARGS $@ & "$GHIDRA_LANG_EXTTOOL_qemu" -gdb tcp::$QEMU_GDB -S $OPT_EXTRA_QEMU_ARGS $@ &
else else
"$GHIDRA_LANG_EXTTOOL_qemu" $OPT_EXTRA_QEMU_ARGS $@ <$TTY_TARGET >$TTY_TARGET 2>&1 & "$GHIDRA_LANG_EXTTOOL_qemu" -gdb tcp::$QEMU_GDB -S $OPT_EXTRA_QEMU_ARGS $@ <$TTY_TARGET >$TTY_TARGET 2>&1 &
fi fi
# Give QEMU a moment to open the socket # Give QEMU a moment to open the socket
@ -68,7 +68,6 @@ sleep 0.1
-ex "python import ghidragdb" \ -ex "python import ghidragdb" \
-ex "file \"$target_image\"" \ -ex "file \"$target_image\"" \
-ex "set args $target_args" \ -ex "set args $target_args" \
-ex "set inferior-tty $TTY_TARGET" \
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \ -ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-ex "ghidra trace start" \ -ex "ghidra trace start" \
-ex "ghidra trace sync-enable" \ -ex "ghidra trace sync-enable" \

View File

@ -0,0 +1,41 @@
::@title raw gdb
::@no-image
::@desc <html><body width="300px">
::@desc <h3>Start <tt>gdb</tt></h3>
::@desc <p>
::@desc This will start <tt>gdb</tt> and connect to it.
::@desc It will not launch a target, so you can (must) set up your target manually.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group raw
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb_raw
::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
::@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "set architecture %OPT_ARCH%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^
-ex "set confirm on" ^
-ex "set pagination on"

View File

@ -0,0 +1,53 @@
::@title remote gdb
::@no-image
::@desc <html><body width="300px">
::@desc <h3>Launch with local <tt>gdb</tt> and connect to a stub (e.g., <tt>gdbserver</tt>)</h3>
::@desc <p>
::@desc This will start <tt>gdb</tt> on the local system and then use it to connect to the remote system.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group remote
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb_remote
::@enum TargetType:str remote extended-remote
::@env OPT_TARGET_TYPE:TargetType="remote" "Target" "The type of remote target"
::@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
::@env OPT_PORT:int=9999 "Port" "The host's listening port"
::@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
IF "%OPT_ARCH%"=="" (
set archcmd=
) ELSE (
set archcmd=-ex "set arch %OPT_ARCH%"
)
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
%archcmd% ^
-ex "echo Connecting to %OPT_HOST%:%OPT_PORT%... " ^
-ex "target %OPT_TARGET_TYPE% %OPT_HOST%:%OPT_PORT%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^
-ex "ghidra trace sync-synth-stopped" ^
-ex "set confirm on" ^
-ex "set pagination on"

View File

@ -0,0 +1,39 @@
::@timeout 60000
::@title gdb via ssh
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>gdb</tt> via <tt>ssh</tt></h3>
::@desc <p>
::@desc This will launch the target on a remote machine using <tt>gdb</tt> via <tt>ssh</tt>.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group remote
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb_ssh
::@enum StartCmd:str run start starti
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image on the remote system"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
::@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
::@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection."
::@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
::@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb on the remote system. Omit the full path to resolve using the system PATH."
::@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
@echo off
set cmd=TERM='%TERM%' '%OPT_GDB_PATH%' ^
-q ^
-ex 'set pagination off' ^
-ex 'set confirm off' ^
-ex 'show version' ^
-ex 'python import ghidragdb' ^
-ex 'file \"%OPT_TARGET_IMG%\"' ^
-ex 'set args %OPT_TARGET_ARGS%' ^
-ex 'ghidra trace connect \"localhost:%OPT_REMOTE_PORT%\"' ^
-ex 'ghidra trace start' ^
-ex 'ghidra trace sync-enable' ^
-ex '%OPT_START_CMD%' ^
-ex 'set confirm on' ^
-ex 'set pagination on'
"%OPT_SSH_PATH%" "-R%OPT_REMOTE_PORT%:%GHIDRA_TRACE_RMI_ADDR%" -t %OPT_EXTRA_SSH_ARGS% "%OPT_HOST%" "%cmd%"

View File

@ -1,18 +1,18 @@
#!/usr/bin/bash #!/usr/bin/bash
## ### ## ###
# IP: GHIDRA # IP: GHIDRA
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
#@timeout 60000 #@timeout 60000
#@title gdb via ssh #@title gdb via ssh
@ -29,6 +29,7 @@
#@enum StartCmd:str run start starti #@enum StartCmd:str run start starti
#@arg :str "Image" "The target binary executable image on the remote system" #@arg :str "Image" "The target binary executable image on the remote system"
#@args "Arguments" "Command-line arguments to pass to the target" #@args "Arguments" "Command-line arguments to pass to the target"
#@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host" #@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
#@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection." #@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection."
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care." #@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
@ -39,7 +40,7 @@ target_image="$1"
shift shift
target_args="$@" target_args="$@"
ssh "-R$OPT_REMOTE_PORT:$GHIDRA_TRACE_RMI_ADDR" -t $OPT_EXTRA_SSH_ARGS "$OPT_HOST" "TERM='$TERM' '$OPT_GDB_PATH' \ "$OPT_SSH_PATH" "-R$OPT_REMOTE_PORT:$GHIDRA_TRACE_RMI_ADDR" -t $OPT_EXTRA_SSH_ARGS "$OPT_HOST" "TERM='$TERM' '$OPT_GDB_PATH' \
-q \ -q \
-ex 'set pagination off' \ -ex 'set pagination off' \
-ex 'set confirm off' \ -ex 'set confirm off' \

View File

@ -0,0 +1,47 @@
::@timeout 60000
::@title gdb + gdbserver via ssh
::@desc <html><body width="300px">
::@desc <h3>Launch with local <tt>gdb</tt> and <tt>gdbserver</tt> via <tt>ssh</tt></h3>
::@desc <p>
::@desc This will start <tt>gdb</tt> on the local system and then use it to connect and launch the target in <tt>gdbserver</tt> on the remote system via <tt>ssh</tt>.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@menu-group remote
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb_gdbserver_ssh
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image on the remote system"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
::@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
::@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
::@env OPT_GDBSERVER_PATH:str="gdbserver" "gdbserver command (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH."
::@env OPT_EXTRA_GDBSERVER_ARGS:str="" "Extra gdbserver arguments" "Extra arguments to pass to gdbserver. Use with care."
::@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target remote | '%OPT_SSH_PATH%' %OPT_EXTRA_SSH_ARGS% '%OPT_HOST%' '%OPT_GDBSERVER_PATH%' %OPT_EXTRA_GDBSERVER_ARGS% - '%OPT_TARGET_IMG%' %OPT_TARGET_ARGS%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^
-ex "ghidra trace sync-synth-stopped" ^
-ex "set confirm on" ^
-ex "set pagination on"

View File

@ -1,18 +1,18 @@
#!/usr/bin/bash #!/usr/bin/bash
## ### ## ###
# IP: GHIDRA # IP: GHIDRA
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
## ##
#@timeout 60000 #@timeout 60000
#@title gdb + gdbserver via ssh #@title gdb + gdbserver via ssh
@ -28,6 +28,7 @@
#@help TraceRmiLauncherServicePlugin#gdb_gdbserver_ssh #@help TraceRmiLauncherServicePlugin#gdb_gdbserver_ssh
#@arg :str "Image" "The target binary executable image on the remote system" #@arg :str "Image" "The target binary executable image on the remote system"
#@args "Arguments" "Command-line arguments to pass to the target" #@args "Arguments" "Command-line arguments to pass to the target"
#@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host" #@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care." #@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
#@env OPT_GDBSERVER_PATH:str="gdbserver" "gdbserver command (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH." #@env OPT_GDBSERVER_PATH:str="gdbserver" "gdbserver command (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH."
@ -53,8 +54,7 @@ fi
-ex "set confirm off" \ -ex "set confirm off" \
-ex "show version" \ -ex "show version" \
-ex "python import ghidragdb" \ -ex "python import ghidragdb" \
-ex "set inferior-tty $TTY_TARGET" \ -ex "target remote | '$OPT_SSH_PATH' $OPT_EXTRA_SSH_ARGS '$OPT_HOST' '$OPT_GDBSERVER_PATH' $OPT_EXTRA_GDBSERVER_ARGS - $@" \
-ex "target remote | ssh $OPT_EXTRA_SSH_ARGS '$OPT_HOST' '$OPT_GDBSERVER_PATH' $OPT_EXTRA_GDBSERVER_ARGS - $@" \
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \ -ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-ex "ghidra trace start" \ -ex "ghidra trace start" \
-ex "ghidra trace sync-enable" \ -ex "ghidra trace sync-enable" \

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.mapping;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.disassemble.DisassemblyInject; import ghidra.app.plugin.core.debug.disassemble.DisassemblyInject;
@ -27,7 +28,11 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Endian;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.TraceInstruction;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
@ -61,7 +66,13 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform
} }
protected boolean isCancelSilently(Address start, long snap) { protected boolean isCancelSilently(Address start, long snap) {
return trace.getCodeManager().instructions().getAt(snap, start) != null; TraceInstruction exists = trace.getCodeManager().instructions().getAt(snap, start);
if (exists == null) {
return false;
}
var states = trace.getMemoryManager().getStates(snap, exists.getRange());
return TraceMemoryOperations.isStateEntirely(exists.getRange(), states,
TraceMemoryState.KNOWN);
} }
protected Collection<DisassemblyInject> getDisassemblyInjections(TracePlatform platform) { protected Collection<DisassemblyInject> getDisassemblyInjections(TracePlatform platform) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -43,7 +43,8 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap;
import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.control.ControlMode;
import ghidra.debug.api.emulation.*; import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory;
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
@ -781,7 +782,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
TraceThread eventThread = key.time.getEventThread(key.trace); TraceThread eventThread = key.time.getEventThread(key.trace);
be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP); be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP);
RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor); RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor);
key = new CacheKey(key.platform, key.time.advanced(result.schedule())); if (result.schedule().hasSteps()) {
key = new CacheKey(key.platform, key.time.dropPSteps().advanced(result.schedule()));
}
else {
key = new CacheKey(key.platform, key.time.advanced(result.schedule()));
}
Msg.info(this, "Stopped emulation at " + key.time); Msg.info(this, "Stopped emulation at " + key.time);
TraceSnapshot destSnap = writeToScratch(key, be.ce); TraceSnapshot destSnap = writeToScratch(key, be.ce);
cacheEmulator(key, be.ce); cacheEmulator(key, be.ce);

View File

@ -38,11 +38,11 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug
import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.DecodePcodeExecutionException; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.InterruptPcodeExecutionException;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@ -489,25 +489,180 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe
trace.getBreakpointManager() trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread), .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
trace.getBreakpointManager() TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread), .addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
emu_swi();
emu_exec_decoded();
""");
trace.getBreakpointManager() trace.getBreakpointManager()
.addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread), .addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
} }
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored // This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread)); TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result1.schedule()); assertEquals(TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2),
result1.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException); assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// This will test if the one just hit gets ignored // This will test if the one just hit gets ignored
EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
result1.schedule(), monitor, Scheduler.oneThread(thread)); result1.schedule(), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule()); assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException); assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
}
@Test
public void testStepAfterExecutionBreakpoint() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x00400000);
Address addrI1;
try (Transaction tx = program.openTransaction("Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r0",
"mov r0, r1",
"mov r2, r0");
ii.next(); // addrText
addrI1 = ii.next().getMinAddress();
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
try (Transaction tx = trace.openTransaction("Add breakpoint")) {
trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
emu_swi();
emu_exec_decoded();
""");
}
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2),
result1.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// Now, step it forward to complete the instruction
emulationPlugin.emulate(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0).steppedForward(thread, 2), monitor);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
}
@Test
public void testStuckAtUserop() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x00400000);
Address addrI1;
try (Transaction tx = program.openTransaction("Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r0",
"mov r0, r1",
"mov r2, r0");
ii.next(); // addrText
addrI1 = ii.next().getMinAddress();
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
try (Transaction tx = trace.openTransaction("Add breakpoint")) {
TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
// Force "partial instruction"
tb.setEmuSleigh("""
r1 = 0xbeef;
pcodeop_one(r1);
emu_exec_decoded();
""");
}
TraceSchedule stuck = TraceSchedule.snap(0)
.steppedForward(thread, 1)
.steppedPcodeForward(thread, 2);
assertEquals(0, emulationPlugin.cache.size());
// This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(stuck, result1.schedule());
assertTrue(result1.error() instanceof PcodeExecutionException);
// Save this for comparison later
DebuggerPcodeMachine<?> emu = Unique.assertOne(emulationPlugin.cache.values()).emulator();
// We shouldn't get any further
EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
result1.schedule(), monitor, Scheduler.oneThread(thread));
assertEquals(stuck, result2.schedule());
assertTrue(result1.error() instanceof PcodeExecutionException);
// For efficiency, esp. after a long run, make sure we used the same emulator
assertSame(emu, Unique.assertOne(emulationPlugin.cache.values()).emulator());
} }
@Test @Test
@ -817,6 +972,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe
newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000)); newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000));
newTraceThread.setName("MyThread"); newTraceThread.setName("MyThread");
@SuppressWarnings("unused")
PcodeThread<byte[]> newEmuThread = emulator.newThread(newTraceThread.getPath()); PcodeThread<byte[]> newEmuThread = emulator.newThread(newTraceThread.getPath());
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,6 +16,7 @@
package ghidra.trace.database.listing; package ghidra.trace.database.listing;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -29,9 +30,13 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.context.DBTraceRegisterContextManager; import ghidra.trace.database.context.DBTraceRegisterContextManager;
import ghidra.trace.database.context.DBTraceRegisterContextSpace; import ghidra.trace.database.context.DBTraceRegisterContextSpace;
import ghidra.trace.database.guest.InternalTracePlatform; import ghidra.trace.database.guest.InternalTracePlatform;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*; import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.util.*; import ghidra.trace.util.*;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -92,7 +97,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
protected void truncateOrDelete(TraceInstruction exists) { protected void truncateOrDelete(TraceInstruction exists) {
if (exists.getStartSnap() < lifespan.lmin()) { if (exists.getStartSnap() < lifespan.lmin()) {
exists.setEndSnap(lifespan.lmin()); exists.setEndSnap(lifespan.lmin() - 1);
} }
else { else {
exists.delete(); exists.delete();
@ -471,6 +476,15 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
conflict, conflictCodeUnit, overwrite); conflict, conflictCodeUnit, overwrite);
} }
protected boolean isKnown(DBTraceMemorySpace ms, long snap, CodeUnit cu) {
if (ms == null) {
return false;
}
AddressRange range = new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
var states = ms.getStates(snap, range);
return TraceMemoryOperations.isStateEntirely(range, states, TraceMemoryState.KNOWN);
}
/** /**
* Checks the intended locations for conflicts with existing units. * Checks the intended locations for conflicts with existing units.
* *
@ -486,6 +500,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
Set<Address> skipDelaySlots) { Set<Address> skipDelaySlots) {
// NOTE: Partly derived from CodeManager#checkInstructionSet() // NOTE: Partly derived from CodeManager#checkInstructionSet()
// Attempted to factor more fluently // Attempted to factor more fluently
DBTraceMemoryManager mm = space.trace.getMemoryManager();
for (InstructionBlock block : instructionSet) { for (InstructionBlock block : instructionSet) {
// If block contains a known error, record its address, and do not proceed beyond it // If block contains a known error, record its address, and do not proceed beyond it
Address errorAddress = null; Address errorAddress = null;
@ -519,6 +534,12 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
lastProtoInstr = protoInstr; lastProtoInstr = protoInstr;
} }
CodeUnit existsCu = overlap.getRight(); CodeUnit existsCu = overlap.getRight();
DBTraceMemorySpace ms =
mm.getMemorySpace(existsCu.getAddress().getAddressSpace(), false);
if (!isKnown(ms, startSnap, existsCu) && existsCu instanceof TraceCodeUnit tcu) {
tcu.delete();
continue;
}
int cmp = existsCu.getMinAddress().compareTo(protoInstr.getMinAddress()); int cmp = existsCu.getMinAddress().compareTo(protoInstr.getMinAddress());
boolean existsIsInstruction = (existsCu instanceof TraceInstruction); boolean existsIsInstruction = (existsCu instanceof TraceInstruction);
if (cmp == 0 && existsIsInstruction) { if (cmp == 0 && existsIsInstruction) {
@ -552,7 +573,7 @@ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView
} }
// NOTE: existsIsInstruction implies cmp != 0, so record as off-cut conflict // NOTE: existsIsInstruction implies cmp != 0, so record as off-cut conflict
block.setCodeUnitConflict(existsCu.getAddress(), protoInstr.getAddress(), block.setCodeUnitConflict(existsCu.getAddress(), protoInstr.getAddress(),
flowFromAddress, existsIsInstruction, existsIsInstruction); flowFromAddress, existsIsInstruction, true);
} }
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -39,13 +39,17 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetView<T> extends Abstrac
private final Predicate<? super T> predicate; private final Predicate<? super T> predicate;
/** /**
* TODO Document me * Construct an {@link AddressSetView} based on the given map of entries and predicate.
* *
* The caller must reduce the map if only a certain range is desired. * <p>
* The spatial map is a 2-dimensional collection of entries, but only the address dimension is
* considered. This set behaves as the union of address ranges for all entries whose values pass
* the predicate. Typically, the caller reduces the map first.
* *
* @param lock * @param space the address space of the given map
* @param map * @param lock a lock to ensure access to the underlying database is synchronized
* @param predicate * @param map the map whose entries to test
* @param predicate the predicate for testing entry values
*/ */
public DBTraceAddressSnapRangePropertyMapAddressSetView(AddressSpace space, ReadWriteLock lock, public DBTraceAddressSnapRangePropertyMapAddressSetView(AddressSpace space, ReadWriteLock lock,
SpatialMap<TraceAddressSnapRange, T, TraceAddressSnapRangeQuery> map, SpatialMap<TraceAddressSnapRange, T, TraceAddressSnapRangeQuery> map,

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -515,6 +515,12 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new); Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
} }
public static TraceAddressSnapRangeQuery mostRecent(AddressRange range, Lifespan span) {
return intersecting(
new ImmutableTraceAddressSnapRange(range, span),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) { public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) {
return equalTo(shape, null, TraceAddressSnapRangeQuery::new); return equalTo(shape, null, TraceAddressSnapRangeQuery::new);
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -279,6 +279,13 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
m -> m.getMostRecentStateEntry(snap, address)); m -> m.getMostRecentStateEntry(snap, address));
} }
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate) {
return delegateRead(range.getAddressSpace(),
m -> m.getViewMostRecentStateEntry(snap, range, predicate));
}
@Override @Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap, public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) { Address address) {

View File

@ -406,11 +406,17 @@ public class DBTraceMemorySpace
@Override @Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap, public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) { Address address) {
return getViewMostRecentStateEntry(snap, new AddressRangeImpl(address, address), s -> true);
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate) {
assertInSpace(range);
for (Lifespan span : viewport.getOrderedSpans(snap)) { for (Lifespan span : viewport.getOrderedSpans(snap)) {
Entry<TraceAddressSnapRange, TraceMemoryState> entry = var entry = stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(range, span))
stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(address, span)) .firstEntry();
.firstEntry(); if (entry != null && predicate.test(entry.getValue())) {
if (entry != null) {
return entry; return entry;
} }
} }

View File

@ -22,6 +22,7 @@ import org.apache.commons.collections4.IteratorUtils;
import generic.NestedIterator; import generic.NestedIterator;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.database.code.InstructionDB;
import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
@ -41,6 +42,7 @@ import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.listing.*; import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewListing; import ghidra.trace.model.program.TraceProgramViewListing;
import ghidra.trace.model.property.TracePropertyMapOperations; import ghidra.trace.model.property.TracePropertyMapOperations;
@ -65,8 +67,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
public int getBytes(ByteBuffer buffer, int addressOffset) { public int getBytes(ByteBuffer buffer, int addressOffset) {
DBTraceMemorySpace mem = trace.getMemoryManager().get(this, false); DBTraceMemorySpace mem = trace.getMemoryManager().get(this, false);
if (mem == null) { if (mem == null) {
// TODO: 0-fill instead? Will need to check memory space bounds. buffer.put((byte) 0);
return 0; return 1;
} }
return mem.getViewBytes(program.snap, address.add(addressOffset), buffer); return mem.getViewBytes(program.snap, address.add(addressOffset), buffer);
} }
@ -717,9 +719,21 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
public Instruction createInstruction(Address addr, InstructionPrototype prototype, public Instruction createInstruction(Address addr, InstructionPrototype prototype,
MemBuffer memBuf, ProcessorContextView context, int forcedLengthOverride) MemBuffer memBuf, ProcessorContextView context, int forcedLengthOverride)
throws CodeUnitInsertionException { throws CodeUnitInsertionException {
// TODO: Why memBuf? Can it vary from program memory? int checkLengthOverride =
InstructionDB.checkLengthOverride(forcedLengthOverride, prototype);
int length = checkLengthOverride != 0 ? checkLengthOverride : prototype.getLength();
AddressRange range;
try {
range = new AddressRangeImpl(addr, length);
}
catch (AddressOverflowException e) {
throw new CodeUnitInsertionException("Code unit would extend beyond address space");
}
var mostRecent = program.memory.memoryManager.getViewMostRecentStateEntry(program.snap,
range, s -> s == TraceMemoryState.KNOWN);
long snap = mostRecent == null ? program.snap : mostRecent.getKey().getY2();
return codeOperations.instructions() return codeOperations.instructions()
.create(Lifespan.nowOn(program.snap), addr, platform, prototype, context, .create(Lifespan.nowOn(snap), addr, platform, prototype, context,
forcedLengthOverride); forcedLengthOverride);
} }

View File

@ -1635,6 +1635,17 @@ public class DBTraceProgramView implements TraceProgramView {
} }
protected boolean isCodeVisible(TraceCodeUnit cu, Lifespan lifespan) { protected boolean isCodeVisible(TraceCodeUnit cu, Lifespan lifespan) {
try {
byte[] cubytes = cu.getBytes();
byte[] mmbytes = new byte[cubytes.length];
memory.getBytes(cu.getAddress(), mmbytes);
if (!Arrays.equals(cubytes, mmbytes)) {
return false;
}
}
catch (MemoryAccessException e) {
throw new AssertionError(e);
}
return viewport.isCompletelyVisible(cu.getRange(), lifespan, cu, return viewport.isCompletelyVisible(cu.getRange(), lifespan, cu,
getCodeOcclusion(cu.getTraceSpace())); getCodeOcclusion(cu.getTraceSpace()));
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -48,7 +48,13 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
/** /**
* Create an instruction for the host platform * Create an instruction for the host platform
* *
* @see #create(Lifespan, Address, TracePlatform, InstructionPrototype, ProcessorContextView) * @param lifespan the lifespan for the instruction unit
* @param address the starting address of the instruction
* @param prototype the instruction prototype
* @param context the input disassembly context for the instruction
* @param forcedLengthOverride reduced instruction byte-length (1..7) or 0 to use default length
* @return the new instruction
* @throws CodeUnitInsertionException if the instruction cannot be created
*/ */
default TraceInstruction create(Lifespan lifespan, Address address, default TraceInstruction create(Lifespan lifespan, Address address,
InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride) InstructionPrototype prototype, ProcessorContextView context, int forcedLengthOverride)
@ -76,7 +82,14 @@ public interface TraceInstructionsView extends TraceBaseDefinedUnitsView<TraceIn
/** /**
* Create several instructions for the host platform * Create several instructions for the host platform
* *
* @see #addInstructionSet(Lifespan, TracePlatform, InstructionSet, boolean) * <p>
* <b>NOTE:</b> This does not throw {@link CodeUnitInsertionException}. Conflicts are instead
* recorded in the {@code instructionSet}.
*
* @param lifespan the lifespan for all instruction units
* @param instructionSet the set of instructions to add
* @param overwrite true to replace conflicting instructions
* @return the (host) address set of instructions actually added
*/ */
default AddressSetView addInstructionSet(Lifespan lifespan, InstructionSet instructionSet, default AddressSetView addInstructionSet(Lifespan lifespan, InstructionSet instructionSet,
boolean overwrite) { boolean overwrite) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -62,6 +62,38 @@ import ghidra.util.task.TaskMonitor;
* accidentally rely on implied temporal relationships in scratch space. * accidentally rely on implied temporal relationships in scratch space.
*/ */
public interface TraceMemoryOperations { public interface TraceMemoryOperations {
/**
* Check if the return value of {@link #getStates(long, AddressRange)} or similar represents a
* single entry of the given state.
*
* <p>
* This method returns false if there is not exactly one entry of the given state whose range
* covers the given range. As a special case, an empty collection will cause this method to
* return true iff state is {@link TraceMemoryState#UNKNOWN}.
*
* @param range the range to check, usually that passed to
* {@link #getStates(long, AddressRange)}.
* @param stateEntries the collection returned by {@link #getStates(long, AddressRange)}.
* @param state the expected state
* @return true if the state matches
*/
static boolean isStateEntirely(AddressRange range,
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> stateEntries,
TraceMemoryState state) {
if (stateEntries.isEmpty()) {
return state == TraceMemoryState.UNKNOWN;
}
if (stateEntries.size() != 1) {
return false;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent = stateEntries.iterator().next();
if (ent.getValue() != state) {
return false;
}
AddressRange entRange = ent.getKey().getRange();
return entRange.contains(range.getMinAddress()) && entRange.contains(range.getMaxAddress());
}
/** /**
* Get the trace to which the memory manager belongs * Get the trace to which the memory manager belongs
* *
@ -261,13 +293,25 @@ public interface TraceMemoryOperations {
* Get the entry recording the most recent state at the given snap and address, following * Get the entry recording the most recent state at the given snap and address, following
* schedule forks * schedule forks
* *
* @param snap the time * @param snap the latest time to consider
* @param address the location * @param address the address
* @return the state * @return the most-recent entry
*/ */
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap, Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address); Address address);
/**
* Get the entry recording the most recent state since the given snap within the given range
* that satisfies a given predicate, following schedule forks
*
* @param snap the latest time to consider
* @param range the range of addresses
* @param predicate a predicate on the state
* @return the most-recent entry
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate);
/** /**
* Get at least the subset of addresses having state satisfying the given predicate * Get at least the subset of addresses having state satisfying the given predicate
* *

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -82,6 +82,10 @@ public class PatchStep implements Step {
/** /**
* Generate a single line of Sleigh * Generate a single line of Sleigh
* *
* @param language the target language
* @param address the (start) address of the variable
* @param data the bytes to write to the variable
* @return the Sleigh code
* @see #generateSleighLine(Language, Address, byte[], int) * @see #generateSleighLine(Language, Address, byte[], int)
*/ */
public static String generateSleighLine(Language language, Address address, byte[] data) { public static String generateSleighLine(Language language, Address address, byte[] data) {
@ -169,10 +173,18 @@ public class PatchStep implements Step {
if (register == null) { if (register == null) {
throw new IllegalArgumentException("Could not find a register for " + min); throw new IllegalArgumentException("Could not find a register for " + min);
} }
if (register.getBaseRegister().isProgramCounter()) {
register = register.getBaseRegister();
}
int length = register.getNumBytes(); int length = register.getNumBytes();
array.getData(min.getOffset(), data, 0, length); array.getData(register.getOffset(), data, 0, length);
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false); BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
result.add(String.format("%s=0x%s", register, value.toString(16))); if (register.isProgramCounter()) {
result.add(String.format("goto 0x%s", value.toString(16)));
}
else {
result.add(String.format("%s=0x%s", register, value.toString(16)));
}
remains.remove(spanOfRegister(register)); remains.remove(spanOfRegister(register));
} }
} }
@ -232,6 +244,11 @@ public class PatchStep implements Step {
return StepType.PATCH; return StepType.PATCH;
} }
@Override
public long getSkipCount() {
return 0;
}
@Override @Override
public boolean isNop() { public boolean isNop() {
// TODO: If parsing beforehand, base on number of ops // TODO: If parsing beforehand, base on number of ops
@ -307,7 +324,7 @@ public class PatchStep implements Step {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
emuThread.stepPatch(sleigh); emuThread.stepPatch(sleigh);
} }
@ -366,20 +383,26 @@ public class PatchStep implements Step {
// SemisparseArray is a bit overkill, no? // SemisparseArray is a bit overkill, no?
Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>(); Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>();
for (PcodeOp op : prog.getCode()) { for (PcodeOp op : prog.getCode()) {
// Only accept patches in form [mem/reg] = [constant] // Only accept patches in form [mem/reg] = [constant], or goto [constant]
switch (op.getOpcode()) { switch (op.getOpcode()) {
case PcodeOp.COPY: case PcodeOp.COPY -> {
if (!getPatchCopyOp(language, result, op)) { if (!getPatchCopyOp(language, result, op)) {
return null; return null;
} }
break; }
case PcodeOp.STORE: case PcodeOp.STORE -> {
if (!getPatchStoreOp(language, result, op)) { if (!getPatchStoreOp(language, result, op)) {
return null; return null;
} }
break; }
default: case PcodeOp.BRANCH -> {
if (!getPatchBranchOp(language, result, op)) {
return null;
}
}
default -> {
return null; return null;
}
} }
} }
return result; return result;
@ -405,8 +428,7 @@ public class PatchStep implements Step {
} }
protected boolean getPatchStoreOp(Language language, protected boolean getPatchStoreOp(Language language,
Map<AddressSpace, SemisparseByteArray> result, Map<AddressSpace, SemisparseByteArray> result, PcodeOp op) {
PcodeOp op) {
Varnode vnSpace = op.getInput(0); Varnode vnSpace = op.getInput(0);
if (!vnSpace.isConstant()) { if (!vnSpace.isConstant()) {
return false; return false;
@ -427,4 +449,17 @@ public class PatchStep implements Step {
return true; return true;
} }
protected boolean getPatchBranchOp(Language language,
Map<AddressSpace, SemisparseByteArray> result, PcodeOp op) {
Address target = op.getInput(0).getAddress();
if (target.getAddressSpace() != language.getDefaultSpace()) {
return false;
}
Register pc = language.getProgramCounter();
SemisparseByteArray array =
result.computeIfAbsent(pc.getAddressSpace(), as -> new SemisparseByteArray());
array.putData(pc.getOffset(),
Utils.longToBytes(target.getOffset(), pc.getNumBytes(), language.isBigEndian()));
return true;
}
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -81,14 +81,17 @@ public interface Scheduler {
/** /**
* The result of running a machine * The result of running a machine
*
* @param schedule the actual schedule executed
* @param error if applicable, the error that interrupted execution
*/ */
record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult { record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult {}
}
/** /**
* Get the next step to schedule * Get the next step to schedule
* *
* @return the (instruction-level) thread and tick count * @param trace the trace being emulated
* @return the thread and (instruction-level) tick count
*/ */
TickStep nextSlice(Trace trace); TickStep nextSlice(Trace trace);
@ -118,12 +121,17 @@ public interface Scheduler {
TickStep slice = nextSlice(trace); TickStep slice = nextSlice(trace);
eventThread = slice.getThread(tm, eventThread); eventThread = slice.getThread(tm, eventThread);
emuThread = machine.getThread(eventThread.getPath(), true); emuThread = machine.getThread(eventThread.getPath(), true);
if (emuThread.getFrame() != null) { long ticksLeft = slice.tickCount;
if (ticksLeft > 0 && emuThread.getFrame() != null) {
monitor.checkCancelled();
emuThread.finishInstruction(); emuThread.finishInstruction();
ticksLeft--;
completedTicks++;
} }
for (int i = 0; i < slice.tickCount; i++) { while (ticksLeft > 0) {
monitor.checkCancelled(); monitor.checkCancelled();
emuThread.stepInstruction(); emuThread.stepInstruction();
ticksLeft--;
completedTicks++; completedTicks++;
} }
completedSteps = completedSteps.steppedForward(eventThread, completedTicks); completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
@ -134,11 +142,11 @@ public interface Scheduler {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks); completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
PcodeFrame frame = emuThread.getFrame(); PcodeFrame frame = emuThread.getFrame();
if (frame == null) { if (frame == null) {
return new RecordRunResult(completedSteps, e); return new RecordRunResult(completedSteps.assumeRecorded(), e);
} }
// Rewind one so stepping retries the op causing the error // Rewind one so stepping retries the op causing the error
frame.stepBack(); frame.stepBack();
int count = frame.count(); int count = frame.resetCount();
if (count == 0) { if (count == 0) {
// If we've decoded, but could not execute the first op, just drop the p-code steps // If we've decoded, but could not execute the first op, just drop the p-code steps
emuThread.dropInstruction(); emuThread.dropInstruction();
@ -146,11 +154,11 @@ public interface Scheduler {
} }
// The +1 accounts for the decode step // The +1 accounts for the decode step
return new RecordRunResult( return new RecordRunResult(
completedSteps.steppedPcodeForward(eventThread, count + 1), e); completedSteps.steppedPcodeForward(eventThread, count + 1).assumeRecorded(), e);
} }
catch (CancelledException e) { catch (CancelledException e) {
return new RecordRunResult( return new RecordRunResult(
completedSteps.steppedForward(eventThread, completedTicks), e); completedSteps.steppedForward(eventThread, completedTicks).assumeRecorded(), e);
} }
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,6 +21,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -240,7 +241,7 @@ public class Sequence implements Comparable<Sequence> {
} }
/** /**
* Richly compare to sequences * Richly compare two sequences
* *
* <p> * <p>
* The result indicates not only which is "less" or "greater" than the other, but also indicates * The result indicates not only which is "less" or "greater" than the other, but also indicates
@ -355,6 +356,14 @@ public class Sequence implements Comparable<Sequence> {
return count; return count;
} }
public long totalSkipCount() {
long count = 0;
for (Step step : steps) {
count += step.getSkipCount();
}
return count;
}
/** /**
* Compute to total number of patches specified * Compute to total number of patches specified
* *
@ -440,4 +449,25 @@ public class Sequence implements Comparable<Sequence> {
} }
return thread; return thread;
} }
/**
* Check if the first instruction step is actually to finish an incomplete instruction.
*
* @param thread the thread whose instruction to potentially finish
* @param machine a machine bound to the trace whose current state reflects the given position
* @return if a finish was performed, this sequence with one initial step removed, i.e., a
* sequence representing the steps remaining
*/
Sequence checkFinish(TraceThread thread, PcodeMachine<?> machine) {
PcodeThread<?> emuThread = machine.getThread(thread.getPath(), true);
if (emuThread.getFrame() == null) {
return this;
}
Sequence result = new Sequence(new ArrayList<>(steps));
emuThread.finishInstruction();
if (result.steps.get(0).rewind(1) == 0) {
result.steps.remove(0);
}
return result;
}
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -48,6 +48,11 @@ public class SkipStep extends AbstractStep {
return StepType.SKIP; return StepType.SKIP;
} }
@Override
public long getSkipCount() {
return tickCount;
}
@Override @Override
protected String toStringStepPart() { protected String toStringStepPart() {
return String.format("s%d", tickCount); return String.format("s%d", tickCount);
@ -66,7 +71,7 @@ public class SkipStep extends AbstractStep {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
for (int i = 0; i < tickCount; i++) { for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1); monitor.incrementProgress(1);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -116,6 +116,8 @@ public interface Step extends Comparable<Step> {
long getTickCount(); long getTickCount();
long getSkipCount();
long getPatchCount(); long getPatchCount();
/** /**
@ -147,7 +149,7 @@ public interface Step extends Comparable<Step> {
* method sets the count to 0 and returns the (positive) difference, indicating this step should * method sets the count to 0 and returns the (positive) difference, indicating this step should
* be removed from the sequence, and the remaining steps rewound from the preceding step. * be removed from the sequence, and the remaining steps rewound from the preceding step.
* *
* @param steps the count to rewind * @param count the count to rewind
* @return the number of steps remaining * @return the number of steps remaining
*/ */
long rewind(long count); long rewind(long count);
@ -155,7 +157,7 @@ public interface Step extends Comparable<Step> {
/** /**
* Richly compare this step to another * Richly compare this step to another
* *
* @param step the object of comparison (this being the subject) * @param that the object of comparison (this being the subject)
* @return a result describing the relationship from subject to object * @return a result describing the relationship from subject to object
*/ */
CompareResult compareStep(Step that); CompareResult compareStep(Step that);
@ -183,7 +185,7 @@ public interface Step extends Comparable<Step> {
return thread; return thread;
} }
<T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException; throws CancelledException;
long coalescePatches(Language language, List<Step> steps); long coalescePatches(Language language, List<Step> steps);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -48,6 +48,11 @@ public class TickStep extends AbstractStep {
return StepType.TICK; return StepType.TICK;
} }
@Override
public long getSkipCount() {
return 0;
}
@Override @Override
protected String toStringStepPart() { protected String toStringStepPart() {
return Long.toString(tickCount); return Long.toString(tickCount);
@ -66,7 +71,7 @@ public class TickStep extends AbstractStep {
} }
@Override @Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor) public void execute(PcodeThread<?> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
for (int i = 0; i < tickCount; i++) { for (int i = 0; i < tickCount; i++) {
monitor.incrementProgress(1); monitor.incrementProgress(1);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -39,7 +39,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* @return the schedule * @return the schedule
*/ */
public static final TraceSchedule snap(long snap) { public static final TraceSchedule snap(long snap) {
return new TraceSchedule(snap, new Sequence(), new Sequence()); return new TraceSchedule(snap, new Sequence(), new Sequence(), Source.RECORD);
} }
private static final String PARSE_ERR_MSG = private static final String PARSE_ERR_MSG =
@ -55,9 +55,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* threads forward, and/or patching machine state. * threads forward, and/or patching machine state.
* *
* @param spec the string specification * @param spec the string specification
* @param source the presumed source of the schedule
* @return the parsed schedule * @return the parsed schedule
*/ */
public static TraceSchedule parse(String spec) { public static TraceSchedule parse(String spec, Source source) {
String[] parts = spec.split(":", 2); String[] parts = spec.split(":", 2);
if (parts.length > 2) { if (parts.length > 2) {
throw new AssertionError(); throw new AssertionError();
@ -98,24 +99,76 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
ticks = new Sequence(); ticks = new Sequence();
pTicks = new Sequence(); pTicks = new Sequence();
} }
return new TraceSchedule(snap, ticks, pTicks); return new TraceSchedule(snap, ticks, pTicks, source);
}
/**
* As in {@link #parse(String, Source)}, but assumed abnormal
*
* @param spec the string specification
* @return the parsed schedule
*/
public static TraceSchedule parse(String spec) {
return parse(spec, Source.INPUT);
}
public enum Source {
/**
* The schedule comes from the user or some source other than a recorded emulation schedule.
*/
INPUT {
@Override
Source adjust(long pTickCount, long pPatchCount, long pSkipCount) {
// The first tick is decode, so <= 1 tick is definitely not a full instruction
return pTickCount <= 1 && pPatchCount == 0 && pSkipCount == 0 ? RECORD : INPUT;
}
},
/**
* The schedule comes from recording actual emulation.
*
* <p>
* Specifically, the p-code steps must be known not to exceed one instruction.
*/
RECORD {
@Override
Source adjust(long pTickCount, long pPatchCount, long pSkipCount) {
return pPatchCount == 0 && pSkipCount == 0 ? RECORD : INPUT;
}
};
abstract Source adjust(long tickCount, long patchCount, long skipCount);
} }
private final long snap; private final long snap;
private final Sequence steps; private final Sequence steps;
private final Sequence pSteps; private final Sequence pSteps;
private final Source source;
/** /**
* Construct the given schedule * Construct the given schedule
* *
* @param snap the initial trace snapshot * @param snap the initial trace snapshot
* @param steps the step sequence * @param steps the step sequence
* @param pSteps the of p-code step sequence * @param pSteps the p-code step sequence
* @param source if the p-code steps are known not to exceed one instruction
*/ */
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) { public TraceSchedule(long snap, Sequence steps, Sequence pSteps, Source source) {
this.snap = snap; this.snap = snap;
this.steps = steps; this.steps = steps;
this.pSteps = pSteps; this.pSteps = pSteps;
this.source = source.adjust(pSteps.totalTickCount(), pSteps.totalPatchCount(),
pSteps.totalSkipCount());
}
/**
* Construct the given schedule, but assumed abnormal
*
* @param snap the initial trace snapshot
* @param steps the step sequence
* @param pSteps the p-code step sequence
*/
public TraceSchedule(long snap, Sequence steps, Sequence pSteps) {
this(snap, steps, pSteps, Source.INPUT);
} }
@Override @Override
@ -152,33 +205,17 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
result = this.steps.compareSeq(that.steps); result = this.steps.compareSeq(that.steps);
switch (result) {
case UNREL_LT:
case UNREL_GT:
return result;
case REL_LT:
if (this.pSteps.isNop()) {
return CompareResult.REL_LT;
}
else {
return CompareResult.UNREL_LT;
}
case REL_GT:
if (that.pSteps.isNop()) {
return CompareResult.REL_GT;
}
else {
return CompareResult.UNREL_GT;
}
default: // EQUALS, compare pSteps
}
result = this.pSteps.compareSeq(that.pSteps); return switch (result) {
if (result != CompareResult.EQUALS) { case UNREL_LT, UNREL_GT -> result;
return result; case REL_LT -> (this.pSteps.isNop() || this.source == Source.RECORD)
} ? CompareResult.REL_LT
: CompareResult.UNREL_LT;
return CompareResult.EQUALS; case REL_GT -> (that.pSteps.isNop() || that.source == Source.RECORD)
? CompareResult.REL_GT
: CompareResult.UNREL_GT;
default -> this.pSteps.compareSeq(that.pSteps);
};
} }
@Override @Override
@ -222,10 +259,19 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return steps.isNop() && pSteps.isNop(); return steps.isNop() && pSteps.isNop();
} }
/**
* Check if this schedule has instruction steps
*
* @return true if this indicates at least one instruction step
*/
public boolean hasSteps() {
return !steps.isNop();
}
/** /**
* Get the source snapshot * Get the source snapshot
* *
* @return * @return the snapshot key
*/ */
public long getSnap() { public long getSnap() {
return snap; return snap;
@ -234,7 +280,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
/** /**
* Get the last thread key stepped by this schedule * Get the last thread key stepped by this schedule
* *
* @return * @return the thread key
*/ */
public long getLastThreadKey() { public long getLastThreadKey() {
long last = pSteps.getLastThreadKey(); long last = pSteps.getLastThreadKey();
@ -396,6 +442,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor); pRemains.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
} }
else { else {
remains = remains.checkFinish(lastThread, machine);
lastThread = lastThread =
remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor); remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor); lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
@ -417,7 +464,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule steppedForward(TraceThread thread, long tickCount) { public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount)); steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
/** /**
@ -430,7 +477,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule skippedForward(TraceThread thread, long tickCount) { public TraceSchedule skippedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.advance(new SkipStep(thread == null ? -1 : thread.getKey(), tickCount)); steps.advance(new SkipStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) { protected TraceSchedule doSteppedBackward(Trace trace, long tickCount, Set<Long> visited) {
@ -454,7 +501,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.rewind(tickCount); steps.rewind(tickCount);
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence(), Source.RECORD);
} }
/** /**
@ -486,7 +533,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) { public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount)); pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
/** /**
@ -499,7 +546,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) { public TraceSchedule skippedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new SkipStep(thread == null ? -1 : thread.getKey(), pTickCount)); pTicks.advance(new SkipStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
/** /**
@ -519,7 +566,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
} }
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.rewind(pStepCount); pTicks.rewind(pStepCount);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
private long keyOf(TraceThread thread) { private long keyOf(TraceThread thread) {
@ -530,6 +577,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* Returns the equivalent of executing this schedule then performing a given patch * Returns the equivalent of executing this schedule then performing a given patch
* *
* @param thread the thread context for the patch; cannot be null * @param thread the thread context for the patch; cannot be null
* @param language the sleigh language for the patch
* @param sleigh a single line of sleigh, excluding the terminating semicolon. * @param sleigh a single line of sleigh, excluding the terminating semicolon.
* @return the resulting schedule * @return the resulting schedule
*/ */
@ -538,19 +586,20 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new PatchStep(thread.getKey(), sleigh)); pTicks.advance(new PatchStep(thread.getKey(), sleigh));
pTicks.coalescePatches(language); pTicks.coalescePatches(language);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
ticks.advance(new PatchStep(keyOf(thread), sleigh)); ticks.advance(new PatchStep(keyOf(thread), sleigh));
ticks.coalescePatches(language); ticks.coalescePatches(language);
return new TraceSchedule(snap, ticks, new Sequence()); return new TraceSchedule(snap, ticks, new Sequence(), Source.RECORD);
} }
/** /**
* Returns the equivalent of executing this schedule then performing the given patches * Returns the equivalent of executing this schedule then performing the given patches
* *
* @param thread the thread context for the patch; cannot be null * @param thread the thread context for the patch; cannot be null
* @param sleigh the lines of sleigh, excluding the terminating semicolons. * @param language the sleigh language for the patch
* @param sleigh the lines of sleigh, excluding the terminating semicolons
* @return the resulting schedule * @return the resulting schedule
*/ */
public TraceSchedule patched(TraceThread thread, Language language, List<String> sleigh) { public TraceSchedule patched(TraceThread thread, Language language, List<String> sleigh) {
@ -560,14 +609,14 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
pTicks.advance(new PatchStep(thread.getKey(), line)); pTicks.advance(new PatchStep(thread.getKey(), line));
} }
pTicks.coalescePatches(language); pTicks.coalescePatches(language);
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks, Source.INPUT);
} }
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
for (String line : sleigh) { for (String line : sleigh) {
ticks.advance(new PatchStep(thread.getKey(), line)); ticks.advance(new PatchStep(thread.getKey(), line));
} }
ticks.coalescePatches(language); ticks.coalescePatches(language);
return new TraceSchedule(snap, ticks, new Sequence()); return new TraceSchedule(snap, ticks, new Sequence(), Source.RECORD);
} }
/** /**
@ -575,7 +624,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* *
* <p> * <p>
* This operation cannot be used to append instruction steps after p-code steps. Thus, if this * This operation cannot be used to append instruction steps after p-code steps. Thus, if this
* schedule contains any p-code steps and {@code} next has instruction steps, an error will be * schedule contains any p-code steps and {@code next} has instruction steps, an error will be
* *
* @param next the schedule to append. Its snap is ignored. * @param next the schedule to append. Its snap is ignored.
* @return the complete schedule * @return the complete schedule
@ -586,16 +635,25 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
if (this.pSteps.isNop()) { if (this.pSteps.isNop()) {
Sequence ticks = this.steps.clone(); Sequence ticks = this.steps.clone();
ticks.advance(next.steps); ticks.advance(next.steps);
return new TraceSchedule(this.snap, ticks, next.pSteps.clone()); return new TraceSchedule(this.snap, ticks, next.pSteps.clone(), next.source);
} }
else if (next.steps.isNop()) { else if (next.steps.isNop()) {
Sequence pTicks = this.steps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(next.pSteps); pTicks.advance(next.pSteps);
return new TraceSchedule(this.snap, this.steps.clone(), pTicks); return new TraceSchedule(this.snap, this.steps.clone(), pTicks, Source.INPUT);
} }
throw new IllegalArgumentException("Cannot have instructions steps following p-code steps"); throw new IllegalArgumentException("Cannot have instructions steps following p-code steps");
} }
/**
* Drop the p-code steps
*
* @return the schedule without ops
*/
public TraceSchedule dropPSteps() {
return new TraceSchedule(this.snap, this.steps, new Sequence());
}
/** /**
* Get the threads involved in the schedule * Get the threads involved in the schedule
* *
@ -611,4 +669,8 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
result.remove(null); result.remove(null);
return result; return result;
} }
public TraceSchedule assumeRecorded() {
return new TraceSchedule(snap, steps, pSteps, Source.RECORD);
}
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,13 +19,16 @@ import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import org.junit.*; import org.junit.*;
import db.Transaction; import db.Transaction;
import ghidra.app.plugin.assembler.*;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.AddressSet; import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
@ -35,6 +38,13 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.listing.DBTraceCodeManager; import ghidra.trace.database.listing.DBTraceCodeManager;
import ghidra.trace.database.memory.DBTraceMemoryManager; import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -48,9 +58,11 @@ public class DBTraceProgramViewListingTest extends AbstractGhidraHeadlessIntegra
DBTraceCodeManager code; DBTraceCodeManager code;
protected static void assertUndefined(CodeUnit cu) { protected static void assertUndefined(CodeUnit cu) {
Data data = (Data) cu; if (cu instanceof Data data && DataType.DEFAULT.equals(data.getDataType()) &&
assertEquals(DataType.DEFAULT, data.getDataType()); !data.isDefined()) {
assertFalse(data.isDefined()); return;
}
fail("Expected undefined unit, but was '%s'".formatted(cu));
} }
protected <T> List<T> takeN(int n, Iterator<T> it) { protected <T> List<T> takeN(int n, Iterator<T> it) {
@ -896,4 +908,81 @@ public class DBTraceProgramViewListingTest extends AbstractGhidraHeadlessIntegra
assertArrayEquals(b.arr(1), cu0.getBytes()); assertArrayEquals(b.arr(1), cu0.getBytes());
assertArrayEquals(b.arr(8), cu1.getBytes()); assertArrayEquals(b.arr(8), cu1.getBytes());
} }
@Test
public void testGetCodeUnitsInScratchView() throws Throwable {
TraceTimeManager tm = b.trace.getTimeManager();
Address entry = b.addr(0x00400000);
AddressSetView set = b.set(b.range(0x00400000, 0x00400003));
Assembler asm = Assemblers.getAssembler(b.language);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("imm r1, #234");
buf.assemble("add r1, r1");
final long snap;
try (Transaction tx = b.startTransaction()) {
TraceThread thread = b.getOrAddThread("Threads[1]", 0);
tm.getSnapshot(0, true);
memory.addRegion("Memory[test]", Lifespan.nowOn(0), b.range(0x00400000, 0x00400fff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
TraceSnapshot scratch = tm.getSnapshot(Long.MIN_VALUE, true);
snap = scratch.getKey();
scratch.setSchedule(TraceSchedule.ZERO.steppedForward(thread, 1));
view.setSnap(snap);
Disassembler dis =
Disassembler.getDisassembler(view, TaskMonitor.DUMMY, msg -> Msg.error(this, msg));
AddressSetView result = dis.disassemble(entry, set);
assertEquals(set, result);
assertEquals(4, memory.putBytes(0, entry, ByteBuffer.wrap(buf.getBytes())));
// No disassembly at snap 0
}
byte[] arr = new byte[4];
view.getMemory().getBytes(entry, arr);
assertArrayEquals(buf.getBytes(), arr);
assertUndefined(listing.getCodeUnitAt(entry));
}
@Test
public void testCreateCodeUnitsInScratchViewAfterBytesChanged() throws Throwable {
TraceTimeManager tm = b.trace.getTimeManager();
Address entry = b.addr(0x00400000);
AddressSetView set = b.set(b.range(0x00400000, 0x00400003));
Assembler asm = Assemblers.getAssembler(b.language);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("imm r1, #234");
buf.assemble("add r1, r1");
final long snap;
try (Transaction tx = b.startTransaction()) {
TraceThread thread = b.getOrAddThread("Threads[1]", 0);
tm.getSnapshot(0, true);
memory.addRegion("Memory[test]", Lifespan.nowOn(0), b.range(0x00400000, 0x00400fff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
TraceSnapshot scratch = tm.getSnapshot(Long.MIN_VALUE, true);
snap = scratch.getKey();
scratch.setSchedule(TraceSchedule.ZERO.steppedForward(thread, 1));
view.setSnap(snap);
Disassembler dis =
Disassembler.getDisassembler(view, TaskMonitor.DUMMY, msg -> Msg.error(this, msg));
AddressSetView result = dis.disassemble(entry, set);
assertEquals(set, result);
assertEquals(4, memory.putBytes(0, entry, ByteBuffer.wrap(buf.getBytes())));
// No disassembly at snap 0
// Attempt re-disassembly at scratch snap
result = dis.disassemble(entry, set);
assertEquals(set, result);
}
assertEquals("imm r1,#0xea", listing.getCodeUnitAt(entry).toString());
}
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -224,19 +224,30 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
expectU("0:10", "1:10"); expectU("0:10", "1:10");
expectU("0:t0-10", "0:t1-10"); expectU("0:t0-10", "0:t1-10");
// We don't know how many p-code steps complete an instruction step // We don't know how many p-code steps complete an instruction step
expectU("0:t0-10.1", "0:t0-11"); // But we need at least 2 to actually enter the instruction
expectU("0:t0-10.2", "0:t0-11");
expectU("0:t0-10;t1-5", "0:t0-11;t1-5"); expectU("0:t0-10;t1-5", "0:t0-11;t1-5");
expectR("0:t0-10", "0:t0-11"); expectR("0:t0-10", "0:t0-11");
expectR("0:t0-10", "0:t0-10;t1-5"); expectR("0:t0-10", "0:t0-10;t1-5");
expectR("0:t0-10", "0:t0-11;t1-5"); expectR("0:t0-10", "0:t0-11;t1-5");
expectR("0:t0-10", "0:t0-10.1"); expectR("0:t0-10", "0:t0-10.2");
expectR("0:t0-10", "0:t0-11.1"); expectR("0:t0-10", "0:t0-11.2");
expectR("0:t0-10", "0:t0-10;t1-5.1"); expectR("0:t0-10", "0:t0-10;t1-5.2");
expectR("0:t0-10", "0:t0-11;t1-5.1"); expectR("0:t0-10", "0:t0-11;t1-5.2");
expectE("0:t0-10", "0:t0-10"); expectE("0:t0-10", "0:t0-10");
expectE("0:t0-10.1", "0:t0-10.1"); expectE("0:t0-10.2", "0:t0-10.2");
}
@Test
public void testCompare2() {
TraceSchedule timeL = TraceSchedule.parse("0:t0-2.5");
TraceSchedule timeR = TraceSchedule.parse("0:t0-3");
assertEquals(CompareResult.UNREL_LT, timeL.compareSchedule(timeR));
assertEquals(CompareResult.REL_LT, timeL.assumeRecorded().compareSchedule(timeR));
assertEquals(CompareResult.UNREL_LT, timeL.compareSchedule(timeR.assumeRecorded()));
} }
public String strRelativize(String fromSpec, String toSpec) { public String strRelativize(String fromSpec, String toSpec) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -400,6 +400,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void stepInstruction() { public void stepInstruction() {
assertCompletedInstruction();
PcodeProgram inj = getInject(counter); PcodeProgram inj = getInject(counter);
if (inj != null) { if (inj != null) {
instruction = null; instruction = null;
@ -576,7 +577,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void executeInstruction() { public void executeInstruction() {
assertCompletedInstruction();
instruction = decoder.decodeInstruction(counter, context); instruction = decoder.decodeInstruction(counter, context);
PcodeProgram insProg = PcodeProgram.fromInstruction(instruction); PcodeProgram insProg = PcodeProgram.fromInstruction(instruction);
preExecuteInstruction(); preExecuteInstruction();

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -159,7 +159,7 @@ public class PcodeFrame {
} }
/** /**
* The number of p-code ops executed * Get and reset the number of p-code ops executed
* *
* <p> * <p>
* Contrast this to {@link #index()}, which marks the next op to be executed. This counts the * Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
@ -167,7 +167,9 @@ public class PcodeFrame {
* *
* @return the count * @return the count
*/ */
public int count() { public int resetCount() {
int count = this.count;
this.count = 0;
return count; return count;
} }