mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-19 16:40:38 -09:00
GP-6664: PyGhidra now outputs full Java stacktrace
This commit is contained in:
@@ -567,6 +567,9 @@ import pdb # imports Python's pdb
|
||||
import pdb_ # imports Ghidra's pdb
|
||||
```
|
||||
## Change History
|
||||
__3.2.0__
|
||||
* When an uncaught `JException` occurs, PyGhidra will now output the full Java stack trace.
|
||||
|
||||
__3.1.0__
|
||||
* PyGhidra will now, by default, restore `sys.modules` to its prior state after a PyGhidra script is
|
||||
run (or the interactive interpreter is reset) so the next time a script is run, it freshly loads
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
__version__ = "3.1.0"
|
||||
__version__ = "3.2.0"
|
||||
|
||||
# stub for documentation and typing
|
||||
# this is mostly to hide the function parameter
|
||||
|
||||
@@ -20,6 +20,7 @@ import logging
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import types
|
||||
from code import InteractiveConsole
|
||||
from typing import Generator
|
||||
@@ -35,10 +36,10 @@ from java.lang import String # type:ignore @UnresolvedImport
|
||||
from java.lang import Thread as JThread # type:ignore @UnresolvedImport
|
||||
from java.util import Collections # type:ignore @UnresolvedImport
|
||||
from java.util.function import Consumer # type:ignore @UnresolvedImport
|
||||
from jpype import JClass, JImplements, JOverride
|
||||
from jpype import JClass, JImplements, JOverride, JException
|
||||
|
||||
from pyghidra.internal.plugin.completions import PythonCodeCompleter
|
||||
from pyghidra.script import PyGhidraScript
|
||||
from pyghidra.script import PyGhidraScript, print_stacktrace
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -296,8 +297,7 @@ class PyConsole(InteractiveConsole):
|
||||
super().showsyntaxerror(filename=filename, **kwargs)
|
||||
|
||||
def showtraceback(self) -> None:
|
||||
with self.redirect_writer():
|
||||
super().showtraceback()
|
||||
print_stacktrace(self._script, "<console>")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _run_context(self) -> Generator[None, None, None]:
|
||||
|
||||
@@ -32,7 +32,7 @@ from pathlib import Path
|
||||
from typing import Generator, List, NoReturn, Tuple, Union
|
||||
|
||||
import jpype
|
||||
from jpype import imports, _jpype
|
||||
from jpype import imports, _jpype, JException
|
||||
from packaging.version import Version
|
||||
|
||||
from pyghidra.javac import java_compile
|
||||
@@ -176,6 +176,12 @@ def _lastrun() -> Path:
|
||||
|
||||
return None
|
||||
|
||||
def _pyghidra_excepthook(exc_type, exc_value, tb):
|
||||
sys.__excepthook__(exc_type, exc_value, tb)
|
||||
if isinstance(exc_type, JException):
|
||||
# Remove the first line of the Java stack trace...it's already output
|
||||
sys.stderr.write(exc_value.stacktrace().partition("\n")[2])
|
||||
|
||||
class PyGhidraLauncher:
|
||||
"""
|
||||
Base pyghidra launcher
|
||||
@@ -221,6 +227,10 @@ class PyGhidraLauncher:
|
||||
self.vm_args = self._jvm_args()
|
||||
self.args = []
|
||||
self.app_info = ApplicationInfo.from_file(ghidra_dir / "application.properties")
|
||||
|
||||
# Install our custom excepthook (only if no one else already has)
|
||||
if sys.excepthook == sys.__excepthook__:
|
||||
sys.excepthook = _pyghidra_excepthook
|
||||
|
||||
def _setup_dev_classpath(self, utility_dir: Path):
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,7 @@ import traceback
|
||||
from collections.abc import ItemsView, KeysView
|
||||
from importlib.machinery import ModuleSpec, SourceFileLoader
|
||||
from pathlib import Path
|
||||
from jpype import JClass, JImplementationFor
|
||||
from jpype import JClass, JImplementationFor, JException
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -255,18 +255,7 @@ class PyGhidraScript(dict):
|
||||
spec.loader.exec_module(m)
|
||||
# pylint: disable=bare-except
|
||||
except:
|
||||
# filter the traceback so that it stops at the script
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
i = 0
|
||||
tb = traceback.extract_tb(exc_tb)
|
||||
for fs in tb:
|
||||
if fs.filename == script_path:
|
||||
break
|
||||
i += 1
|
||||
ss = traceback.StackSummary.from_list(tb[i:])
|
||||
e = traceback.TracebackException(exc_type, exc_value, exc_tb)
|
||||
e.stack = ss
|
||||
self._script.printerr(''.join(e.format()))
|
||||
print_stacktrace(self._script, script_path)
|
||||
finally:
|
||||
sys.argv = orig_argv
|
||||
|
||||
@@ -320,3 +309,22 @@ def get_current_interpreter():
|
||||
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def print_stacktrace(script, script_path):
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
i = 0
|
||||
tb = traceback.extract_tb(exc_tb)
|
||||
for fs in tb:
|
||||
if fs.filename == script_path:
|
||||
break
|
||||
i += 1
|
||||
ss = traceback.StackSummary.from_list(tb[i:])
|
||||
te = traceback.TracebackException(exc_type, exc_value, exc_tb)
|
||||
te.stack = ss
|
||||
output = ''.join(te.format())
|
||||
if isinstance(exc_type, JException):
|
||||
# Remove the first line of the Java stack trace...it's already output
|
||||
output += exc_value.stacktrace().partition('\n')[2]
|
||||
script.printerr(output)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user