mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -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
|
import pdb_ # imports Ghidra's pdb
|
||||||
```
|
```
|
||||||
## Change History
|
## Change History
|
||||||
|
__3.2.0__
|
||||||
|
* When an uncaught `JException` occurs, PyGhidra will now output the full Java stack trace.
|
||||||
|
|
||||||
__3.1.0__
|
__3.1.0__
|
||||||
* PyGhidra will now, by default, restore `sys.modules` to its prior state after a PyGhidra script is
|
* 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
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
__version__ = "3.1.0"
|
__version__ = "3.2.0"
|
||||||
|
|
||||||
# stub for documentation and typing
|
# stub for documentation and typing
|
||||||
# this is mostly to hide the function parameter
|
# this is mostly to hide the function parameter
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
import types
|
import types
|
||||||
from code import InteractiveConsole
|
from code import InteractiveConsole
|
||||||
from typing import Generator
|
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.lang import Thread as JThread # type:ignore @UnresolvedImport
|
||||||
from java.util import Collections # type:ignore @UnresolvedImport
|
from java.util import Collections # type:ignore @UnresolvedImport
|
||||||
from java.util.function import Consumer # 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.internal.plugin.completions import PythonCodeCompleter
|
||||||
from pyghidra.script import PyGhidraScript
|
from pyghidra.script import PyGhidraScript, print_stacktrace
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -296,8 +297,7 @@ class PyConsole(InteractiveConsole):
|
|||||||
super().showsyntaxerror(filename=filename, **kwargs)
|
super().showsyntaxerror(filename=filename, **kwargs)
|
||||||
|
|
||||||
def showtraceback(self) -> None:
|
def showtraceback(self) -> None:
|
||||||
with self.redirect_writer():
|
print_stacktrace(self._script, "<console>")
|
||||||
super().showtraceback()
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _run_context(self) -> Generator[None, None, None]:
|
def _run_context(self) -> Generator[None, None, None]:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from pathlib import Path
|
|||||||
from typing import Generator, List, NoReturn, Tuple, Union
|
from typing import Generator, List, NoReturn, Tuple, Union
|
||||||
|
|
||||||
import jpype
|
import jpype
|
||||||
from jpype import imports, _jpype
|
from jpype import imports, _jpype, JException
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
|
|
||||||
from pyghidra.javac import java_compile
|
from pyghidra.javac import java_compile
|
||||||
@@ -176,6 +176,12 @@ def _lastrun() -> Path:
|
|||||||
|
|
||||||
return None
|
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:
|
class PyGhidraLauncher:
|
||||||
"""
|
"""
|
||||||
Base pyghidra launcher
|
Base pyghidra launcher
|
||||||
@@ -221,6 +227,10 @@ class PyGhidraLauncher:
|
|||||||
self.vm_args = self._jvm_args()
|
self.vm_args = self._jvm_args()
|
||||||
self.args = []
|
self.args = []
|
||||||
self.app_info = ApplicationInfo.from_file(ghidra_dir / "application.properties")
|
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):
|
def _setup_dev_classpath(self, utility_dir: Path):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import traceback
|
|||||||
from collections.abc import ItemsView, KeysView
|
from collections.abc import ItemsView, KeysView
|
||||||
from importlib.machinery import ModuleSpec, SourceFileLoader
|
from importlib.machinery import ModuleSpec, SourceFileLoader
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jpype import JClass, JImplementationFor
|
from jpype import JClass, JImplementationFor, JException
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
@@ -255,18 +255,7 @@ class PyGhidraScript(dict):
|
|||||||
spec.loader.exec_module(m)
|
spec.loader.exec_module(m)
|
||||||
# pylint: disable=bare-except
|
# pylint: disable=bare-except
|
||||||
except:
|
except:
|
||||||
# filter the traceback so that it stops at the script
|
print_stacktrace(self._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:])
|
|
||||||
e = traceback.TracebackException(exc_type, exc_value, exc_tb)
|
|
||||||
e.stack = ss
|
|
||||||
self._script.printerr(''.join(e.format()))
|
|
||||||
finally:
|
finally:
|
||||||
sys.argv = orig_argv
|
sys.argv = orig_argv
|
||||||
|
|
||||||
@@ -320,3 +309,22 @@ def get_current_interpreter():
|
|||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return None
|
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