diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng.py index d6dd89a392..bf32012a3c 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng.py @@ -41,6 +41,10 @@ def main(): from ghidradbg import commands as cmd from ghidradbg.util import dbg + # So that the user can re-enter by typing repl() + global repl + repl = cmd.repl + cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) args = os.getenv('OPT_TARGET_ARGS') if args: @@ -51,7 +55,10 @@ def main(): cmd.ghidra_trace_sync_enable() # TODO: HACK - dbg.wait() + try: + dbg.wait() + except KeyboardInterrupt as ki: + dbg.interrupt() cmd.repl() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py index 09b4bbddfc..5648efa218 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py @@ -27,6 +27,7 @@ from ghidratrace.client import Client, Address, AddressRange, TraceObject from pybag import pydbg, userdbg, kerneldbg from pybag.dbgeng import core as DbgEng from pybag.dbgeng import exception +from pybag.dbgeng.win32.kernel32 import STILL_ACTIVE from . import util, arch, methods, hooks from .dbgmodel.imodelobject import ModelObjectKind @@ -841,6 +842,9 @@ def ghidra_trace_disassemble(address): @util.dbg.eng_thread def compute_proc_state(nproc=None): + exit_code = util.GetExitCode() + if exit_code is not None and exit_code != STILL_ACTIVE: + return 'TERMINATED' status = util.dbg._base._control.GetExecutionStatus() if status == DbgEng.DEBUG_STATUS_BREAK: return 'STOPPED' diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py index 31fa11d9ad..8877dfdba8 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py @@ -268,7 +268,8 @@ def on_process_selected(): @log_errors def on_process_deleted(*args): # print("ON_PROCESS_DELETED") - proc = args[0] + exit_code = args[0] + proc = util.selected_process() on_exited(proc) if proc in PROC_STATE: del PROC_STATE[proc] diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py index dd03897d73..47a9495855 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/hooks.py @@ -84,7 +84,7 @@ class ProcessState(object): except BaseException as e: print(f"Couldn't record page with SP: {e}") self.visited.add(hashable_frame) - if first or self.regions or self.threads or self.modules: + if first or self.regions or self.modules: # Sections, memory syscalls, or stack allocations commands.put_regions() self.regions = False @@ -153,7 +153,7 @@ def process_event(self, listener, event): print(f"Ignoring {desc} because target is invalid") return event_process = util.get_process() - if event_process.IsValid() and event_process not in PROC_STATE: + if event_process.IsValid() and event_process.GetProcessID() not in PROC_STATE: PROC_STATE[event_process.GetProcessID()] = ProcessState() rc = event_process.GetBroadcaster().AddListener(listener, ALL_EVENTS) if not rc: diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py index f769814431..7489164fc7 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py @@ -657,7 +657,7 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange): f'ghidra trace putmem 0x{offset_start:x} {range.length()}', result) if result.Succeeded(): return - print(f"Could not read 0x{offset_start:x}: {result}") + #print(f"Could not read 0x{offset_start:x}: {result}") exec_convert_errors( f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error') diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java index 3ac0d6b524..512f857679 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java @@ -22,8 +22,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.*; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; @@ -65,7 +64,8 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca if (reg == null) { return null; } - if (!thread.getLifespan().contains(snap)) { + Lifespan lifespan = thread.getLifespan(); + if (lifespan == null || !lifespan.contains(snap)) { return null; } TraceMemorySpace regs = reg.getAddressSpace().isRegisterSpace() diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java index 4ae640a4b4..04d0331921 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java @@ -20,6 +20,7 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; import ghidra.app.services.DebuggerLogicalBreakpointService; +import ghidra.app.services.DebuggerTraceManagerService; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -31,6 +32,7 @@ import ghidra.framework.plugintool.util.PluginStatus; status = PluginStatus.RELEASED, servicesRequired = { DebuggerLogicalBreakpointService.class, + DebuggerTraceManagerService.class, }, eventsConsumed = { TraceOpenedPluginEvent.class, diff --git a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java index 91ca083fc4..648768ce7c 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java @@ -33,7 +33,7 @@ import ghidra.features.bsim.query.description.DescriptionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.protocol.*; -import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.DomainFile; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionManager; @@ -100,14 +100,23 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript { gensig.addFunctionTags(dbInfo.functionTags); gensig.addDateColumnName(dbInfo.dateColumnName); - DomainFolder df = currentProgram.getDomainFile().getParent(); - URL folderURL = df.getSharedProjectURL(); - if (folderURL == null) { - folderURL = df.getLocalProjectURL(); + DomainFile dFile = currentProgram.getDomainFile(); + URL fileURL = dFile.getSharedProjectURL(null); + if (fileURL == null) { + fileURL = dFile.getLocalProjectURL(null); + } + if (fileURL == null) { + popup("Cannot add signatures for program which has never been saved"); + return; } - String path = GhidraURL.getProjectPathname(folderURL); - URL normalizedProjectURL = GhidraURL.getProjectURL(folderURL); + String path = GhidraURL.getProjectPathname(fileURL); + //bsim adds the program name to the path so we need to remove the program + //name here + int lastSlash = path.lastIndexOf('/'); + path = lastSlash == 0 ? "/" : path.substring(0, lastSlash); + + URL normalizedProjectURL = GhidraURL.getProjectURL(fileURL); String repo = normalizedProjectURL.toExternalForm(); gensig.openProgram(this.currentProgram, null, null, null, repo, path); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ExternalEntryFunctionAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ExternalEntryFunctionAnalyzer.java index 7495d36eaa..27cf96a020 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ExternalEntryFunctionAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ExternalEntryFunctionAnalyzer.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +19,7 @@ import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; import ghidra.program.model.address.*; -import ghidra.program.model.listing.Listing; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.util.task.TaskMonitor; @@ -42,8 +40,6 @@ public class ExternalEntryFunctionAnalyzer extends AbstractAnalyzer { */ public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) { - Listing listing = program.getListing(); - AddressSet funcStarts = new AddressSet(); monitor.setMessage("Finding External Entry Functions"); @@ -53,9 +49,17 @@ public class ExternalEntryFunctionAnalyzer extends AbstractAnalyzer { AddressIterator entryIter = program.getSymbolTable().getExternalEntryPointIterator(); while (entryIter.hasNext() && !monitor.isCancelled()) { Address entry = entryIter.next(); - if (set.contains(entry) && listing.getInstructionAt(entry) != null) { - funcStarts.addRange(entry, entry); + if (!set.contains(entry)) { + continue; } + + // check for any indicators this is a good start of a function + // must have an instruction at the entry, and not be part of another function + if (!isGoodFunctionStart(program, entry)) { + continue; + } + + funcStarts.addRange(entry, entry); } // remove any addresses that are already functions @@ -76,5 +80,35 @@ public class ExternalEntryFunctionAnalyzer extends AbstractAnalyzer { return true; } + + /** + * Check if address is a good function start. + * Instruction exists at the location. + * No instruction falls through to this one. + * + * @param program the program + * @param addr address to check if is a good function start + * @return true if would be a good function start, false otherwise + */ + public static boolean isGoodFunctionStart(Program program, Address addr) { + // check location starts with an instruction + if (program.getListing().getInstructionAt(addr) == null) { + return false; + } + Address addrBefore = addr.previous(); + if (addrBefore == null) { + return true; + } + + // check if instruction before, falls into this one. + // other code is responsible for creating functions from references + Instruction instr = program.getListing().getInstructionContaining(addrBefore); + if (instr != null && addr.equals(instr.getFallThrough())) { + return false; + } + + // didn't find anything that would indicate is a bad function start + return true; + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java index 77a3affa58..c9dc664a81 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java @@ -800,15 +800,10 @@ public class DialogComponentProvider * If the status message fits then there is no tool tip. */ private void updateStatusToolTip() { - String text = statusLabel.getText(); - // Get the width of the message. - FontMetrics fm = statusLabel.getFontMetrics(statusLabel.getFont()); - int messageWidth = 0; - if ((fm != null) && (text != null)) { - messageWidth = fm.stringWidth(text); - } - if (messageWidth > statusLabel.getWidth()) { - statusLabel.setToolTipText(text); + Dimension preferredSize = statusLabel.getPreferredSize(); + Dimension size = statusLabel.getSize(); + if (preferredSize.width > size.width || preferredSize.height > size.height) { + statusLabel.setToolTipText(statusLabel.getOriginalText()); } else { statusLabel.setToolTipText(null); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/label/AbstractHtmlLabel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/label/AbstractHtmlLabel.java index 5eb65d6fc9..373621dfa9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/label/AbstractHtmlLabel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/label/AbstractHtmlLabel.java @@ -76,6 +76,18 @@ public abstract class AbstractHtmlLabel extends JLabel updateHtmlView(); } + /** + * Returns the original text of the label. + *
+ * The {@link #getText()} method for this class can return a value that is missing the leading + * <html> tag. + * + * @return text of this label + */ + public String getOriginalText() { + return isHtml ? HTML_TAG + getText() : getText(); + } + @Override public void updateUI() { super.updateUI(); diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/local/LocalWindowsNativeProcessPtySession.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/local/LocalWindowsNativeProcessPtySession.java index 5b6a71208d..4df5657b76 100644 --- a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/local/LocalWindowsNativeProcessPtySession.java +++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/local/LocalWindowsNativeProcessPtySession.java @@ -25,6 +25,7 @@ import com.sun.jna.ptr.IntByReference; import ghidra.pty.PtySession; import ghidra.pty.windows.Handle; +import ghidra.pty.windows.jna.JobApiNative; import ghidra.util.Msg; public class LocalWindowsNativeProcessPtySession implements PtySession { @@ -33,14 +34,16 @@ public class LocalWindowsNativeProcessPtySession implements PtySession { private final Handle processHandle; //private final Handle threadHandle; private final String ptyName; + private final Handle jobHandle; public LocalWindowsNativeProcessPtySession(int pid, int tid, Handle processHandle, - Handle threadHandle, String ptyName) { + Handle threadHandle, String ptyName, Handle jobHandle) { this.pid = pid; //this.tid = tid; this.processHandle = processHandle; //this.threadHandle = threadHandle; this.ptyName = ptyName; + this.jobHandle = jobHandle; Msg.info(this, "local Windows Pty session. PID = " + pid); } @@ -84,17 +87,8 @@ public class LocalWindowsNativeProcessPtySession implements PtySession { @Override public void destroyForcibly() { - if (!Kernel32.INSTANCE.TerminateProcess(processHandle.getNative(), 1)) { - int error = Kernel32.INSTANCE.GetLastError(); - switch (error) { - case Kernel32.ERROR_ACCESS_DENIED: - /** - * This indicates the process has already terminated. It's unclear to me whether - * or not that is the only possible cause of this error. - */ - return; - } - throw new LastErrorException(error); + if (!JobApiNative.INSTANCE.TerminateJobObject(jobHandle.getNative(), 1).booleanValue()) { + throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); } } diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java index 952ec903ce..7c7cd83404 100644 --- a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java +++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java @@ -31,6 +31,7 @@ import ghidra.pty.PtyChild; import ghidra.pty.local.LocalWindowsNativeProcessPtySession; import ghidra.pty.windows.jna.ConsoleApiNative; import ghidra.pty.windows.jna.ConsoleApiNative.STARTUPINFOEX; +import ghidra.pty.windows.jna.JobApiNative; public class ConPtyChild extends ConPtyEndpoint implements PtyChild { @@ -83,6 +84,11 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild { * TODO: How to control local echo? */ + HANDLE hJob = JobApiNative.INSTANCE.CreateJobObjectW(null, null); + if (hJob == null) { + throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); + } + STARTUPINFOEX si = prepareStartupInfo(); PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); @@ -102,9 +108,13 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild { throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); } + if (!JobApiNative.INSTANCE.AssignProcessToJobObject(hJob, pi.hProcess).booleanValue()) { + throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); + } + return new LocalWindowsNativeProcessPtySession(pi.dwProcessId.intValue(), - pi.dwThreadId.intValue(), - new Handle(pi.hProcess), new Handle(pi.hThread), "ConPTY"); + pi.dwThreadId.intValue(), new Handle(pi.hProcess), new Handle(pi.hThread), "ConPTY", + new Handle(hJob)); } @Override diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/jna/JobApiNative.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/jna/JobApiNative.java new file mode 100644 index 0000000000..38d7638c81 --- /dev/null +++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/jna/JobApiNative.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pty.windows.jna; + +import com.sun.jna.Native; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.WinDef.BOOL; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.win32.StdCallLibrary; + +import ghidra.pty.windows.jna.ConsoleApiNative.SECURITY_ATTRIBUTES; + +public interface JobApiNative extends StdCallLibrary { + JobApiNative INSTANCE = Native.load("kernel32.dll", JobApiNative.class); + + HANDLE CreateJobObjectW(SECURITY_ATTRIBUTES.ByReference lpJobAttributes, WString lpName); + + BOOL AssignProcessToJobObject(HANDLE hJob, HANDLE hProcess); + + BOOL TerminateJobObject(HANDLE hJob, int uExitCode); +} diff --git a/GhidraDocs/InstallationGuide.html b/GhidraDocs/InstallationGuide.html index ee24ebdbe2..22bcd35509 100644 --- a/GhidraDocs/InstallationGuide.html +++ b/GhidraDocs/InstallationGuide.html @@ -98,7 +98,7 @@ Ghidra team if you have a specific need.
-The Debugger now uses Python to connect to the host platform's native debuggers. This requires -Python 3.9 or later and some additional packages. These packages are included in the distribution, +Python3 (3.7 to 3.12) and some additional packages. These packages are included in the distribution, but you may still install them from PyPI if you prefer: